diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index 0f44e3c66..e9f986fbc 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -38,6 +38,10 @@ import { ReplicationFormSection, replicationValidation, } from '@/react/kubernetes/applications/components/ReplicationFormSection'; +import { + AutoScalingFormSection, + autoScalingValidation, +} from '@/react/kubernetes/applications/components/AutoScalingFormSection'; import { EnvironmentVariablesFieldset } from '@@/form-components/EnvironmentVariablesFieldset'; @@ -255,3 +259,11 @@ withFormValidation( ], replicationValidation ); + +withFormValidation( + ngModule, + withUIRouter(withCurrentUser(withReactQuery(AutoScalingFormSection))), + 'autoScalingFormSection', + ['isMetricsEnabled'], + autoScalingValidation +); diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index d9320c6f2..a2afda66c 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -466,128 +466,13 @@ - -
-
-

This feature is currently disabled and must be enabled by an administrator user.

-

- Server metrics features must be enabled in the - environment configuration view. -

-
-
- -
-
-
- -
- -
-
- -
-
-
- -
- -
- -
- -

Minimum instances is required.

-

- Minimum instances must be greater than 0. -

-

- Minimum instances must be smaller than maximum instances. -

-
-
-
-
-
- -
- -
- -
- -

Maximum instances is required.

-

- Maximum instances must be greater than minimum instances. -

-
-
-
-
-
- -
- -
- -
- -

Target CPU usage is required.

-

- Target CPU usage must be greater than 0. -

-

- Target CPU usage must be smaller than 100. -

-
-
-
-
-
- -
-
- - This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy. -
-
+
+
diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js index 6a0abfac9..303bc46eb 100644 --- a/app/kubernetes/views/applications/create/createApplicationController.js +++ b/app/kubernetes/views/applications/create/createApplicationController.js @@ -156,6 +156,7 @@ class KubernetesCreateApplicationController { this.onChangePersistedFolder = this.onChangePersistedFolder.bind(this); this.onChangeResourceReservation = this.onChangeResourceReservation.bind(this); this.onChangeReplicaCount = this.onChangeReplicaCount.bind(this); + this.onAutoScaleChange = this.onAutoScaleChange.bind(this); } /* #endregion */ @@ -238,6 +239,26 @@ class KubernetesCreateApplicationController { this.formValues.AutoScaler.IsUsed = false; } } + + onAutoScaleChange(values) { + return this.$async(async () => { + if (!this.formValues.AutoScaler.IsUsed && values.isUsed) { + this.formValues.AutoScaler = { + IsUsed: values.isUsed, + MinReplicas: 1, + MaxReplicas: 3, + TargetCPUUtilization: 50, + }; + return; + } + this.formValues.AutoScaler = { + IsUsed: values.isUsed, + MinReplicas: values.minReplicas, + MaxReplicas: values.maxReplicas, + TargetCPUUtilization: values.targetCpuUtilizationPercentage, + }; + }); + } /* #endregion */ /* #region CONFIGMAP UI MANAGEMENT */ diff --git a/app/react/kubernetes/applications/components/AutoScalingFormSection/AutoScalingFormSection.tsx b/app/react/kubernetes/applications/components/AutoScalingFormSection/AutoScalingFormSection.tsx new file mode 100644 index 000000000..e97a8203c --- /dev/null +++ b/app/react/kubernetes/applications/components/AutoScalingFormSection/AutoScalingFormSection.tsx @@ -0,0 +1,132 @@ +import { FormikErrors } from 'formik'; + +import { useCurrentUser } from '@/react/hooks/useUser'; + +import { SwitchField } from '@@/form-components/SwitchField'; +import { Link } from '@@/Link'; +import { TextTip } from '@@/Tip/TextTip'; +import { Input } from '@@/form-components/Input'; +import { FormError } from '@@/form-components/FormError'; +import { Tooltip } from '@@/Tip/Tooltip'; + +import { AutoScalingFormValues } from './types'; + +type Props = { + values: AutoScalingFormValues; + onChange: (values: AutoScalingFormValues) => void; + errors: FormikErrors; + isMetricsEnabled: boolean; +}; + +export function AutoScalingFormSection({ + values, + onChange, + errors, + isMetricsEnabled, +}: Props) { + return ( + <> + {!isMetricsEnabled && } + + onChange({ + ...values, + isUsed: value, + }) + } + /> + {values.isUsed && ( +
+
+ + + onChange({ + ...values, + minReplicas: Number(e.target.value) || 0, + }) + } + data-cy="k8sAppCreate-autoScaleMin" + /> + {errors?.minReplicas && {errors.minReplicas}} +
+
+ + + onChange({ + ...values, + maxReplicas: Number(e.target.value) || 1, + }) + } + data-cy="k8sAppCreate-autoScaleMax" + /> + {errors?.maxReplicas && {errors.maxReplicas}} +
+
+ + + onChange({ + ...values, + targetCpuUtilizationPercentage: Number(e.target.value) || 1, + }) + } + data-cy="k8sAppCreate-targetCPUInput" + /> + {errors?.targetCpuUtilizationPercentage && ( + {errors.targetCpuUtilizationPercentage} + )} +
+
+ )} + + ); +} + +function NoMetricsServerWarning() { + const { isAdmin } = useCurrentUser(); + return ( + + {isAdmin && ( + <> + Server metrics features must be enabled in the{' '} + + environment configuration view + + . + + )} + {!isAdmin && + 'This feature is currently disabled and must be enabled by an administrator user.'} + + ); +} diff --git a/app/react/kubernetes/applications/components/AutoScalingFormSection/autoScalingValidation.ts b/app/react/kubernetes/applications/components/AutoScalingFormSection/autoScalingValidation.ts new file mode 100644 index 000000000..f92f3f5b5 --- /dev/null +++ b/app/react/kubernetes/applications/components/AutoScalingFormSection/autoScalingValidation.ts @@ -0,0 +1,69 @@ +import { SchemaOf, boolean, number, object } from 'yup'; + +import { AutoScalingFormValues } from './types'; + +type ValidationData = { + autoScalerOverflow: boolean; +}; + +export function autoScalingValidation( + validationData?: ValidationData +): SchemaOf { + const { autoScalerOverflow } = validationData || {}; + return object({ + isUsed: boolean().required(), + minReplicas: number() + .min(0, 'Minimum instances must be greater than 0.') + .when('isUsed', (isUsed: boolean) => + isUsed + ? number() + .required('Minimum instances is required.') + .test( + 'maxReplicas', + 'Minimum instances must be less than maximum instances.', + // eslint-disable-next-line func-names + function (this, value?: number): boolean { + if (!value) { + return false; + } + const { maxReplicas } = this.parent as AutoScalingFormValues; + return !maxReplicas || value < maxReplicas; + } + ) + : number() + ), + maxReplicas: number().when('isUsed', (isUsed: boolean) => + isUsed + ? number() + .required('Maximum instances is required.') + .test( + 'minReplicas', + 'Maximum instances must be greater than minimum instances.', + // eslint-disable-next-line func-names + function (this, value?: number): boolean { + if (!value) { + return false; + } + const { minReplicas } = this.parent as AutoScalingFormValues; + return !minReplicas || value > minReplicas; + } + ) + .test( + 'overflow', + 'This application would exceed available resources. Please reduce the maximum instances or the resource reservations.', + () => !autoScalerOverflow + ) + : number() + ), + targetCpuUtilizationPercentage: number().when( + 'isUsed', + (isUsed: boolean) => + isUsed + ? number() + .min(0, 'Target CPU usage must be greater than 0.') + .max(100, 'Target CPU usage must be smaller than 100.') + .required('Target CPU utilization percentage is required.') + : number() + ), + }); +} diff --git a/app/react/kubernetes/applications/components/AutoScalingFormSection/index.ts b/app/react/kubernetes/applications/components/AutoScalingFormSection/index.ts new file mode 100644 index 000000000..89d587466 --- /dev/null +++ b/app/react/kubernetes/applications/components/AutoScalingFormSection/index.ts @@ -0,0 +1,2 @@ +export { AutoScalingFormSection } from './AutoScalingFormSection'; +export { autoScalingValidation } from './autoScalingValidation'; diff --git a/app/react/kubernetes/applications/components/AutoScalingFormSection/types.ts b/app/react/kubernetes/applications/components/AutoScalingFormSection/types.ts new file mode 100644 index 000000000..e53dc225a --- /dev/null +++ b/app/react/kubernetes/applications/components/AutoScalingFormSection/types.ts @@ -0,0 +1,6 @@ +export type AutoScalingFormValues = { + isUsed: boolean; + minReplicas?: number; + maxReplicas?: number; + targetCpuUtilizationPercentage?: number; +};