diff --git a/app-frontend/src/containers/monitoring/LoadBalancerStats.tsx b/app-frontend/src/containers/monitoring/LoadBalancerStats.tsx index fd0d2cf..f432d82 100644 --- a/app-frontend/src/containers/monitoring/LoadBalancerStats.tsx +++ b/app-frontend/src/containers/monitoring/LoadBalancerStats.tsx @@ -3,6 +3,7 @@ import ApiComponent from "../global/ApiComponent"; import { Row, Col, Card, Avatar, Icon, Tooltip } from "antd"; import Toaster from "../../utils/Toaster"; import CenteredSpinner from "../global/CenteredSpinner"; +import ErrorRetry from "../global/ErrorRetry"; class LoadBalancerStatsCard extends Component { render() { @@ -42,22 +43,30 @@ class LoadBalancerStatsCard extends Component { export default class LoadBalancerStats extends ApiComponent< {}, - { apiData: any } + { apiData: any; isLoading: boolean } > { private updateApiInterval: any; constructor(props: any) { super(props); - this.state = { apiData: undefined }; + this.state = { + apiData: undefined, + isLoading: true + }; } + updateApi() { const self = this; + self.setState({ isLoading: !self.state.apiData }); // set to true just the first time this.apiManager .getLoadBalancerInfo() .then(function(data) { self.setState({ apiData: data }); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } componentWillUnmount() { @@ -76,10 +85,14 @@ export default class LoadBalancerStats extends ApiComponent< } render() { - if (!this.state.apiData) { + if (!!this.state.isLoading) { return ; } + if (!this.state.apiData) { + return ; + } + return (
diff --git a/app-frontend/src/containers/monitoring/NetDataInfo.tsx b/app-frontend/src/containers/monitoring/NetDataInfo.tsx index 1cbc37a..7480f59 100644 --- a/app-frontend/src/containers/monitoring/NetDataInfo.tsx +++ b/app-frontend/src/containers/monitoring/NetDataInfo.tsx @@ -6,12 +6,17 @@ import CenteredSpinner from "../global/CenteredSpinner"; import NetDataDescription from "./NetDataDescription"; import NetDataSettingsForm from "./NetDataSettingsForm"; import Utils from "../../utils/Utils"; +import ErrorRetry from "../global/ErrorRetry"; -export default class NetDataInfo extends ApiComponent<{}, { apiData: any }> { +export default class NetDataInfo extends ApiComponent< + {}, + { apiData: any; isLoading: boolean } +> { constructor(props: any) { super(props); this.state = { - apiData: undefined + apiData: undefined, + isLoading: true }; } @@ -21,13 +26,16 @@ export default class NetDataInfo extends ApiComponent<{}, { apiData: any }> { refetchData() { const self = this; - self.setState({ apiData: undefined }); - this.apiManager + self.setState({ isLoading: true, apiData: undefined }); + return this.apiManager .getNetDataInfo() .then(function(data) { self.setState({ apiData: data }); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } toggleNetDataClicked(isActivated: boolean) { @@ -38,7 +46,7 @@ export default class NetDataInfo extends ApiComponent<{}, { apiData: any }> { onUpdateNetDataClicked(netDataInfo: any) { const self = this; - self.setState({ apiData: undefined }); + self.setState({ isLoading: true }); return this.apiManager .updateNetDataInfo(netDataInfo) .then(function() { @@ -57,10 +65,14 @@ export default class NetDataInfo extends ApiComponent<{}, { apiData: any }> { render() { const self = this; - if (!this.state.apiData) { + if (this.state.isLoading) { return ; } + if (!this.state.apiData) { + return ; + } + const netDataInfo = this.state.apiData; return ( diff --git a/app-frontend/src/containers/nodes/DockerRegistries.tsx b/app-frontend/src/containers/nodes/DockerRegistries.tsx index d51886b..3dd79ad 100644 --- a/app-frontend/src/containers/nodes/DockerRegistries.tsx +++ b/app-frontend/src/containers/nodes/DockerRegistries.tsx @@ -14,21 +14,23 @@ import { } from "../../models/IRegistryInfo"; import DockerRegistryAdd from "./DockerRegistryAdd"; import { emitDefaultRegistryChanged } from "../../actions/DefaultRegistryActions"; +import ErrorRetry from "../global/ErrorRetry"; class DockerRegistries extends ApiComponent< { emitDefaultRegistryChanged: Function }, - { apiData: IRegistryApi | undefined } + { apiData: IRegistryApi | undefined; isLoading: boolean } > { constructor(props: any) { super(props); this.state = { - apiData: undefined + apiData: undefined, + isLoading: true }; } fetchData() { const self = this; - this.setState({ apiData: undefined }); + this.setState({ apiData: undefined, isLoading: true }); this.apiManager .getDockerRegistries() .then(function(data) { @@ -37,12 +39,15 @@ class DockerRegistries extends ApiComponent< (data as IRegistryApi).defaultPushRegistryId ); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } changeDefault(id: string) { const self = this; - this.setState({ apiData: undefined }); + this.setState({ apiData: undefined, isLoading: true }); this.apiManager .setDefaultPushDockerRegistry(id) @@ -65,7 +70,7 @@ class DockerRegistries extends ApiComponent< ) .indexOf(true) >= 0; - this.setState({ apiData: undefined }); + this.setState({ apiData: undefined, isLoading: true }); (isSelfHosted ? this.apiManager.disableSelfHostedDockerRegistry() @@ -82,7 +87,7 @@ class DockerRegistries extends ApiComponent< editRegistry(dockerRegistry: IRegistryInfo) { const self = this; - this.setState({ apiData: undefined }); + this.setState({ apiData: undefined, isLoading: true }); this.apiManager .updateDockerRegistry(dockerRegistry) @@ -97,7 +102,7 @@ class DockerRegistries extends ApiComponent< addDockerRegistry(dockerRegistry: IRegistryInfo) { const self = this; - this.setState({ apiData: undefined }); + this.setState({ apiData: undefined, isLoading: true }); (dockerRegistry.registryType === IRegistryTypes.LOCAL_REG ? self.apiManager.enableSelfHostedDockerRegistry() : self.apiManager.addDockerRegistry(dockerRegistry) @@ -117,10 +122,14 @@ class DockerRegistries extends ApiComponent< render() { const self = this; - if (!this.state.apiData) { + if (this.state.isLoading) { return ; } + if (!this.state.apiData) { + return ; + } + return (
diff --git a/app-frontend/src/containers/settings/ChangePass.tsx b/app-frontend/src/containers/settings/ChangePass.tsx index 77bb62c..85a07fd 100644 --- a/app-frontend/src/containers/settings/ChangePass.tsx +++ b/app-frontend/src/containers/settings/ChangePass.tsx @@ -35,7 +35,7 @@ export default class ChangePass extends ApiComponent< this.apiManager .changePass(this.state.old, this.state.new1) .catch(Toaster.createCatcher()) - .then(function(data) { + .then(function() { self.setState({ isLoading: false }); }); } diff --git a/app-frontend/src/containers/settings/CheckUpdate.tsx b/app-frontend/src/containers/settings/CheckUpdate.tsx index 920ae74..2fef279 100644 --- a/app-frontend/src/containers/settings/CheckUpdate.tsx +++ b/app-frontend/src/containers/settings/CheckUpdate.tsx @@ -5,46 +5,65 @@ import CenteredSpinner from "../global/CenteredSpinner"; import Toaster from "../../utils/Toaster"; import ReloadCaptainModal from "./ReloadCaptainModal"; import { IVersionInfo } from "../../models/IVersionInfo"; +import ErrorRetry from "../global/ErrorRetry"; export default class CheckUpdate extends ApiComponent< {}, - { versionInfo: IVersionInfo | undefined; isRefreshTimerActivated: boolean } + { + versionInfo: IVersionInfo | undefined; + isRefreshTimerActivated: boolean; + isLoading: boolean; + } > { constructor(props: any) { super(props); this.state = { versionInfo: undefined, - isRefreshTimerActivated: false + isRefreshTimerActivated: false, + isLoading: true }; } componentDidMount() { const self = this; + self.setState({ isLoading: true }); self.apiManager .getVersionInfo() .then(function(data) { self.setState({ versionInfo: data }); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } onPerformUpdateClicked() { const self = this; const versionInfo = this.state.versionInfo; + self.setState({ isLoading: true }); self.apiManager .performUpdate(versionInfo!.latestVersion) - .then(function(data) { + .then(function() { self.setState({ isRefreshTimerActivated: true }); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } render() { const self = this; + + if (self.state.isLoading) { + return ; + } + const versionInfo = this.state.versionInfo; if (!versionInfo) { - return ; + return ; } return ( diff --git a/app-frontend/src/containers/settings/DiskCleanup.tsx b/app-frontend/src/containers/settings/DiskCleanup.tsx index 3ea57d4..8b59fe2 100644 --- a/app-frontend/src/containers/settings/DiskCleanup.tsx +++ b/app-frontend/src/containers/settings/DiskCleanup.tsx @@ -13,6 +13,7 @@ import ApiComponent from "../global/ApiComponent"; import Toaster from "../../utils/Toaster"; import CenteredSpinner from "../global/CenteredSpinner"; import UnusedImagesTable from "./UnusedImagesTable"; +import ErrorRetry from "../global/ErrorRetry"; export interface IUnusedImage { tags: string[]; @@ -22,6 +23,7 @@ export interface IUnusedImage { export default class DiskCleanup extends ApiComponent< {}, { + isLoading: boolean; mostRecentLimit: number; unusedImages?: IUnusedImage[]; selectedImagesForDelete: string[]; @@ -30,6 +32,7 @@ export default class DiskCleanup extends ApiComponent< constructor(props: any) { super(props); this.state = { + isLoading: false, mostRecentLimit: 2, selectedImagesForDelete: [], unusedImages: [] @@ -38,33 +41,45 @@ export default class DiskCleanup extends ApiComponent< onRemoveImagesClicked() { const self = this; - this.setState({ unusedImages: undefined }); + this.setState({ isLoading: true }); this.apiManager .deleteImages(this.state.selectedImagesForDelete) - .then(function(data) { + .then(function() { message.success("Unused images are deleted."); - self.onGetOldImagesClicked(); + self.refreshOldImagesList(); }) - .catch(Toaster.createCatcher()); + .catch( + Toaster.createCatcher(function() { + self.setState({ isLoading: false }); + }) + ); } - onGetOldImagesClicked() { + refreshOldImagesList() { const self = this; - this.setState({ unusedImages: undefined }); - this.apiManager + this.setState({ unusedImages: undefined, isLoading: true }); + return this.apiManager .getUnusedImages(this.state.mostRecentLimit) .then(function(data) { self.setState({ unusedImages: data.unusedImages }); }) - .catch(Toaster.createCatcher()); + .catch(Toaster.createCatcher()) + .then(function() { + self.setState({ isLoading: false }); + }); } render() { const self = this; + + if (self.state.isLoading) { + return ; + } + const unusedImages = this.state.unusedImages; if (!unusedImages) { - return ; + return ; } const hasSelectedImagesForRemoval = !!( @@ -119,7 +134,7 @@ export default class DiskCleanup extends ApiComponent<