diff --git a/libs/i18n/locales/en/translation.json b/libs/i18n/locales/en/translation.json index d3c1801fe..54f5ef969 100644 --- a/libs/i18n/locales/en/translation.json +++ b/libs/i18n/locales/en/translation.json @@ -602,6 +602,7 @@ "Stop": "Stop", "Restart": "Restart", "Open console": "Open console", + "The requested action failed": "The requested action failed", "Expand row": "Expand row", "Ready": "Ready", "Restarts": "Restarts", diff --git a/libs/ui-components/src/components/DetailsPage/Tables/ApplicationLifecycleActions.tsx b/libs/ui-components/src/components/DetailsPage/Tables/ApplicationLifecycleActions.tsx index 9c31b7e95..588e922f8 100644 --- a/libs/ui-components/src/components/DetailsPage/Tables/ApplicationLifecycleActions.tsx +++ b/libs/ui-components/src/components/DetailsPage/Tables/ApplicationLifecycleActions.tsx @@ -216,7 +216,7 @@ const ApplicationLifecycleActions = ({ } > diff --git a/libs/ui-components/src/hooks/useApplicationLifecycle.ts b/libs/ui-components/src/hooks/useApplicationLifecycle.ts index 8fed345a1..e76f6e1dd 100644 --- a/libs/ui-components/src/hooks/useApplicationLifecycle.ts +++ b/libs/ui-components/src/hooks/useApplicationLifecycle.ts @@ -2,7 +2,11 @@ import * as React from 'react'; import type { ApplicationStatusType, Device } from '@flightctl/types'; -import { type ApplicationLifecycleAction, shouldClearPendingLifecycleAction } from '../utils/applicationLifecycle'; +import { + type ApplicationLifecycleAction, + RESTART_PENDING_TIMEOUT_MS, + shouldClearPendingLifecycleAction, +} from '../utils/applicationLifecycle'; import { getErrorMessage } from '../utils/error'; import { useFetch } from './useFetch'; @@ -57,6 +61,21 @@ export const useApplicationLifecycle = ({ } }, [appRestarts, appStatus, pendingAction]); + React.useEffect(() => { + if (pendingAction !== 'restart') { + return; + } + + // Restarting a running application does not produce an observable change in status. + // If the action was successfully submitted, it is considered finished after a short timeout. + const timer = window.setTimeout(() => { + setPendingAction(null); + restartsAtRequestRef.current = undefined; + }, RESTART_PENDING_TIMEOUT_MS); + + return () => window.clearTimeout(timer); + }, [pendingAction]); + const executeAction = React.useCallback( async (action: ApplicationLifecycleAction) => { setIsSubmitting(true); diff --git a/libs/ui-components/src/utils/applicationLifecycle.ts b/libs/ui-components/src/utils/applicationLifecycle.ts index ff42f63fe..8f2a1af80 100644 --- a/libs/ui-components/src/utils/applicationLifecycle.ts +++ b/libs/ui-components/src/utils/applicationLifecycle.ts @@ -151,6 +151,12 @@ export const getDeviceAppLifecycleOverrides = ( return mergeApplicationLifecycleLayers(fleetOverrides, deviceOverrides); }; +/** + * The "status.apps[].restarts" field is only modified on "real" app restarts/crashes, not by successful restarts. + * Successfully restarting an application is not observable through status, and the action is cleared after a timeout. + */ +export const RESTART_PENDING_TIMEOUT_MS = 500; + export const shouldClearPendingLifecycleAction = ( pendingAction: ApplicationLifecycleAction, currentStatus: ApplicationStatusType, @@ -168,11 +174,13 @@ export const shouldClearPendingLifecycleAction = ( case 'start': return ( - currentStatus === ApplicationStatusType.ApplicationStatusStarting || + currentStatus === ApplicationStatusType.ApplicationStatusCompleted || currentStatus === ApplicationStatusType.ApplicationStatusRunning || currentStatus === ApplicationStatusType.ApplicationStatusError ); case 'restart': + // The following conditions to clearing pending "restart" actions may not always eventually be satisfied. + // See RESTART_PENDING_TIMEOUT_MS for more details. return ( currentStatus === ApplicationStatusType.ApplicationStatusStarting || currentStatus !== statusAtRequest ||