mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +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
|
@ -2,14 +2,16 @@ import { ModalType } from '@@/modals';
|
|||
import { confirm } from '@@/modals/confirm';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
|
||||
export function confirmUpdateNamespace(
|
||||
quotaWarning: boolean,
|
||||
ingressWarning: boolean,
|
||||
registriesWarning: boolean
|
||||
) {
|
||||
type Warnings = {
|
||||
quota: boolean;
|
||||
ingress: boolean;
|
||||
registries: boolean;
|
||||
};
|
||||
|
||||
export function confirmUpdateNamespace(warnings: Warnings) {
|
||||
const message = (
|
||||
<>
|
||||
{quotaWarning && (
|
||||
{warnings.quota && (
|
||||
<p>
|
||||
Reducing the quota assigned to an "in-use" namespace may
|
||||
have unintended consequences, including preventing running
|
||||
|
@ -17,13 +19,13 @@ export function confirmUpdateNamespace(
|
|||
them from running at all.
|
||||
</p>
|
||||
)}
|
||||
{ingressWarning && (
|
||||
{warnings.ingress && (
|
||||
<p>
|
||||
Deactivating ingresses may cause applications to be unaccessible. All
|
||||
ingress configurations from affected applications will be removed.
|
||||
</p>
|
||||
)}
|
||||
{registriesWarning && (
|
||||
{warnings.registries && (
|
||||
<p>
|
||||
Some registries you removed might be used by one or more applications
|
||||
inside this environment. Removing the registries access could lead to
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Code } from 'lucide-react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||
import { useTableStateWithStorage } from '@@/datatables/useTableState';
|
||||
import {
|
||||
|
@ -10,20 +11,14 @@ import {
|
|||
RefreshableTableSettings,
|
||||
} from '@@/datatables/types';
|
||||
|
||||
import { NamespaceApp } from './types';
|
||||
import { useApplications } from '../../applications/queries/useApplications';
|
||||
|
||||
import { useColumns } from './columns';
|
||||
|
||||
interface TableSettings extends BasicTableSettings, RefreshableTableSettings {}
|
||||
|
||||
export function NamespaceAppsDatatable({
|
||||
dataset,
|
||||
onRefresh,
|
||||
isLoading,
|
||||
}: {
|
||||
dataset: Array<NamespaceApp>;
|
||||
onRefresh: () => void;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
export function NamespaceAppsDatatable({ namespace }: { namespace: string }) {
|
||||
const environmentId = useEnvironmentId();
|
||||
const tableState = useTableStateWithStorage<TableSettings>(
|
||||
'kube-namespace-apps',
|
||||
'Name',
|
||||
|
@ -31,18 +26,25 @@ export function NamespaceAppsDatatable({
|
|||
...refreshableSettings(set),
|
||||
})
|
||||
);
|
||||
useRepeater(tableState.autoRefreshRate, onRefresh);
|
||||
|
||||
const applicationsQuery = useApplications(environmentId, {
|
||||
refetchInterval: tableState.autoRefreshRate * 1000,
|
||||
namespace,
|
||||
withDependencies: true,
|
||||
});
|
||||
const applications = applicationsQuery.data ?? [];
|
||||
|
||||
const columns = useColumns();
|
||||
|
||||
return (
|
||||
<Datatable
|
||||
dataset={dataset}
|
||||
dataset={applications}
|
||||
settingsManager={tableState}
|
||||
columns={columns}
|
||||
disableSelect
|
||||
title="Applications running in this namespace"
|
||||
titleIcon={Code}
|
||||
isLoading={isLoading}
|
||||
isLoading={applicationsQuery.isLoading}
|
||||
renderTableSettings={() => (
|
||||
<TableSettingsMenu>
|
||||
<TableSettingsMenuAutoRefresh
|
||||
|
|
82
app/react/kubernetes/namespaces/ItemView/NamespaceView.tsx
Normal file
82
app/react/kubernetes/namespaces/ItemView/NamespaceView.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
import { AlertTriangle, Code, Layers, History } from 'lucide-react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { findSelectedTabIndex, Tab, WidgetTabs } from '@@/Widget/WidgetTabs';
|
||||
import { Badge } from '@@/Badge';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { useEventWarningsCount } from '../../queries/useEvents';
|
||||
import { NamespaceYAMLEditor } from '../components/NamespaceYamlEditor';
|
||||
import { ResourceEventsDatatable } from '../../components/EventsDatatable/ResourceEventsDatatable';
|
||||
|
||||
import { UpdateNamespaceForm } from './UpdateNamespaceForm';
|
||||
import { NamespaceAppsDatatable } from './NamespaceAppsDatatable';
|
||||
|
||||
export function NamespaceView() {
|
||||
const stateAndParams = useCurrentStateAndParams();
|
||||
const {
|
||||
params: { id: namespace },
|
||||
} = stateAndParams;
|
||||
|
||||
const environmentId = useEnvironmentId();
|
||||
const eventWarningCount = useEventWarningsCount(environmentId, namespace);
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
name: 'Namespace',
|
||||
icon: Layers,
|
||||
widget: <UpdateNamespaceForm />,
|
||||
selectedTabParam: 'namespace',
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex items-center gap-x-2">
|
||||
Events
|
||||
{eventWarningCount >= 1 && (
|
||||
<Badge type="warnSecondary">
|
||||
<Icon icon={AlertTriangle} className="!mr-1" />
|
||||
{eventWarningCount}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
icon: History,
|
||||
widget: (
|
||||
<ResourceEventsDatatable
|
||||
namespace={namespace}
|
||||
storageKey="kubernetes.namespace.events"
|
||||
noWidget={false}
|
||||
/>
|
||||
),
|
||||
selectedTabParam: 'events',
|
||||
},
|
||||
{
|
||||
name: 'YAML',
|
||||
icon: Code,
|
||||
widget: <NamespaceYAMLEditor />,
|
||||
selectedTabParam: 'YAML',
|
||||
},
|
||||
];
|
||||
const currentTabIndex = findSelectedTabIndex(stateAndParams, tabs);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Namespace details"
|
||||
breadcrumbs={[
|
||||
{ label: 'Namespaces', link: 'kubernetes.resourcePools' },
|
||||
namespace,
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
<>
|
||||
<WidgetTabs tabs={tabs} currentTabIndex={currentTabIndex} />
|
||||
{tabs[currentTabIndex].widget}
|
||||
<NamespaceAppsDatatable namespace={namespace} />
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
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;
|
||||
});
|
||||
}
|
|
@ -2,18 +2,17 @@ import { createColumnHelper } from '@tanstack/react-table';
|
|||
import _ from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { humanize, truncate } from '@/portainer/filters/filters';
|
||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||
import { humanize } from '@/portainer/filters/filters';
|
||||
|
||||
import { Link } from '@@/Link';
|
||||
import { ExternalBadge } from '@@/Badge/ExternalBadge';
|
||||
|
||||
import { isExternalApplication } from '../../applications/utils';
|
||||
import { cpuHumanValue } from '../../applications/utils/cpuHumanValue';
|
||||
import { Application } from '../../applications/ListView/ApplicationsDatatable/types';
|
||||
|
||||
import { NamespaceApp } from './types';
|
||||
|
||||
const columnHelper = createColumnHelper<NamespaceApp>();
|
||||
const columnHelper = createColumnHelper<Application>();
|
||||
|
||||
export function useColumns() {
|
||||
const hideStacksQuery = usePublicSettings<boolean>({
|
||||
|
@ -27,7 +26,7 @@ export function useColumns() {
|
|||
columnHelper.accessor('Name', {
|
||||
header: 'Name',
|
||||
cell: ({ row: { original: item } }) => (
|
||||
<>
|
||||
<div className="flex flex-0">
|
||||
<Link
|
||||
to="kubernetes.applications.application"
|
||||
params={{ name: item.Name, namespace: item.ResourcePool }}
|
||||
|
@ -40,7 +39,7 @@ export function useColumns() {
|
|||
<ExternalBadge />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
!hideStacksQuery.data &&
|
||||
|
@ -50,23 +49,34 @@ export function useColumns() {
|
|||
}),
|
||||
columnHelper.accessor('Image', {
|
||||
header: 'Image',
|
||||
cell: ({ row: { original: item } }) => (
|
||||
<>
|
||||
{truncate(item.Image, 64)}
|
||||
{item.Containers?.length > 1 && (
|
||||
<>+ {item.Containers.length - 1}</>
|
||||
)}
|
||||
</>
|
||||
cell: ({ getValue }) => (
|
||||
<div className="max-w-md truncate">{getValue()}</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('CPU', {
|
||||
header: 'CPU',
|
||||
cell: ({ getValue }) => cpuHumanValue(getValue()),
|
||||
}),
|
||||
columnHelper.accessor('Memory', {
|
||||
header: 'Memory',
|
||||
cell: ({ getValue }) => humanize(getValue()),
|
||||
}),
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row.Resource?.CpuRequest
|
||||
? cpuHumanValue(row.Resource?.CpuRequest)
|
||||
: '-',
|
||||
{
|
||||
header: 'CPU',
|
||||
cell: ({ getValue }) => getValue(),
|
||||
}
|
||||
),
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row.Resource?.MemoryRequest ? row.Resource?.MemoryRequest : '-',
|
||||
{
|
||||
header: 'Memory',
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
if (value === '-') {
|
||||
return value;
|
||||
}
|
||||
return humanize(value);
|
||||
},
|
||||
}
|
||||
),
|
||||
]),
|
||||
[hideStacksQuery.data]
|
||||
);
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
import { createUpdateRegistriesPayload } from './createUpdateRegistriesPayload';
|
||||
|
||||
const tests: {
|
||||
testName: string;
|
||||
params: Parameters<typeof createUpdateRegistriesPayload>[0];
|
||||
expected: ReturnType<typeof createUpdateRegistriesPayload>;
|
||||
}[] = [
|
||||
{
|
||||
testName: 'Add new registry',
|
||||
params: {
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: [],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
namespaceName: 'newns',
|
||||
newRegistriesValues: [
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: [],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
initialRegistriesValues: [
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
environmentId: 7,
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
Id: 2,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'Remove a registry',
|
||||
params: {
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
namespaceName: 'newns',
|
||||
newRegistriesValues: [
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
initialRegistriesValues: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
environmentId: 7,
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
Id: 1,
|
||||
Namespaces: [],
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
testName: 'Remove all registries',
|
||||
params: {
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
namespaceName: 'newns',
|
||||
newRegistriesValues: [],
|
||||
initialRegistriesValues: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'portainer',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: 3,
|
||||
Name: 'portainertest',
|
||||
URL: 'test123.com',
|
||||
BaseURL: '',
|
||||
Authentication: false,
|
||||
Username: '',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'7': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
environmentId: 7,
|
||||
},
|
||||
expected: [
|
||||
{
|
||||
Id: 1,
|
||||
Namespaces: [],
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Namespaces: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
describe('createUpdateRegistriesPayload', () => {
|
||||
tests.forEach(({ testName, params, expected }) => {
|
||||
it(`Should return the correct payload: ${testName}`, () => {
|
||||
expect(createUpdateRegistriesPayload(params)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
import { uniqBy } from 'lodash';
|
||||
|
||||
import { Registry } from '@/react/portainer/registries/types/registry';
|
||||
|
||||
import { UpdateRegistryPayload } from '../types';
|
||||
|
||||
export function createUpdateRegistriesPayload({
|
||||
registries,
|
||||
namespaceName,
|
||||
newRegistriesValues,
|
||||
initialRegistriesValues,
|
||||
environmentId,
|
||||
}: {
|
||||
registries: Registry[] | undefined;
|
||||
namespaceName: string;
|
||||
newRegistriesValues: Registry[];
|
||||
initialRegistriesValues: Registry[];
|
||||
environmentId: number;
|
||||
}): UpdateRegistryPayload[] {
|
||||
if (!registries) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all unique registries from both initial and new values
|
||||
const uniqueRegistries = uniqBy(
|
||||
[...initialRegistriesValues, ...newRegistriesValues],
|
||||
'Id'
|
||||
);
|
||||
|
||||
const payload = uniqueRegistries.map((registry) => {
|
||||
const currentNamespaces =
|
||||
registry.RegistryAccesses?.[`${environmentId}`]?.Namespaces || [];
|
||||
|
||||
const existsInNewValues = newRegistriesValues.some(
|
||||
(r) => r.Id === registry.Id
|
||||
);
|
||||
|
||||
// If registry is in new values, add namespace; if not, remove it
|
||||
const updatedNamespaces = existsInNewValues
|
||||
? [...new Set([...currentNamespaces, namespaceName])]
|
||||
: currentNamespaces.filter((ns) => ns !== namespaceName);
|
||||
|
||||
return {
|
||||
Id: registry.Id,
|
||||
Namespaces: updatedNamespaces,
|
||||
};
|
||||
});
|
||||
|
||||
return payload;
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
import { computeInitialValues } from './useNamespaceFormValues';
|
||||
|
||||
type NamespaceTestData = {
|
||||
testName: string;
|
||||
namespaceData: Parameters<typeof computeInitialValues>[0];
|
||||
expectedFormValues: ReturnType<typeof computeInitialValues>;
|
||||
};
|
||||
|
||||
// various namespace data from simple to complex
|
||||
const tests: NamespaceTestData[] = [
|
||||
{
|
||||
testName:
|
||||
'No resource quotas, registries, storage requests or ingress controllers',
|
||||
namespaceData: {
|
||||
namespaceName: 'test',
|
||||
environmentId: 4,
|
||||
storageClasses: [
|
||||
{
|
||||
Name: 'local-path',
|
||||
AccessModes: ['RWO'],
|
||||
Provisioner: 'rancher.io/local-path',
|
||||
AllowVolumeExpansion: false,
|
||||
},
|
||||
],
|
||||
namespace: {
|
||||
Id: '6110390e-f7cb-4f23-b219-197e4a1d0291',
|
||||
Name: 'test',
|
||||
Status: {
|
||||
phase: 'Active',
|
||||
},
|
||||
Annotations: null,
|
||||
CreationDate: '2024-10-17T17:50:08+13:00',
|
||||
NamespaceOwner: 'admin',
|
||||
IsSystem: false,
|
||||
IsDefault: false,
|
||||
},
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'aliharriss',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'4': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
ingressClasses: [
|
||||
{
|
||||
Name: 'none',
|
||||
ClassName: 'none',
|
||||
Type: 'custom',
|
||||
Availability: true,
|
||||
New: false,
|
||||
Used: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
expectedFormValues: {
|
||||
name: 'test',
|
||||
ingressClasses: [
|
||||
{
|
||||
Name: 'none',
|
||||
ClassName: 'none',
|
||||
Type: 'custom',
|
||||
Availability: true,
|
||||
New: false,
|
||||
Used: false,
|
||||
},
|
||||
],
|
||||
resourceQuota: {
|
||||
enabled: false,
|
||||
memory: '0',
|
||||
cpu: '0',
|
||||
},
|
||||
registries: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
testName:
|
||||
'With annotations, registry, storage request, resource quota and disabled ingress controller',
|
||||
namespaceData: {
|
||||
namespaceName: 'newns',
|
||||
environmentId: 4,
|
||||
storageClasses: [
|
||||
{
|
||||
Name: 'local-path',
|
||||
AccessModes: ['RWO'],
|
||||
Provisioner: 'rancher.io/local-path',
|
||||
AllowVolumeExpansion: false,
|
||||
},
|
||||
],
|
||||
namespace: {
|
||||
Id: 'd5c3cb69-bf9b-4625-b754-d7ba6ce2c688',
|
||||
Name: 'newns',
|
||||
Status: {
|
||||
phase: 'Active',
|
||||
},
|
||||
Annotations: {
|
||||
asdf: 'asdf',
|
||||
},
|
||||
CreationDate: '2024-10-01T10:20:46+13:00',
|
||||
NamespaceOwner: 'admin',
|
||||
IsSystem: false,
|
||||
IsDefault: false,
|
||||
ResourceQuota: {
|
||||
metadata: {},
|
||||
spec: {
|
||||
hard: {
|
||||
'limits.cpu': '800m',
|
||||
'limits.memory': '768M',
|
||||
'local-path.storageclass.storage.k8s.io/requests.storage': '1G',
|
||||
'requests.cpu': '800m',
|
||||
'requests.memory': '768M',
|
||||
'services.loadbalancers': '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'aliharriss',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'4': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
ingressClasses: [
|
||||
{
|
||||
Name: 'none',
|
||||
ClassName: 'none',
|
||||
Type: 'custom',
|
||||
Availability: true,
|
||||
New: false,
|
||||
Used: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
expectedFormValues: {
|
||||
name: 'newns',
|
||||
ingressClasses: [
|
||||
{
|
||||
Name: 'none',
|
||||
ClassName: 'none',
|
||||
Type: 'custom',
|
||||
Availability: true,
|
||||
New: false,
|
||||
Used: false,
|
||||
},
|
||||
],
|
||||
resourceQuota: {
|
||||
enabled: true,
|
||||
memory: '768',
|
||||
cpu: '0.8',
|
||||
},
|
||||
registries: [
|
||||
{
|
||||
Id: 1,
|
||||
Type: 6,
|
||||
Name: 'dockerhub',
|
||||
URL: 'docker.io',
|
||||
BaseURL: '',
|
||||
Authentication: true,
|
||||
Username: 'aliharriss',
|
||||
Gitlab: {
|
||||
ProjectId: 0,
|
||||
InstanceURL: '',
|
||||
ProjectPath: '',
|
||||
},
|
||||
Quay: {
|
||||
OrganisationName: '',
|
||||
},
|
||||
Ecr: {
|
||||
Region: '',
|
||||
},
|
||||
RegistryAccesses: {
|
||||
'4': {
|
||||
UserAccessPolicies: null,
|
||||
TeamAccessPolicies: null,
|
||||
Namespaces: ['newns'],
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
UseOrganisation: false,
|
||||
OrganisationName: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('useNamespaceFormValues', () => {
|
||||
tests.forEach((test) => {
|
||||
it(`should return the correct form values: ${test.testName}`, () => {
|
||||
const formValues = computeInitialValues(test.namespaceData);
|
||||
expect(formValues).toEqual(test.expectedFormValues);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { StorageClass } from '@/react/portainer/environments/types';
|
||||
import { Registry } from '@/react/portainer/registries/types/registry';
|
||||
|
||||
import { NamespaceFormValues, PortainerNamespace } from '../types';
|
||||
import { megaBytesValue, parseCPU } from '../resourceQuotaUtils';
|
||||
import { IngressControllerClassMap } from '../../cluster/ingressClass/types';
|
||||
|
||||
interface ComputeInitialValuesParams {
|
||||
namespaceName: string;
|
||||
environmentId: number;
|
||||
storageClasses?: StorageClass[];
|
||||
namespace?: PortainerNamespace;
|
||||
registries?: Registry[];
|
||||
ingressClasses?: IngressControllerClassMap[];
|
||||
}
|
||||
|
||||
export function computeInitialValues({
|
||||
namespaceName,
|
||||
environmentId,
|
||||
namespace,
|
||||
registries,
|
||||
ingressClasses,
|
||||
}: ComputeInitialValuesParams): NamespaceFormValues | null {
|
||||
if (!namespace) {
|
||||
return null;
|
||||
}
|
||||
const memory = namespace.ResourceQuota?.spec?.hard?.['requests.memory'] ?? '';
|
||||
const cpu = namespace.ResourceQuota?.spec?.hard?.['requests.cpu'] ?? '';
|
||||
|
||||
const registriesUsed = registries?.filter(
|
||||
(registry) =>
|
||||
registry.RegistryAccesses?.[`${environmentId}`]?.Namespaces.includes(
|
||||
namespaceName
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
name: namespaceName,
|
||||
ingressClasses: ingressClasses ?? [],
|
||||
resourceQuota: {
|
||||
enabled: !!memory || !!cpu,
|
||||
memory: `${megaBytesValue(memory)}`,
|
||||
cpu: `${parseCPU(cpu)}`,
|
||||
},
|
||||
registries: registriesUsed ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
export function useNamespaceFormValues({
|
||||
namespaceName,
|
||||
environmentId,
|
||||
storageClasses,
|
||||
namespace,
|
||||
registries,
|
||||
ingressClasses,
|
||||
}: ComputeInitialValuesParams): NamespaceFormValues | null {
|
||||
return useMemo(
|
||||
() =>
|
||||
computeInitialValues({
|
||||
namespaceName,
|
||||
environmentId,
|
||||
storageClasses,
|
||||
namespace,
|
||||
registries,
|
||||
ingressClasses,
|
||||
}),
|
||||
[
|
||||
storageClasses,
|
||||
namespace,
|
||||
registries,
|
||||
namespaceName,
|
||||
ingressClasses,
|
||||
environmentId,
|
||||
]
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue