1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 07:19:41 +02:00

refactor(app): migrate-autoscaling [EE-6387] (#10709)

* refactor(app): migrate-autoscaling [EE-6387]
This commit is contained in:
Ali 2024-01-03 10:42:39 +13:00 committed by GitHub
parent 6da71661d5
commit 2d77e71085
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 122 deletions

View file

@ -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<AutoScalingFormValues>;
isMetricsEnabled: boolean;
};
export function AutoScalingFormSection({
values,
onChange,
errors,
isMetricsEnabled,
}: Props) {
return (
<>
{!isMetricsEnabled && <NoMetricsServerWarning />}
<SwitchField
disabled={!isMetricsEnabled}
label="Enable auto scaling for this application"
labelClass="col-sm-3 col-lg-2"
checked={values.isUsed}
onChange={(value: boolean) =>
onChange({
...values,
isUsed: value,
})
}
/>
{values.isUsed && (
<div className="grid grid-cols-1 md:grid-cols-3 w-full gap-x-4 gap-y-2 my-3">
<div className="flex flex-col min-w-fit">
<label htmlFor="min-instances" className="font-normal text-xs">
Minimum instances
</label>
<Input
id="min-instances"
type="number"
min="0"
value={values.minReplicas}
max={values.maxReplicas || 1}
onChange={(e) =>
onChange({
...values,
minReplicas: Number(e.target.value) || 0,
})
}
data-cy="k8sAppCreate-autoScaleMin"
/>
{errors?.minReplicas && <FormError>{errors.minReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<label htmlFor="max-instances" className="font-normal text-xs">
Maximum instances
</label>
<Input
id="max-instances"
type="number"
value={values.maxReplicas}
min={values.minReplicas || 1}
onChange={(e) =>
onChange({
...values,
maxReplicas: Number(e.target.value) || 1,
})
}
data-cy="k8sAppCreate-autoScaleMax"
/>
{errors?.maxReplicas && <FormError>{errors.maxReplicas}</FormError>}
</div>
<div className="flex flex-col min-w-fit">
<label
htmlFor="cpu-threshold"
className="font-normal text-xs flex items-center"
>
Target CPU usage (<b>%</b>)
<Tooltip message="The autoscaler will ensure enough instances are running to maintain an average CPU usage across all instances." />
</label>
<Input
id="cpu-threshold"
type="number"
value={values.targetCpuUtilizationPercentage}
min="1"
max="100"
onChange={(e) =>
onChange({
...values,
targetCpuUtilizationPercentage: Number(e.target.value) || 1,
})
}
data-cy="k8sAppCreate-targetCPUInput"
/>
{errors?.targetCpuUtilizationPercentage && (
<FormError>{errors.targetCpuUtilizationPercentage}</FormError>
)}
</div>
</div>
)}
</>
);
}
function NoMetricsServerWarning() {
const { isAdmin } = useCurrentUser();
return (
<TextTip color="orange">
{isAdmin && (
<>
Server metrics features must be enabled in the{' '}
<Link to="kubernetes.cluster.setup">
environment configuration view
</Link>
.
</>
)}
{!isAdmin &&
'This feature is currently disabled and must be enabled by an administrator user.'}
</TextTip>
);
}

View file

@ -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<AutoScalingFormValues> {
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()
),
});
}

View file

@ -0,0 +1,2 @@
export { AutoScalingFormSection } from './AutoScalingFormSection';
export { autoScalingValidation } from './autoScalingValidation';

View file

@ -0,0 +1,6 @@
export type AutoScalingFormValues = {
isUsed: boolean;
minReplicas?: number;
maxReplicas?: number;
targetCpuUtilizationPercentage?: number;
};