From 20267e2beb91b06ed1ed270f48e32799bfc6a276 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Sep 2024 15:58:03 +0200 Subject: [PATCH 01/14] Dont stop work on formatting error --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 3499d88b8..5c8c3580d 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,9 @@ lint: npm run "lint" npm run "lint-ts" +.PHONY: lint-fix +lint-fix: + npm run "lint-fix" .PHONY: lint-strict lint-strict: From bb9c520a728ca8587f1b032282b69c9ca6e64563 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Sep 2024 16:05:00 +0200 Subject: [PATCH 02/14] start work on alert --- src/components/component/toolbar.tsx | 26 +++--- .../active-component-overview.tsx | 2 + .../page-active-component/overview.tsx | 78 ++++++++++++------ .../deployment-component-overview.tsx | 1 + src/store/radix-api.ts | 79 ++++++++++++++++++- src/utils/sleep.ts | 3 + 6 files changed, 154 insertions(+), 35 deletions(-) create mode 100644 src/utils/sleep.ts diff --git a/src/components/component/toolbar.tsx b/src/components/component/toolbar.tsx index 7b6871541..4e1ca804d 100644 --- a/src/components/component/toolbar.tsx +++ b/src/components/component/toolbar.tsx @@ -1,14 +1,15 @@ import { Button, CircularProgress } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; +import { errorToast } from '../global-top-nav/styled-toaster'; import { - type Component, + Component, + useResetScaledComponentMutation, useRestartComponentMutation, - useStartComponentMutation, useStopComponentMutation, } from '../../store/radix-api'; import { getFetchErrorMessage } from '../../store/utils'; -import { errorToast } from '../global-top-nav/styled-toaster'; +import { sleep } from '../../utils/sleep'; type Props = { appName: string; @@ -16,7 +17,7 @@ type Props = { component?: Component; startEnabled?: boolean; stopEnabled?: boolean; - refetch?: () => unknown; + refetch?: Function; }; export function Toolbar({ appName, @@ -26,12 +27,12 @@ export function Toolbar({ stopEnabled, refetch, }: Props) { - const [startTrigger, startState] = useStartComponentMutation(); + const [resetTrigger, resetState] = useResetScaledComponentMutation(); const [restartTrigger, restartState] = useRestartComponentMutation(); const [stopTrigger, stopState] = useStopComponentMutation(); - const isStartEnabled = - !startState.isLoading && component?.status === 'Stopped'; + const isResetEnabled = + !resetState.isLoading && component?.replicasOverride != null; const isStopEnabled = !stopState.isLoading && @@ -50,14 +51,16 @@ export function Toolbar({ const onStart = async () => { try { - await startTrigger({ + await resetTrigger({ appName, envName, componentName: component.name, }).unwrap(); await refetch?.(); } catch (error) { - errorToast(`Failed to start component. ${getFetchErrorMessage(error)}`); + errorToast( + `Failed to resume component scaling. ${getFetchErrorMessage(error)}` + ); } }; const onStop = async () => { @@ -67,6 +70,7 @@ export function Toolbar({ envName, componentName: component.name, }).unwrap(); + await sleep(500); await refetch?.(); } catch (error) { errorToast(`Failed to stop component. ${getFetchErrorMessage(error)}`); @@ -88,8 +92,8 @@ export function Toolbar({
{startEnabled && ( - )} diff --git a/src/components/page-active-component/active-component-overview.tsx b/src/components/page-active-component/active-component-overview.tsx index 7a72f7937..904d1c8af 100644 --- a/src/components/page-active-component/active-component-overview.tsx +++ b/src/components/page-active-component/active-component-overview.tsx @@ -77,12 +77,14 @@ export const ActiveComponentOverview: FunctionComponent<{ refetch={refetch} />
diff --git a/src/components/page-active-component/overview.tsx b/src/components/page-active-component/overview.tsx index 52be9fb0e..307b22965 100644 --- a/src/components/page-active-component/overview.tsx +++ b/src/components/page-active-component/overview.tsx @@ -1,7 +1,6 @@ -import { Icon, Typography } from '@equinor/eds-core-react'; +import { Button, Icon, Typography } from '@equinor/eds-core-react'; import { external_link } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; -import type { FunctionComponent } from 'react'; import { DefaultAlias } from './default-alias'; @@ -11,64 +10,99 @@ import { ComponentPorts } from '../component/component-ports'; import { DockerImage } from '../docker-image'; import { ComponentStatusBadge } from '../status-badges'; -import type { +import { ApplicationAlias, Component, Deployment, DnsAlias as DnsAliasModel, ExternalDns, + useResetScaledComponentMutation, } from '../../store/radix-api'; import './style.css'; -import { externalUrls } from '../../externalUrls'; +import { DNSAliases } from './dns-aliases'; import { ResourceRequirements } from '../resource-requirements'; import { Runtime } from '../runtime'; -import { DNSAliases } from './dns-aliases'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { sleep } from '../../utils/sleep'; const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; -export const Overview: FunctionComponent<{ +type Props = { + appName: string; appAlias?: ApplicationAlias; dnsAliases?: DnsAliasModel[]; dnsExternalAliases?: ExternalDns[]; envName: string; component: Component; deployment: Deployment; -}> = ({ + refetch: Function; +}; +export const Overview = ({ + appName, appAlias, dnsAliases, dnsExternalAliases, envName, component, deployment, -}) => { + refetch, +}: Props) => { const dnsAliasUrls = dnsAliases ? dnsAliases.map((alias) => alias.url) : []; const dnsExternalAliasUrls = dnsExternalAliases ? dnsExternalAliases.map((alias) => alias.fqdn) : []; + const [resetTrigger, { isLoading: isLoadingReset }] = + useResetScaledComponentMutation(); const isStopped = component.status == 'Stopped'; - const isScaledDown = - component.horizontalScalingSummary?.desiredReplicas === 0 && isStopped; + const isManuallyStopped = component.replicasOverride === 0 && isStopped; + const isManuallyScaled = + component.replicasOverride != null && !isManuallyStopped; + const isScaledDown = isStopped && !isManuallyStopped; + + const onReset = handlePromiseWithToast(async () => { + await resetTrigger({ + appName, + envName, + componentName: component.name, + }).unwrap(); + await sleep(1000); + await refetch(); + }); return (
- Overview - - {isStopped && !isScaledDown && ( - - Component has been manually stopped; please note that a new deployment - will cause it to be restarted unless you set replicas of - the component to 0 in{' '} - + Component has been manually stopped; Click reset below and resume + regular scaling. + + + )} + {isManuallyScaled && ( + + Component has been manually scaled; Click reset below and resume + regular scaling. + )} {isScaledDown && Component has been stopped by autoscaler.} + Overview
diff --git a/src/components/page-deployment-component/deployment-component-overview.tsx b/src/components/page-deployment-component/deployment-component-overview.tsx index bc6006797..b520128a7 100644 --- a/src/components/page-deployment-component/deployment-component-overview.tsx +++ b/src/components/page-deployment-component/deployment-component-overview.tsx @@ -48,6 +48,7 @@ export const DeploymentComponentOverview: FunctionComponent<{ {deployment && component && ( <> ({ + query: (queryArg) => ({ + url: `/applications/${queryArg.appName}/environments/${queryArg.envName}/components/${queryArg.componentName}/reset-scale`, + method: 'POST', + headers: { + 'Impersonate-User': queryArg['Impersonate-User'], + 'Impersonate-Group': queryArg['Impersonate-Group'], + }, + }), + }), restartComponent: build.mutation< RestartComponentApiResponse, RestartComponentApiArg @@ -709,6 +722,19 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + resetManuallyScaledComponentsInEnvironment: build.mutation< + ResetManuallyScaledComponentsInEnvironmentApiResponse, + ResetManuallyScaledComponentsInEnvironmentApiArg + >({ + query: (queryArg) => ({ + url: `/applications/${queryArg.appName}/environments/${queryArg.envName}/reset-scale`, + method: 'POST', + headers: { + 'Impersonate-User': queryArg['Impersonate-User'], + 'Impersonate-Group': queryArg['Impersonate-Group'], + }, + }), + }), restartEnvironment: build.mutation< RestartEnvironmentApiResponse, RestartEnvironmentApiArg @@ -1007,6 +1033,19 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + resetManuallyScaledComponentsInApplication: build.mutation< + ResetManuallyScaledComponentsInApplicationApiResponse, + ResetManuallyScaledComponentsInApplicationApiArg + >({ + query: (queryArg) => ({ + url: `/applications/${queryArg.appName}/reset-scale`, + method: 'POST', + headers: { + 'Impersonate-User': queryArg['Impersonate-User'], + 'Impersonate-Group': queryArg['Impersonate-Group'], + }, + }), + }), restartApplication: build.mutation< RestartApplicationApiResponse, RestartApplicationApiArg @@ -1487,6 +1526,19 @@ export type ReplicaLogApiArg = { /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ 'Impersonate-Group'?: string; }; +export type ResetScaledComponentApiResponse = unknown; +export type ResetScaledComponentApiArg = { + /** Name of application */ + appName: string; + /** Name of environment */ + envName: string; + /** Name of component */ + componentName: string; + /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ + 'Impersonate-User'?: string; + /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ + 'Impersonate-Group'?: string; +}; export type RestartComponentApiResponse = unknown; export type RestartComponentApiArg = { /** Name of application */ @@ -1840,6 +1892,17 @@ export type JobLogApiArg = { /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ 'Impersonate-Group'?: string; }; +export type ResetManuallyScaledComponentsInEnvironmentApiResponse = unknown; +export type ResetManuallyScaledComponentsInEnvironmentApiArg = { + /** Name of application */ + appName: string; + /** Name of environment */ + envName: string; + /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ + 'Impersonate-User'?: string; + /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ + 'Impersonate-Group'?: string; +}; export type RestartEnvironmentApiResponse = unknown; export type RestartEnvironmentApiArg = { /** Name of application */ @@ -2133,6 +2196,15 @@ export type RegenerateDeployKeyApiArg = { /** Regenerate deploy key and secret data */ regenerateDeployKeyAndSecretData: RegenerateDeployKeyAndSecretData; }; +export type ResetManuallyScaledComponentsInApplicationApiResponse = unknown; +export type ResetManuallyScaledComponentsInApplicationApiArg = { + /** Name of application */ + appName: string; + /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ + 'Impersonate-User'?: string; + /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ + 'Impersonate-Group'?: string; +}; export type RestartApplicationApiResponse = unknown; export type RestartApplicationApiArg = { /** Name of application */ @@ -2371,6 +2443,8 @@ export type Component = { replicaList?: ReplicaSummary[]; /** Deprecated: Array of pod names. Use ReplicaList instead */ replicas?: string[]; + /** Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and >= 1 is manually scaled. */ + replicasOverride?: number | null; resources?: ResourceRequirements; runtime?: Runtime; /** ScheduledJobPayloadPath defines the payload path, where payload for Job Scheduler will be mapped as a file. From radixconfig.yaml */ @@ -2901,8 +2975,6 @@ export type ScheduledJobSummary = { timeLimitSeconds?: number; }; export type ScheduledBatchSummary = { - /** Defines a user defined ID of the batch. */ - batchId?: string; /** Created timestamp */ created?: string; /** DeploymentName name of RadixDeployment for the batch */ @@ -3308,6 +3380,7 @@ export const { useChangeEnvVarMutation, useUpdateComponentExternalDnsTlsMutation, useReplicaLogQuery, + useResetScaledComponentMutation, useRestartComponentMutation, useScaleComponentMutation, useGetAzureKeyVaultSecretVersionsQuery, @@ -3331,6 +3404,7 @@ export const { useRestartJobMutation, useStopJobMutation, useJobLogQuery, + useResetManuallyScaledComponentsInEnvironmentMutation, useRestartEnvironmentMutation, useStartEnvironmentMutation, useStopEnvironmentMutation, @@ -3354,6 +3428,7 @@ export const { useGetPrivateImageHubsQuery, useUpdatePrivateImageHubsSecretValueMutation, useRegenerateDeployKeyMutation, + useResetManuallyScaledComponentsInApplicationMutation, useRestartApplicationMutation, useStartApplicationMutation, useStopApplicationMutation, diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 000000000..055d9b496 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(durationMs: number): Promise { + return new Promise((resolve) => setTimeout(resolve, durationMs)); +} From a74e934a0aef406e0767301a6a4326c8f9bd6140 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 5 Sep 2024 16:15:02 +0200 Subject: [PATCH 03/14] update from master --- src/components/component/toolbar.tsx | 6 ++-- .../page-active-component/overview.tsx | 28 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/components/component/toolbar.tsx b/src/components/component/toolbar.tsx index 4e1ca804d..fbc8c1581 100644 --- a/src/components/component/toolbar.tsx +++ b/src/components/component/toolbar.tsx @@ -1,15 +1,15 @@ import { Button, CircularProgress } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { errorToast } from '../global-top-nav/styled-toaster'; import { - Component, + type Component, useResetScaledComponentMutation, useRestartComponentMutation, useStopComponentMutation, } from '../../store/radix-api'; import { getFetchErrorMessage } from '../../store/utils'; import { sleep } from '../../utils/sleep'; +import { errorToast } from '../global-top-nav/styled-toaster'; type Props = { appName: string; @@ -17,7 +17,7 @@ type Props = { component?: Component; startEnabled?: boolean; stopEnabled?: boolean; - refetch?: Function; + refetch?: () => unknown; }; export function Toolbar({ appName, diff --git a/src/components/page-active-component/overview.tsx b/src/components/page-active-component/overview.tsx index 307b22965..0b67372d4 100644 --- a/src/components/page-active-component/overview.tsx +++ b/src/components/page-active-component/overview.tsx @@ -11,19 +11,19 @@ import { DockerImage } from '../docker-image'; import { ComponentStatusBadge } from '../status-badges'; import { - ApplicationAlias, - Component, - Deployment, - DnsAlias as DnsAliasModel, - ExternalDns, + type ApplicationAlias, + type Component, + type Deployment, + type DnsAlias as DnsAliasModel, + type ExternalDns, useResetScaledComponentMutation, } from '../../store/radix-api'; import './style.css'; -import { DNSAliases } from './dns-aliases'; +import { sleep } from '../../utils/sleep'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; import { ResourceRequirements } from '../resource-requirements'; import { Runtime } from '../runtime'; -import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; -import { sleep } from '../../utils/sleep'; +import { DNSAliases } from './dns-aliases'; const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; @@ -35,7 +35,7 @@ type Props = { envName: string; component: Component; deployment: Deployment; - refetch: Function; + refetch: () => unknown; }; export const Overview = ({ appName, @@ -74,8 +74,9 @@ export const Overview = ({
{isManuallyStopped && ( - Component has been manually stopped; Click reset below and resume - regular scaling. + Component has been manually stopped; Click reset to resume regular + scaling. +
- )} - - {stopEnabled && ( - - )} - - - - {restartInProgress && } -
-
- ); -} - -Toolbar.propTypes = { - appName: PropTypes.string.isRequired, - envName: PropTypes.string.isRequired, - component: PropTypes.object as PropTypes.Validator, - startEnabled: PropTypes.bool, - stopEnabled: PropTypes.bool, -}; diff --git a/src/components/global-top-nav/styled-toaster.tsx b/src/components/global-top-nav/styled-toaster.tsx index 0c8084406..4a3d464c9 100644 --- a/src/components/global-top-nav/styled-toaster.tsx +++ b/src/components/global-top-nav/styled-toaster.tsx @@ -83,7 +83,8 @@ export function successToast( export function handlePromiseWithToast, TReturn>( fn: (...args: TArgs) => TReturn, - successContent = 'Saved' + successContent = 'Saved', + errorContent = 'Error while saving' ) { return async (...args: TArgs): Promise | undefined> => { try { @@ -91,7 +92,7 @@ export function handlePromiseWithToast, TReturn>( successToast(successContent); return ret; } catch (e) { - errorToast(`Error while saving. ${getFetchErrorMessage(e)}`); + errorToast(`${errorContent}. ${getFetchErrorMessage(e)}`); return undefined; } }; diff --git a/src/components/page-active-component/active-component-overview.tsx b/src/components/page-active-component/active-component-overview.tsx index 904d1c8af..814f4ca8b 100644 --- a/src/components/page-active-component/active-component-overview.tsx +++ b/src/components/page-active-component/active-component-overview.tsx @@ -19,22 +19,27 @@ import { getEnvsUrl } from '../../utils/routing'; import AsyncResource from '../async-resource/async-resource'; import { Breadcrumb } from '../breadcrumb'; import { ActiveComponentSecrets } from '../component/secrets/active-component-secrets'; -import { Toolbar } from '../component/toolbar'; import { EnvironmentVariables } from '../environment-variables'; import { routeWithParams } from '../../utils/string'; import './style.css'; +import { ActiveComponentToolbar } from './active-component-toolbar'; +import { ComponentStatus } from './component-status'; export const ActiveComponentOverview: FunctionComponent<{ appName: string; envName: string; componentName: string; }> = ({ appName, envName, componentName }) => { - const { data: application, refetch } = useGetApplicationQuery( + const { data: application } = useGetApplicationQuery( { appName }, { skip: !appName, pollingInterval } ); - const { data: environment, ...envState } = useGetEnvironmentQuery( + const { + data: environment, + refetch, + ...envState + } = useGetEnvironmentQuery( { appName, envName }, { skip: !appName || !envName, pollingInterval } ); @@ -68,23 +73,27 @@ export const ActiveComponentOverview: FunctionComponent<{ {component && ( <> - + + +
diff --git a/src/components/page-active-component/active-component-toolbar.tsx b/src/components/page-active-component/active-component-toolbar.tsx new file mode 100644 index 000000000..02566f425 --- /dev/null +++ b/src/components/page-active-component/active-component-toolbar.tsx @@ -0,0 +1,87 @@ +import { Button, CircularProgress } from '@equinor/eds-core-react'; +import { + type Component, + useRestartComponentMutation, + useScaleComponentMutation, + useStopComponentMutation, +} from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; + +type Props = { + component: Component; + appName: string; + envName: string; +}; + +export function ActiveComponentToolbar({ component, appName, envName }: Props) { + const [restartTrigger, restartState] = useRestartComponentMutation(); + const [stopTrigger, stopState] = useStopComponentMutation(); + const [scaleTrigger, scaleState] = useScaleComponentMutation(); + + const isStopped = component?.status === 'Stopped'; + const restartInProgress = + restartState.isLoading || + component?.status === 'Reconciling' || + component?.status === 'Restarting'; + + return ( + <> +
+
+ + + + + {restartInProgress && } +
+
+ + ); +} diff --git a/src/components/page-active-component/component-status.tsx b/src/components/page-active-component/component-status.tsx new file mode 100644 index 000000000..2f7c4ae39 --- /dev/null +++ b/src/components/page-active-component/component-status.tsx @@ -0,0 +1,77 @@ +import { Button } from '@equinor/eds-core-react'; +import { + type Component, + useResetScaledComponentMutation, +} from '../../store/radix-api'; +import { sleep } from '../../utils/sleep'; +import { Alert } from '../alert'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; + +type Props = { + appName: string; + envName: string; + componentName: string; + refetch: () => unknown; + component: Component; +}; + +export function ComponentStatus({ + appName, + envName, + componentName, + refetch, + component, +}: Props) { + const [resetTrigger, resetState] = useResetScaledComponentMutation(); + const onReset = handlePromiseWithToast(async () => { + await resetTrigger({ + appName, + envName, + componentName, + }).unwrap(); + await sleep(1000); + await refetch(); + }); + + const isStopped = component?.status === 'Stopped'; + const isManuallyScaled = component?.replicasOverride != null; + const isManuallyStopped = isStopped && isManuallyScaled; + return ( + <> + {isStopped && !isManuallyScaled && ( + Component has been stopped by autoscaler. + )} + {isManuallyStopped && ( + + Component has been manually stopped; Click reset to resume regular + scaling. +
+ +
+ )} + + {isManuallyScaled && !isManuallyStopped && ( + + Component has been manually scaled; Click reset to resume regular + scaling. +
+ +
+ )} + + ); +} diff --git a/src/components/page-active-component/overview.tsx b/src/components/page-active-component/overview.tsx index da50fb1c5..3d32b8af6 100644 --- a/src/components/page-active-component/overview.tsx +++ b/src/components/page-active-component/overview.tsx @@ -1,26 +1,22 @@ -import { Button, Icon, Typography } from '@equinor/eds-core-react'; +import { Icon, Typography } from '@equinor/eds-core-react'; import { external_link } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; import { DefaultAlias } from './default-alias'; -import { Alert } from '../alert'; import { ComponentIdentity } from '../component/component-identity'; import { ComponentPorts } from '../component/component-ports'; import { DockerImage } from '../docker-image'; import { ComponentStatusBadge } from '../status-badges'; -import { - type ApplicationAlias, - type Component, - type Deployment, - type DnsAlias as DnsAliasModel, - type ExternalDns, - useResetScaledComponentMutation, +import type { + ApplicationAlias, + Component, + Deployment, + DnsAlias as DnsAliasModel, + ExternalDns, } from '../../store/radix-api'; import './style.css'; -import { sleep } from '../../utils/sleep'; -import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; import { ResourceRequirements } from '../resource-requirements'; import { Runtime } from '../runtime'; import { DNSAliases } from './dns-aliases'; @@ -28,82 +24,28 @@ import { DNSAliases } from './dns-aliases'; const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; type Props = { - appName: string; appAlias?: ApplicationAlias; dnsAliases?: DnsAliasModel[]; dnsExternalAliases?: ExternalDns[]; envName: string; component: Component; deployment: Deployment; - refetch: () => unknown; }; export const Overview = ({ - appName, appAlias, dnsAliases, dnsExternalAliases, envName, component, deployment, - refetch, }: Props) => { const dnsAliasUrls = dnsAliases ? dnsAliases.map((alias) => alias.url) : []; const dnsExternalAliasUrls = dnsExternalAliases ? dnsExternalAliases.map((alias) => alias.fqdn) : []; - const [resetTrigger, { isLoading: isLoadingReset }] = - useResetScaledComponentMutation(); - - const isStopped = component.status == 'Stopped'; - const isManuallyStopped = component.replicasOverride === 0 && isStopped; - const isManuallyScaled = - component.replicasOverride != null && !isManuallyStopped; - const isScaledDown = isStopped && !isManuallyStopped; - - const onReset = handlePromiseWithToast(async () => { - await resetTrigger({ - appName, - envName, - componentName: component.name, - }).unwrap(); - await sleep(1000); - await refetch(); - }); return (
- {isManuallyStopped && ( - - Component has been manually stopped; Click reset to resume regular - scaling. -
- -
- )} - {isManuallyScaled && ( - - Component has been manually scaled; Click reset to resume regular - scaling. -
- -
- )} - {isScaledDown && Component has been stopped by autoscaler.} - Overview
diff --git a/src/components/page-active-job-component/active-job-component-overview.tsx b/src/components/page-active-job-component/active-job-component-overview.tsx index 52dea23c9..90ea6ccb4 100644 --- a/src/components/page-active-job-component/active-job-component-overview.tsx +++ b/src/components/page-active-job-component/active-job-component-overview.tsx @@ -4,12 +4,14 @@ import type { FunctionComponent } from 'react'; import { JobComponentVulnerabilityDetails } from './job-component-vulnerability-details'; import { Overview } from './overview'; +import { Button, CircularProgress } from '@equinor/eds-core-react'; import { routes } from '../../routes'; import { pollingInterval } from '../../store/defaults'; import { useGetBatchesQuery, useGetEnvironmentQuery, useGetJobsQuery, + useRestartComponentMutation, } from '../../store/radix-api'; import { getEnvsUrl } from '../../utils/routing'; import { routeWithParams } from '../../utils/string'; @@ -18,8 +20,8 @@ import { Breadcrumb } from '../breadcrumb'; import { ScheduledBatchList } from '../component/scheduled-job/scheduled-batch-list'; import { ScheduledJobList } from '../component/scheduled-job/scheduled-job-list'; import { ActiveComponentSecrets } from '../component/secrets/active-component-secrets'; -import { Toolbar } from '../component/toolbar'; import { EnvironmentVariables } from '../environment-variables'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; import { ComponentReplicaList } from '../page-active-component/component-replica-list'; export const ActiveJobComponentOverview: FunctionComponent<{ @@ -52,6 +54,12 @@ export const ActiveJobComponentOverview: FunctionComponent<{ const component = deployment?.components?.find( ({ name }) => name === jobComponentName ); + const [restartTrigger, restartState] = useRestartComponentMutation(); + const isStopped = component?.status === 'Stopped'; + const restartInProgress = + restartState.isLoading || + component?.status === 'Reconciling' || + component?.status === 'Restarting'; return ( <> @@ -66,16 +74,31 @@ export const ActiveJobComponentOverview: FunctionComponent<{ { label: jobComponentName }, ]} /> - {component && ( <> - +
+
+ + {restartInProgress && } +
+
diff --git a/src/components/page-deployment-component/deployment-component-overview.tsx b/src/components/page-deployment-component/deployment-component-overview.tsx index b520128a7..e9cf904d0 100644 --- a/src/components/page-deployment-component/deployment-component-overview.tsx +++ b/src/components/page-deployment-component/deployment-component-overview.tsx @@ -16,7 +16,11 @@ export const DeploymentComponentOverview: FunctionComponent<{ deploymentName: string; componentName: string; }> = ({ appName, deploymentName, componentName }) => { - const { data: deployment, ...deploymentState } = useGetDeploymentQuery( + const { + data: deployment, + refetch, + ...deploymentState + } = useGetDeploymentQuery( { appName, deploymentName }, { skip: !appName || !deploymentName, pollingInterval } ); @@ -48,7 +52,6 @@ export const DeploymentComponentOverview: FunctionComponent<{ {deployment && component && ( <> Date: Mon, 9 Sep 2024 15:52:29 +0200 Subject: [PATCH 06/14] Scale popup --- .../active-component-toolbar.tsx | 117 +++++++++++++++--- .../status-popover/status-popover.tsx | 4 +- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/components/page-active-component/active-component-toolbar.tsx b/src/components/page-active-component/active-component-toolbar.tsx index 02566f425..aeb9f0eaf 100644 --- a/src/components/page-active-component/active-component-toolbar.tsx +++ b/src/components/page-active-component/active-component-toolbar.tsx @@ -1,4 +1,11 @@ -import { Button, CircularProgress } from '@equinor/eds-core-react'; +import { + Button, + CircularProgress, + Slider, + TextField, + Typography, +} from '@equinor/eds-core-react'; +import { type ChangeEvent, useState } from 'react'; import { type Component, useRestartComponentMutation, @@ -6,6 +13,7 @@ import { useStopComponentMutation, } from '../../store/radix-api'; import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { ScrimPopup } from '../scrim-popup'; type Props = { component: Component; @@ -16,7 +24,6 @@ type Props = { export function ActiveComponentToolbar({ component, appName, envName }: Props) { const [restartTrigger, restartState] = useRestartComponentMutation(); const [stopTrigger, stopState] = useStopComponentMutation(); - const [scaleTrigger, scaleState] = useScaleComponentMutation(); const isStopped = component?.status === 'Stopped'; const restartInProgress = @@ -28,23 +35,12 @@ export function ActiveComponentToolbar({ component, appName, envName }: Props) { <>
- + + + setVisibleScrim(false)} + isDismissable + > + + ) => + setReplicas(Number(e.target.value)) + } + /> +
+
+ + Manually scale component. This will disable any automatic scaling{' '} +
+ untill manuall scaling is reset. +
+
+ + + + +
+
+
+ ); +} diff --git a/src/components/status-popover/status-popover.tsx b/src/components/status-popover/status-popover.tsx index a7eb167e2..aa2f245c7 100644 --- a/src/components/status-popover/status-popover.tsx +++ b/src/components/status-popover/status-popover.tsx @@ -23,7 +23,6 @@ export type StatusPopoverType = | 'default'; export type StatusPopoverProps = { - className?: string; title?: ReactNode; icon?: ReactNode; type?: StatusPopoverType; @@ -33,7 +32,6 @@ export const StatusPopover: FunctionComponent< PropsWithChildren > = ({ children, - className, title, icon = , type, @@ -54,7 +52,7 @@ export const StatusPopover: FunctionComponent< {title} )} - {children} + {children} Date: Tue, 10 Sep 2024 08:33:48 +0200 Subject: [PATCH 07/14] Scale popup --- .../active-component-toolbar.tsx | 83 ++++++++++--------- .../component-status.tsx | 4 +- .../page-active-component/style.css | 7 ++ src/store/radix-api.ts | 4 +- 4 files changed, 54 insertions(+), 44 deletions(-) diff --git a/src/components/page-active-component/active-component-toolbar.tsx b/src/components/page-active-component/active-component-toolbar.tsx index aeb9f0eaf..490c3be25 100644 --- a/src/components/page-active-component/active-component-toolbar.tsx +++ b/src/components/page-active-component/active-component-toolbar.tsx @@ -1,11 +1,11 @@ import { Button, CircularProgress, + Dialog, Slider, - TextField, Typography, } from '@equinor/eds-core-react'; -import { type ChangeEvent, useState } from 'react'; +import { useState } from 'react'; import { type Component, useRestartComponentMutation, @@ -13,7 +13,7 @@ import { useStopComponentMutation, } from '../../store/radix-api'; import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; -import { ScrimPopup } from '../scrim-popup'; +import './style.css'; type Props = { component: Component; @@ -35,14 +35,18 @@ export function ActiveComponentToolbar({ component, appName, envName }: Props) { <>
- @@ -87,17 +91,20 @@ type ScaleProps = { appName: string; envName: string; componentName: string; + currentReplicas: number; }; -function ScaleButtonPopover({ +function ScaleButtonPopup({ disabled, appName, envName, componentName, + currentReplicas, }: ScaleProps) { - const [replicas, setReplicas] = useState(0); + const [replicas, setReplicas] = useState(null); const [visibleScrim, setVisibleScrim] = useState(false); const [scaleTrigger, scaleState] = useScaleComponentMutation(); + const current = replicas ?? currentReplicas; const onScale = handlePromiseWithToast( async () => { @@ -105,9 +112,10 @@ function ScaleButtonPopover({ appName, envName, componentName, - replicas: replicas.toFixed(), + replicas: current.toFixed(), }).unwrap(); setVisibleScrim(false); + setReplicas(null); }, 'Scaling component', 'Failed to scale component' @@ -119,48 +127,41 @@ function ScaleButtonPopover({ Scale - setVisibleScrim(false)} isDismissable + style={{ width: '400px' }} > - - ) => - setReplicas(Number(e.target.value)) - } - /> -
-
- - Manually scale component. This will disable any automatic scaling{' '} -
- untill manuall scaling is reset. -
-
- + + Scale Component + + + + This will disable any automatic scaling untill manuall scaling is + reset. + + setReplicas(values[0])} + aria-labelledby="simple-slider" + /> + + + +
- -
- +
+ +
); } diff --git a/src/components/page-active-component/component-status.tsx b/src/components/page-active-component/component-status.tsx index 2f7c4ae39..7f18ad530 100644 --- a/src/components/page-active-component/component-status.tsx +++ b/src/components/page-active-component/component-status.tsx @@ -59,8 +59,8 @@ export function ComponentStatus({ {isManuallyScaled && !isManuallyStopped && ( - Component has been manually scaled; Click reset to resume regular - scaling. + Component has been manually scaled to {component.replicasOverride}{' '} + replicas; Click reset to resume regular scaling.
@@ -65,21 +103,11 @@ export function ActiveComponentToolbar({ component, appName, envName }: Props) { - {restartInProgress && } + {isWorking && }
@@ -88,44 +116,24 @@ export function ActiveComponentToolbar({ component, appName, envName }: Props) { type ScaleProps = { disabled: boolean; - appName: string; - envName: string; - componentName: string; currentReplicas: number; + onScale: (replicas: number) => unknown; }; -function ScaleButtonPopup({ - disabled, - appName, - envName, - componentName, - currentReplicas, -}: ScaleProps) { +function ScaleButtonPopup({ disabled, currentReplicas, onScale }: ScaleProps) { const [replicas, setReplicas] = useState(null); const [visibleScrim, setVisibleScrim] = useState(false); - const [scaleTrigger, scaleState] = useScaleComponentMutation(); const current = replicas ?? currentReplicas; - const onScale = handlePromiseWithToast( - async () => { - await scaleTrigger({ - appName, - envName, - componentName, - replicas: current.toFixed(), - }).unwrap(); - setVisibleScrim(false); - setReplicas(null); - }, - 'Scaling component', - 'Failed to scale component' - ); + const onLocalScale = async () => { + await onScale(current); + setVisibleScrim(false); + setReplicas(null); + }; return (
- +
-