1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 12:25:22 +02:00

fix(app): improve resource quota error handling [EE-5933] (#10951)

This commit is contained in:
Ali 2024-01-15 13:29:35 +13:00 committed by GitHub
parent 488fcc7cc5
commit 6d71a28584
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 50 deletions

View file

@ -97,10 +97,16 @@ export function ApplicationSummaryWidget() {
<>
{failedCreateCondition && (
<div
className="vertical-center alert alert-danger mb-2"
className="flex gap-1 items-start alert alert-danger mb-2"
data-cy="k8sAppDetail-failedCreateMessage"
>
<Icon icon={Info} className="mr-1" mode="danger" />
<div className="mt-0.5">
<Icon
icon={Info}
className="mr-1 shrink-0"
mode="danger"
/>
</div>
<div>
<div className="font-semibold">
Failed to create application

View file

@ -1,21 +1,25 @@
import { FormikErrors } from 'formik';
import { BoxSelector } from '@@/BoxSelector';
import { FormSection } from '@@/form-components/FormSection';
import { TextTip } from '@@/Tip/TextTip';
import { FormError } from '@@/form-components/FormError';
import { DeploymentType } from '../types';
import { getDeploymentOptions } from './deploymentOptions';
import { DeploymentType } from '../../types';
import { getDeploymentOptions } from '../../CreateView/deploymentOptions';
interface Props {
value: DeploymentType;
onChange(value: DeploymentType): void;
values: DeploymentType;
onChange(values: DeploymentType): void;
errors: FormikErrors<DeploymentType>;
supportGlobalDeployment: boolean;
}
export function AppDeploymentTypeFormSection({
supportGlobalDeployment,
value,
values,
onChange,
errors,
supportGlobalDeployment,
}: Props) {
const options = getDeploymentOptions(supportGlobalDeployment);
@ -27,10 +31,11 @@ export function AppDeploymentTypeFormSection({
<BoxSelector
slim
options={options}
value={value}
value={values}
onChange={onChange}
radioName="deploymentType"
/>
{!!errors && <FormError>{errors}</FormError>}
</FormSection>
);
}

View file

@ -0,0 +1,19 @@
import { SchemaOf, mixed } from 'yup';
import { DeploymentType } from '../../types';
type ValidationData = {
isQuotaExceeded: boolean;
};
export function deploymentTypeValidation(
validationData?: ValidationData
): SchemaOf<DeploymentType> {
return mixed()
.oneOf(['Replicated', 'Global'])
.test(
'exhaused',
`This application would exceed available resources. Please review resource reservations or the instance count.`,
() => !validationData?.isQuotaExceeded
);
}

View file

@ -55,14 +55,16 @@ export function ResourceReservationFormSection({
tooltip="An instance of this application will reserve this amount of memory. If the instance memory usage exceeds the reservation, it might be subject to OOM."
>
<div className="col-xs-10">
<SliderWithInput
value={Number(values.memoryLimit) ?? 0}
onChange={(value) => onChange({ ...values, memoryLimit: value })}
max={maxMemoryLimit}
step={128}
dataCy="k8sAppCreate-memoryLimit"
visibleTooltip
/>
{maxMemoryLimit > 0 && (
<SliderWithInput
value={Number(values.memoryLimit) ?? 0}
onChange={(value) => onChange({ ...values, memoryLimit: value })}
max={maxMemoryLimit}
step={128}
dataCy="k8sAppCreate-memoryLimit"
visibleTooltip
/>
)}
{errors?.memoryLimit && (
<FormError className="pt-1">{errors.memoryLimit}</FormError>
)}
@ -74,21 +76,23 @@ export function ResourceReservationFormSection({
tooltip="An instance of this application will reserve this amount of CPU. If the instance CPU usage exceeds the reservation, it might be subject to CPU throttling."
>
<div className="col-xs-10">
<Slider
onChange={(value) =>
onChange(
typeof value === 'number'
? { ...values, cpuLimit: value }
: { ...values, cpuLimit: value[0] ?? 0 }
)
}
value={values.cpuLimit}
min={0}
max={maxCpuLimit}
step={0.01}
dataCy="k8sAppCreate-cpuLimitSlider"
visibleTooltip
/>
{maxCpuLimit > 0 && (
<Slider
onChange={(value) =>
onChange(
typeof value === 'number'
? { ...values, cpuLimit: value }
: { ...values, cpuLimit: value[0] ?? 0 }
)
}
value={values.cpuLimit}
min={0}
max={maxCpuLimit}
step={0.1}
dataCy="k8sAppCreate-cpuLimitSlider"
visibleTooltip
/>
)}
{errors?.cpuLimit && (
<FormError className="pt-1">{errors.cpuLimit}</FormError>
)}

View file

@ -5,6 +5,7 @@ import { ResourceQuotaFormValues } from './types';
type ValidationData = {
maxMemoryLimit: number;
maxCpuLimit: number;
isEnvironmentAdmin: boolean;
};
export function resourceReservationValidation(
@ -13,16 +14,36 @@ export function resourceReservationValidation(
return object().shape({
memoryLimit: number()
.min(0)
.test(
'exhaused',
`The memory capacity for this namespace has been exhausted, so you cannot deploy the application.${
validationData?.isEnvironmentAdmin
? ''
: ' Contact your administrator to expand the memory capacity of the namespace.'
}`,
() => !!validationData && validationData.maxMemoryLimit > 0
)
.max(
validationData?.maxMemoryLimit || 0,
`Value must be between 0 and ${validationData?.maxMemoryLimit}`
({ value }) =>
`Value must be between 0 and ${validationData?.maxMemoryLimit}MB now - the previous value of ${value} exceeds this`
)
.required(),
cpuLimit: number()
.min(0)
.test(
'exhaused',
`The CPU capacity for this namespace has been exhausted, so you cannot deploy the application.${
validationData?.isEnvironmentAdmin
? ''
: ' Contact your administrator to expand the CPU capacity of the namespace.'
}`,
() => !!validationData && validationData.maxCpuLimit > 0
)
.max(
validationData?.maxCpuLimit || 0,
`Value must be between 0 and ${validationData?.maxCpuLimit}`
({ value }) =>
`Value must be between 0 and ${validationData?.maxCpuLimit} now - the previous value of ${value} exceeds this`
)
.required(),
});