mirror of
https://github.com/portainer/portainer.git
synced 2025-07-21 14:29:40 +02:00
refactor(namespace): migrate namespace edit to react [r8s-125] (#38)
This commit is contained in:
parent
40c7742e46
commit
ce7e0d8d60
108 changed files with 3183 additions and 2194 deletions
256
app/react/kubernetes/namespaces/ItemView/UpdateNamespaceForm.tsx
Normal file
256
app/react/kubernetes/namespaces/ItemView/UpdateNamespaceForm.tsx
Normal file
|
@ -0,0 +1,256 @@
|
|||
import { Formik } from 'formik';
|
||||
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
|
||||
import { useEnvironmentRegistries } from '@/react/portainer/environments/queries/useEnvironmentRegistries';
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
import { Registry } from '@/react/portainer/registries/types/registry';
|
||||
|
||||
import { Loading, Widget, WidgetBody } from '@@/Widget';
|
||||
import { Alert } from '@@/Alert';
|
||||
|
||||
import { NamespaceInnerForm } from '../components/NamespaceForm/NamespaceInnerForm';
|
||||
import { useNamespacesQuery } from '../queries/useNamespacesQuery';
|
||||
import { useClusterResourceLimitsQuery } from '../queries/useResourceLimitsQuery';
|
||||
import { NamespaceFormValues, NamespacePayload } from '../types';
|
||||
import { getNamespaceValidationSchema } from '../components/NamespaceForm/NamespaceForm.validation';
|
||||
import { transformFormValuesToNamespacePayload } from '../components/NamespaceForm/utils';
|
||||
import { useNamespaceQuery } from '../queries/useNamespaceQuery';
|
||||
import { useIngressControllerClassMapQuery } from '../../cluster/ingressClass/useIngressControllerClassMap';
|
||||
import { ResourceQuotaFormValues } from '../components/NamespaceForm/ResourceQuotaFormSection/types';
|
||||
import { IngressControllerClassMap } from '../../cluster/ingressClass/types';
|
||||
import { useUpdateNamespaceMutation } from '../queries/useUpdateNamespaceMutation';
|
||||
|
||||
import { useNamespaceFormValues } from './useNamespaceFormValues';
|
||||
import { confirmUpdateNamespace } from './ConfirmUpdateNamespace';
|
||||
import { createUpdateRegistriesPayload } from './createUpdateRegistriesPayload';
|
||||
|
||||
export function UpdateNamespaceForm() {
|
||||
const {
|
||||
params: { id: namespaceName },
|
||||
} = useCurrentStateAndParams();
|
||||
const router = useRouter();
|
||||
|
||||
// for initial values
|
||||
const { user } = useCurrentUser();
|
||||
const environmentId = useEnvironmentId();
|
||||
const environmentQuery = useCurrentEnvironment();
|
||||
const namespacesQuery = useNamespacesQuery(environmentId);
|
||||
const resourceLimitsQuery = useClusterResourceLimitsQuery(environmentId);
|
||||
const namespaceQuery = useNamespaceQuery(environmentId, namespaceName, {
|
||||
params: { withResourceQuota: 'true' },
|
||||
});
|
||||
const registriesQuery = useEnvironmentRegistries(environmentId, {
|
||||
hideDefault: true,
|
||||
});
|
||||
const ingressClassesQuery = useIngressControllerClassMapQuery({
|
||||
environmentId,
|
||||
namespace: namespaceName,
|
||||
allowedOnly: true,
|
||||
});
|
||||
const storageClasses =
|
||||
environmentQuery.data?.Kubernetes.Configuration.StorageClasses;
|
||||
const { data: namespaces } = namespacesQuery;
|
||||
const { data: resourceLimits } = resourceLimitsQuery;
|
||||
const { data: namespace } = namespaceQuery;
|
||||
const { data: registries } = registriesQuery;
|
||||
const { data: ingressClasses } = ingressClassesQuery;
|
||||
|
||||
const updateNamespaceMutation = useUpdateNamespaceMutation(environmentId);
|
||||
|
||||
const namespaceNames = Object.keys(namespaces || {});
|
||||
const memoryLimit = resourceLimits?.Memory ?? 0;
|
||||
const cpuLimit = resourceLimits?.CPU ?? 0;
|
||||
const initialValues = useNamespaceFormValues({
|
||||
namespaceName,
|
||||
environmentId,
|
||||
storageClasses,
|
||||
namespace,
|
||||
registries,
|
||||
ingressClasses,
|
||||
});
|
||||
const isQueryLoading =
|
||||
environmentQuery.isLoading ||
|
||||
resourceLimitsQuery.isLoading ||
|
||||
namespacesQuery.isLoading ||
|
||||
namespaceQuery.isLoading ||
|
||||
registriesQuery.isLoading ||
|
||||
ingressClassesQuery.isLoading;
|
||||
|
||||
const isQueryError =
|
||||
environmentQuery.isError ||
|
||||
resourceLimitsQuery.isError ||
|
||||
namespacesQuery.isError ||
|
||||
namespaceQuery.isError ||
|
||||
registriesQuery.isError ||
|
||||
ingressClassesQuery.isError;
|
||||
|
||||
if (isQueryLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (isQueryError) {
|
||||
return (
|
||||
<Alert color="error" title="Error">
|
||||
Error loading namespace
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (!initialValues) {
|
||||
return (
|
||||
<Alert color="warn" title="Warning">
|
||||
No data found for namespace
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<Widget>
|
||||
<WidgetBody>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values, user.Username)}
|
||||
validateOnMount
|
||||
validationSchema={getNamespaceValidationSchema(
|
||||
memoryLimit,
|
||||
cpuLimit,
|
||||
namespaceNames
|
||||
)}
|
||||
>
|
||||
{(formikProps) => (
|
||||
<NamespaceInnerForm
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...formikProps}
|
||||
isEdit
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
async function handleSubmit(values: NamespaceFormValues, userName: string) {
|
||||
const createNamespacePayload: NamespacePayload =
|
||||
transformFormValuesToNamespacePayload(values, userName);
|
||||
const updateRegistriesPayload = createUpdateRegistriesPayload({
|
||||
registries,
|
||||
namespaceName,
|
||||
newRegistriesValues: values.registries,
|
||||
initialRegistriesValues: initialValues?.registries || [],
|
||||
environmentId,
|
||||
});
|
||||
|
||||
// give update warnings if needed
|
||||
const isNamespaceAccessRemoved = hasNamespaceAccessBeenRemoved(
|
||||
values.registries,
|
||||
initialValues?.registries || [],
|
||||
environmentId,
|
||||
values.name
|
||||
);
|
||||
const isIngressClassesRemoved = hasIngressClassesBeenRemoved(
|
||||
values.ingressClasses,
|
||||
initialValues?.ingressClasses || []
|
||||
);
|
||||
const warnings = {
|
||||
quota: hasResourceQuotaBeenReduced(
|
||||
values.resourceQuota,
|
||||
initialValues?.resourceQuota
|
||||
),
|
||||
ingress: isIngressClassesRemoved,
|
||||
registries: isNamespaceAccessRemoved,
|
||||
};
|
||||
if (Object.values(warnings).some(Boolean)) {
|
||||
const confirmed = await confirmUpdateNamespace(warnings);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// update the namespace
|
||||
updateNamespaceMutation.mutate(
|
||||
{
|
||||
createNamespacePayload,
|
||||
updateRegistriesPayload,
|
||||
namespaceIngressControllerPayload: values.ingressClasses,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifySuccess(
|
||||
'Success',
|
||||
`Namespace '${values.name}' updated successfully`
|
||||
);
|
||||
router.stateService.reload();
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function hasResourceQuotaBeenReduced(
|
||||
newResourceQuota: ResourceQuotaFormValues,
|
||||
initialResourceQuota?: ResourceQuotaFormValues
|
||||
) {
|
||||
if (!initialResourceQuota) {
|
||||
return false;
|
||||
}
|
||||
// if the new value is an empty string or '0', it's counted as 'unlimited'
|
||||
const unlimitedValue = String(Number.MAX_SAFE_INTEGER);
|
||||
return (
|
||||
(Number(initialResourceQuota.cpu) || unlimitedValue) >
|
||||
(Number(newResourceQuota.cpu) || unlimitedValue) ||
|
||||
(Number(initialResourceQuota.memory) || unlimitedValue) >
|
||||
(Number(newResourceQuota.memory) || unlimitedValue)
|
||||
);
|
||||
}
|
||||
|
||||
function hasNamespaceAccessBeenRemoved(
|
||||
newRegistries: Registry[],
|
||||
initialRegistries: Registry[],
|
||||
environmentId: number,
|
||||
namespaceName: string
|
||||
) {
|
||||
return initialRegistries.some((oldRegistry) => {
|
||||
// Check if the namespace was in the old registry's accesses
|
||||
const isNamespaceInOldAccesses =
|
||||
oldRegistry.RegistryAccesses?.[`${environmentId}`]?.Namespaces.includes(
|
||||
namespaceName
|
||||
);
|
||||
|
||||
if (!isNamespaceInOldAccesses) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the corresponding new registry
|
||||
const newRegistry = newRegistries.find((r) => r.Id === oldRegistry.Id);
|
||||
if (!newRegistry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the registry no longer exists or the namespace is not in its accesses, access has been removed
|
||||
const isNamespaceInNewAccesses =
|
||||
newRegistry.RegistryAccesses?.[`${environmentId}`]?.Namespaces.includes(
|
||||
namespaceName
|
||||
);
|
||||
|
||||
return !isNamespaceInNewAccesses;
|
||||
});
|
||||
}
|
||||
|
||||
function hasIngressClassesBeenRemoved(
|
||||
newIngressClasses: IngressControllerClassMap[],
|
||||
initialIngressClasses: IngressControllerClassMap[]
|
||||
) {
|
||||
// go through all old classes and check if their availability has changed
|
||||
return initialIngressClasses.some((oldClass) => {
|
||||
const newClass = newIngressClasses.find((c) => c.Name === oldClass.Name);
|
||||
return newClass?.Availability !== oldClass.Availability;
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue