From e07ee05ee7a1fe94a62c6a80378e10adfbc82e48 Mon Sep 17 00:00:00 2001 From: Ali <83188384+testA113@users.noreply.github.com> Date: Wed, 3 Jan 2024 09:46:26 +1300 Subject: [PATCH] refactor(app): persisted folders form section [EE-6235] (#10693) * refactor(app): persisted folder section [EE-6235] --- .../volumes-datatable/volumesDatatable.html | 8 +- app/kubernetes/converters/application.js | 2 +- .../converters/persistentVolumeClaim.js | 30 ++- app/kubernetes/converters/storageClass.js | 2 +- app/kubernetes/helpers/application/index.js | 16 +- app/kubernetes/helpers/resourceQuotaHelper.js | 8 +- .../models/application/formValues.js | 18 +- .../models/application/models/index.js | 2 +- app/kubernetes/models/volume/models.js | 2 +- app/kubernetes/react/components/index.ts | 22 +- .../create/createApplication.html | 249 +----------------- .../create/createApplicationController.js | 56 ++-- .../ingresses-datatable/template.html | 2 +- .../volumes-storages-datatable/template.html | 8 +- app/kubernetes/views/volumes/edit/volume.html | 8 +- .../views/volumes/edit/volumeController.js | 14 +- .../views/volumes/volumesController.js | 6 +- app/react-tools/withFormValidation.ts | 9 +- .../ButtonSelector/ButtonSelector.tsx | 3 +- .../FormSection/FormSection.tsx | 3 + .../FormSectionTitle/FormSectionTitle.tsx | 8 +- .../InputGroup/InputGroupAddon.tsx | 4 +- ...or.tsx => DataAccessPolicyFormSection.tsx} | 2 +- .../applications/application.queries.ts | 2 +- .../PersistedFolderItem.tsx | 240 +++++++++++++++++ .../PersistedFoldersFormSection.tsx | 127 +++++++++ .../PersistedFoldersFormSection/index.ts | 1 + .../persistedFoldersValidation.ts | 113 ++++++++ .../PersistedFoldersFormSection/types.ts | 28 ++ app/react/kubernetes/applications/types.ts | 5 + .../ConfigureForm/ConfigureForm.tsx | 2 +- .../ConfigureForm/StorageClassDatatable.tsx | 2 +- ...ssesFormValues.ts => useStorageClasses.ts} | 28 +- .../ConfigMapsDatatable.tsx | 8 +- .../SecretsDatatable/SecretsDatatable.tsx | 8 +- .../kubernetes/dashboard/DashboardView.tsx | 12 +- app/react/kubernetes/volumes/.keep | 0 app/react/kubernetes/volumes/queries.ts | 21 -- .../volumes/{service.ts => usePVCsQuery.ts} | 27 +- 39 files changed, 732 insertions(+), 374 deletions(-) rename app/react/kubernetes/applications/CreateView/{KubeApplicationAccessPolicySelector.tsx => DataAccessPolicyFormSection.tsx} (97%) create mode 100644 app/react/kubernetes/applications/components/PersistedFoldersFormSection/PersistedFolderItem.tsx create mode 100644 app/react/kubernetes/applications/components/PersistedFoldersFormSection/PersistedFoldersFormSection.tsx create mode 100644 app/react/kubernetes/applications/components/PersistedFoldersFormSection/index.ts create mode 100644 app/react/kubernetes/applications/components/PersistedFoldersFormSection/persistedFoldersValidation.ts create mode 100644 app/react/kubernetes/applications/components/PersistedFoldersFormSection/types.ts rename app/react/kubernetes/cluster/ConfigureView/ConfigureForm/{useStorageClassesFormValues.ts => useStorageClasses.ts} (80%) delete mode 100644 app/react/kubernetes/volumes/.keep delete mode 100644 app/react/kubernetes/volumes/queries.ts rename app/react/kubernetes/volumes/{service.ts => usePVCsQuery.ts} (53%) diff --git a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html b/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html index 7d2691155..1563567a2 100644 --- a/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html +++ b/app/kubernetes/components/datatables/volumes-datatable/volumesDatatable.html @@ -137,9 +137,9 @@ @@ -188,7 +188,7 @@ - - {{ item.PersistentVolumeClaim.StorageClass.Name }} + {{ item.PersistentVolumeClaim.storageClass.Name }} {{ item.PersistentVolumeClaim.Storage }} diff --git a/app/kubernetes/converters/application.js b/app/kubernetes/converters/application.js index 01b224293..7555c66b3 100644 --- a/app/kubernetes/converters/application.js +++ b/app/kubernetes/converters/application.js @@ -180,7 +180,7 @@ class KubernetesApplicationConverter { persistedFolder.MountPath = matchingVolumeMount.mountPath; if (volume.persistentVolumeClaim) { - persistedFolder.PersistentVolumeClaimName = volume.persistentVolumeClaim.claimName; + persistedFolder.persistentVolumeClaimName = volume.persistentVolumeClaim.claimName; } else { persistedFolder.HostPath = volume.hostPath.path; } diff --git a/app/kubernetes/converters/persistentVolumeClaim.js b/app/kubernetes/converters/persistentVolumeClaim.js index 637df6374..363c7365e 100644 --- a/app/kubernetes/converters/persistentVolumeClaim.js +++ b/app/kubernetes/converters/persistentVolumeClaim.js @@ -19,7 +19,7 @@ class KubernetesPersistentVolumeClaimConverter { res.CreationDate = data.metadata.creationTimestamp; res.Storage = `${data.spec.resources.requests.storage}B`; res.AccessModes = data.spec.accessModes || []; - res.StorageClass = _.find(storageClasses, { Name: data.spec.storageClassName }); + res.storageClass = _.find(storageClasses, { Name: data.spec.storageClassName }); res.Yaml = yaml ? yaml.data : ''; res.ApplicationOwner = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationOwnerLabel] : ''; res.ApplicationName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationNameLabel] : ''; @@ -31,30 +31,32 @@ class KubernetesPersistentVolumeClaimConverter { * @param {KubernetesApplicationFormValues} formValues */ static applicationFormValuesToVolumeClaims(formValues) { - _.remove(formValues.PersistedFolders, (item) => item.NeedsDeletion); + _.remove(formValues.PersistedFolders, (item) => item.needsDeletion); const res = _.map(formValues.PersistedFolders, (item) => { const pvc = new KubernetesPersistentVolumeClaim(); - if (!_.isEmpty(item.ExistingVolume)) { - const existantPVC = item.ExistingVolume.PersistentVolumeClaim; + if (!_.isEmpty(item.existingVolume)) { + const existantPVC = item.existingVolume.PersistentVolumeClaim; pvc.Name = existantPVC.Name; - if (item.PersistentVolumeClaimName) { - pvc.PreviousName = item.PersistentVolumeClaimName; + if (item.persistentVolumeClaimName) { + pvc.PreviousName = item.persistentVolumeClaimName; } - pvc.StorageClass = existantPVC.StorageClass; + pvc.storageClass = existantPVC.storageClass; pvc.Storage = existantPVC.Storage.charAt(0); pvc.CreationDate = existantPVC.CreationDate; pvc.Id = existantPVC.Id; } else { - if (item.PersistentVolumeClaimName) { - pvc.Name = item.PersistentVolumeClaimName; - pvc.PreviousName = item.PersistentVolumeClaimName; + if (item.persistentVolumeClaimName) { + pvc.Name = item.persistentVolumeClaimName; + if (!item.useNewVolume) { + pvc.PreviousName = item.persistentVolumeClaimName; + } } else { pvc.Name = formValues.Name + '-' + pvc.Name; } - pvc.Storage = '' + item.Size + item.SizeUnit.charAt(0); - pvc.StorageClass = item.StorageClass; + pvc.Storage = '' + item.size + item.sizeUnit.charAt(0); + pvc.storageClass = item.storageClass; } - pvc.MountPath = item.ContainerPath; + pvc.MountPath = item.containerPath; pvc.Namespace = formValues.ResourcePool.Namespace.Name; pvc.ApplicationOwner = formValues.ApplicationOwner; pvc.ApplicationName = formValues.Name; @@ -68,7 +70,7 @@ class KubernetesPersistentVolumeClaimConverter { res.metadata.name = pvc.Name; res.metadata.namespace = pvc.Namespace; res.spec.resources.requests.storage = pvc.Storage; - res.spec.storageClassName = pvc.StorageClass ? pvc.StorageClass.Name : ''; + res.spec.storageClassName = pvc.storageClass ? pvc.storageClass.Name : ''; const accessModes = pvc.StorageClass && pvc.StorageClass.AccessModes ? pvc.StorageClass.AccessModes.map((accessMode) => storageClassToPVCAccessModes[accessMode]) : []; res.spec.accessModes = accessModes; res.metadata.labels.app = pvc.ApplicationName; diff --git a/app/kubernetes/converters/storageClass.js b/app/kubernetes/converters/storageClass.js index 7a2afca0b..7a4108d31 100644 --- a/app/kubernetes/converters/storageClass.js +++ b/app/kubernetes/converters/storageClass.js @@ -5,7 +5,7 @@ import { KubernetesStorageClassCreatePayload } from 'Kubernetes/models/storage-c class KubernetesStorageClassConverter { /** - * API StorageClass to front StorageClass + * API storageClass to front storageClass */ static apiToStorageClass(data) { const res = new KubernetesStorageClass(); diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js index 9440b910a..46d780776 100644 --- a/app/kubernetes/helpers/application/index.js +++ b/app/kubernetes/helpers/application/index.js @@ -391,12 +391,12 @@ class KubernetesApplicationHelper { /* #region PERSISTED FOLDERS FV <> VOLUMES */ static generatePersistedFoldersFormValuesFromPersistedFolders(persistedFolders, persistentVolumeClaims) { const finalRes = _.map(persistedFolders, (folder) => { - const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.PersistentVolumeClaimName)); - const res = new KubernetesApplicationPersistedFolderFormValue(pvc.StorageClass); - res.PersistentVolumeClaimName = folder.PersistentVolumeClaimName; - res.Size = parseInt(pvc.Storage, 10); - res.SizeUnit = pvc.Storage.slice(-2); - res.ContainerPath = folder.MountPath; + const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.persistentVolumeClaimName)); + const res = new KubernetesApplicationPersistedFolderFormValue(pvc.storageClass); + res.persistentVolumeClaimName = folder.persistentVolumeClaimName; + res.size = pvc.Storage.slice(0, -2); // remove trailing units + res.sizeUnit = pvc.Storage.slice(-2); + res.containerPath = folder.MountPath; return res; }); return finalRes; @@ -420,11 +420,11 @@ class KubernetesApplicationHelper { } static hasRWOOnly(formValues) { - return _.find(formValues.PersistedFolders, (item) => item.StorageClass && _.isEqual(item.StorageClass.AccessModes, ['RWO'])); + return _.find(formValues.PersistedFolders, (item) => item.storageClass && _.isEqual(item.storageClass.AccessModes, ['RWO'])); } static hasRWX(claims) { - return _.find(claims, (item) => item.StorageClass && _.includes(item.StorageClass.AccessModes, 'RWX')) !== undefined; + return _.find(claims, (item) => item.storageClass && _.includes(item.storageClass.AccessModes, 'RWX')) !== undefined; } /* #endregion */ diff --git a/app/kubernetes/helpers/resourceQuotaHelper.js b/app/kubernetes/helpers/resourceQuotaHelper.js index 46d4cf033..e713ee748 100644 --- a/app/kubernetes/helpers/resourceQuotaHelper.js +++ b/app/kubernetes/helpers/resourceQuotaHelper.js @@ -7,8 +7,8 @@ class KubernetesResourceQuotaHelper { static formatBytes(bytes, decimals = 0, base10 = true) { const res = { - Size: 0, - SizeUnit: 'B', + size: 0, + sizeUnit: 'B', }; if (bytes === 0) { @@ -22,8 +22,8 @@ class KubernetesResourceQuotaHelper { const i = Math.floor(Math.log(bytes) / Math.log(k)); return { - Size: parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), - SizeUnit: sizes[i], + size: parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), + sizeUnit: sizes[i], }; } } diff --git a/app/kubernetes/models/application/formValues.js b/app/kubernetes/models/application/formValues.js index 243765758..aa4f50563 100644 --- a/app/kubernetes/models/application/formValues.js +++ b/app/kubernetes/models/application/formValues.js @@ -81,20 +81,20 @@ export class KubernetesApplicationEnvironmentVariableFormValue { * KubernetesApplicationPersistedFolderFormValue Model */ const _KubernetesApplicationPersistedFolderFormValue = Object.freeze({ - PersistentVolumeClaimName: '', // will be empty for new volumes (create/edit app) and filled for existing ones (edit) - NeedsDeletion: false, - ContainerPath: '', - Size: '', - SizeUnit: 'GB', - StorageClass: {}, - ExistingVolume: null, - UseNewVolume: true, + persistentVolumeClaimName: '', // will be empty for new volumes (create/edit app) and filled for existing ones (edit) + needsDeletion: false, + containerPath: '', + size: '', + sizeUnit: 'GB', + storageClass: {}, + existingVolume: null, + useNewVolume: true, }); export class KubernetesApplicationPersistedFolderFormValue { constructor(storageClass) { Object.assign(this, JSON.parse(JSON.stringify(_KubernetesApplicationPersistedFolderFormValue))); - this.StorageClass = storageClass; + this.storageClass = storageClass; } } diff --git a/app/kubernetes/models/application/models/index.js b/app/kubernetes/models/application/models/index.js index 0391a7f86..1e562c252 100644 --- a/app/kubernetes/models/application/models/index.js +++ b/app/kubernetes/models/application/models/index.js @@ -69,7 +69,7 @@ export class HelmApplication { */ const _KubernetesApplicationPersistedFolder = Object.freeze({ MountPath: '', - PersistentVolumeClaimName: '', + persistentVolumeClaimName: '', HostPath: '', }); diff --git a/app/kubernetes/models/volume/models.js b/app/kubernetes/models/volume/models.js index bb140dcaf..2163412e8 100644 --- a/app/kubernetes/models/volume/models.js +++ b/app/kubernetes/models/volume/models.js @@ -8,7 +8,7 @@ const _KubernetesPersistentVolumeClaim = Object.freeze({ PreviousName: '', Namespace: '', Storage: 0, - StorageClass: {}, // KubernetesStorageClass + storageClass: {}, // KubernetesStorageClass CreationDate: '', ApplicationOwner: '', AccessModes: [], diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index a6bc99a7f..f0ad11e94 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -6,7 +6,7 @@ import { NamespacesSelector } from '@/react/kubernetes/cluster/RegistryAccessVie import { StorageAccessModeSelector } from '@/react/kubernetes/cluster/ConfigureView/ConfigureForm/StorageAccessModeSelector'; import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/AccessView/NamespaceAccessUsersSelector'; import { RegistriesSelector } from '@/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector'; -import { KubeApplicationAccessPolicySelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationAccessPolicySelector'; +import { DataAccessPolicyFormSection } from '@/react/kubernetes/applications/CreateView/DataAccessPolicyFormSection'; import { KubeServicesForm } from '@/react/kubernetes/applications/CreateView/application-services/KubeServicesForm'; import { kubeServicesValidation } from '@/react/kubernetes/applications/CreateView/application-services/kubeServicesValidation'; import { KubeApplicationDeploymentTypeSelector } from '@/react/kubernetes/applications/CreateView/KubeApplicationDeploymentTypeSelector'; @@ -28,6 +28,8 @@ import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/Appl import { SecretsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/SecretsFormSection'; import { configurationsValidationSchema } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/configurationValidationSchema'; import { ConfigMapsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection'; +import { PersistedFoldersFormSection } from '@/react/kubernetes/applications/components/PersistedFoldersFormSection'; +import { persistedFoldersValidation } from '@/react/kubernetes/applications/components/PersistedFoldersFormSection/persistedFoldersValidation'; import { EnvironmentVariablesFieldset } from '@@/form-components/EnvironmentVariablesFieldset'; @@ -94,8 +96,8 @@ export const ngModule = angular r2a(withUIRouter(withReactQuery(withCurrentUser(NodesDatatable))), []) ) .component( - 'kubeApplicationAccessPolicySelector', - r2a(KubeApplicationAccessPolicySelector, [ + 'dataAccessPolicyFormSection', + r2a(DataAccessPolicyFormSection, [ 'value', 'onChange', 'isEdit', @@ -205,3 +207,17 @@ withFormValidation( ['values', 'onChange', 'namespace'], configurationsValidationSchema ); + +withFormValidation( + ngModule, + withUIRouter(withCurrentUser(withReactQuery(PersistedFoldersFormSection))), + 'persistedFoldersFormSection', + [ + 'isEdit', + 'applicationValues', + 'isAddPersistentFolderButtonShown', + 'initialValues', + 'availableVolumes', + ], + persistedFoldersValidation +); diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index 1a597a51d..f559bb292 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -410,250 +410,25 @@ > - -
-
- -
- -
- - No storage option is available to persist data, contact your administrator to enable a storage option. -
- -
-
- - - This namespace has exhausted its storage capacity. Contact your administrator to expand the capacity of the namespace. - -
- -
-
-
- path in container - -
- -
- - - - -
- -
- requested size - - - - -
- -
- storage - - -
- -
- volume - -
- -
-
- - -
-
-
- -
-
-
- -

Path is required.

-
-

This path is already defined.

-
-
- -
-
- -

Size is required.

-

This value must be greater than zero.

-
-

- - You can only request up to - {{ ctrl.state.storages.availabilities[persistedFolder.StorageClass.Name] | kubernetesAppStorageRequestSizeHumanReadable }} for - {{ persistedFolder.StorageClass.Name }} -

-
-
- -

Volume is required.

-
-

This volume is already used.

-
-
- -
-
-
- -
- - Add persisted folder - -
-
-
- +
-
-
- -
-
- -
-
Specify how the data will be used across instances.
-
- - + >
diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js index 10c33e1ea..415c6acf1 100644 --- a/app/kubernetes/views/applications/create/createApplicationController.js +++ b/app/kubernetes/views/applications/create/createApplicationController.js @@ -153,6 +153,7 @@ class KubernetesCreateApplicationController { this.onEnvironmentVariableChange = this.onEnvironmentVariableChange.bind(this); this.onConfigMapsChange = this.onConfigMapsChange.bind(this); this.onSecretsChange = this.onSecretsChange.bind(this); + this.onChangePersistedFolder = this.onChangePersistedFolder.bind(this); } /* #endregion */ @@ -312,21 +313,21 @@ class KubernetesCreateApplicationController { } restorePersistedFolder(index) { - this.formValues.PersistedFolders[index].NeedsDeletion = false; + this.formValues.PersistedFolders[index].needsDeletion = false; this.validatePersistedFolders(); } resetPersistedFolders() { this.formValues.PersistedFolders = _.forEach(this.formValues.PersistedFolders, (persistedFolder) => { - persistedFolder.ExistingVolume = null; - persistedFolder.UseNewVolume = true; + persistedFolder.existingVolume = null; + persistedFolder.useNewVolume = true; }); this.validatePersistedFolders(); } removePersistedFolder(index) { - if (this.state.isEdit && this.formValues.PersistedFolders[index].PersistentVolumeClaimName) { - this.formValues.PersistedFolders[index].NeedsDeletion = true; + if (this.state.isEdit && this.formValues.PersistedFolders[index].persistentVolumeClaimName) { + this.formValues.PersistedFolders[index].needsDeletion = true; } else { this.formValues.PersistedFolders.splice(index, 1); } @@ -334,15 +335,15 @@ class KubernetesCreateApplicationController { } useNewVolume(index) { - this.formValues.PersistedFolders[index].UseNewVolume = true; - this.formValues.PersistedFolders[index].ExistingVolume = null; - this.state.persistedFoldersUseExistingVolumes = !_.reduce(this.formValues.PersistedFolders, (acc, pf) => acc && pf.UseNewVolume, true); + this.formValues.PersistedFolders[index].useNewVolume = true; + this.formValues.PersistedFolders[index].existingVolume = null; + this.state.persistedFoldersUseExistingVolumes = _.some(this.formValues.PersistedFolders, { useNewVolume: false }); this.validatePersistedFolders(); } useExistingVolume(index) { - this.formValues.PersistedFolders[index].UseNewVolume = false; - this.state.persistedFoldersUseExistingVolumes = _.find(this.formValues.PersistedFolders, { UseNewVolume: false }) ? true : false; + this.formValues.PersistedFolders[index].useNewVolume = false; + this.state.persistedFoldersUseExistingVolumes = _.some(this.formValues.PersistedFolders, { useNewVolume: false }); if (this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED) { this.formValues.DataAccessPolicy = this.ApplicationDataAccessPolicies.SHARED; this.resetDeploymentType(); @@ -360,22 +361,26 @@ class KubernetesCreateApplicationController { onChangePersistedFolderPath() { this.state.duplicates.persistedFolders.refs = KubernetesFormValidationHelper.getDuplicates( _.map(this.formValues.PersistedFolders, (persistedFolder) => { - if (persistedFolder.NeedsDeletion) { + if (persistedFolder.needsDeletion) { return undefined; } - return persistedFolder.ContainerPath; + return persistedFolder.containerPath; }) ); this.state.duplicates.persistedFolders.hasRefs = Object.keys(this.state.duplicates.persistedFolders.refs).length > 0; } + onChangePersistedFolder(values) { + this.formValues.PersistedFolders = values; + } + onChangeExistingVolumeSelection() { this.state.duplicates.existingVolumes.refs = KubernetesFormValidationHelper.getDuplicates( _.map(this.formValues.PersistedFolders, (persistedFolder) => { - if (persistedFolder.NeedsDeletion) { + if (persistedFolder.needsDeletion) { return undefined; } - return persistedFolder.ExistingVolume ? persistedFolder.ExistingVolume.PersistentVolumeClaim.Name : ''; + return persistedFolder.existingVolume ? persistedFolder.existingVolume.PersistentVolumeClaim.Name : ''; }) ); this.state.duplicates.existingVolumes.hasRefs = Object.keys(this.state.duplicates.existingVolumes.refs).length > 0; @@ -518,8 +523,8 @@ class KubernetesCreateApplicationController { for (let i = 0; i < this.formValues.PersistedFolders.length; i++) { const folder = this.formValues.PersistedFolders[i]; - if (folder.StorageClass && _.isEqual(folder.StorageClass.AccessModes, ['RWO'])) { - storageOptions.push(folder.StorageClass.Name); + if (folder.storageClass && _.isEqual(folder.storageClass.AccessModes, ['RWO'])) { + storageOptions.push(folder.storageClass.Name); } else { storageOptions.push(''); } @@ -612,7 +617,7 @@ class KubernetesCreateApplicationController { /* #region PERSISTED FOLDERS */ /* #region BUTTONS STATES */ - isAddPersistentFolderButtonShowed() { + isAddPersistentFolderButtonShown() { return !this.isEditAndStatefulSet() && this.formValues.Containers.length <= 1; } @@ -630,7 +635,7 @@ class KubernetesCreateApplicationController { } isEditAndExistingPersistedFolder(index) { - return this.state.isEdit && this.formValues.PersistedFolders[index].PersistentVolumeClaimName; + return this.state.isEdit && this.formValues.PersistedFolders[index].persistentVolumeClaimName; } /* #endregion */ @@ -781,7 +786,7 @@ class KubernetesCreateApplicationController { this.volumes = volumes; const filteredVolumes = _.filter(this.volumes, (volume) => { const isUnused = !KubernetesVolumeHelper.isUsed(volume); - const isRWX = volume.PersistentVolumeClaim.StorageClass && _.includes(volume.PersistentVolumeClaim.StorageClass.AccessModes, 'RWX'); + const isRWX = volume.PersistentVolumeClaim.storageClass && _.includes(volume.PersistentVolumeClaim.storageClass.AccessModes, 'RWX'); return isUnused || isRWX; }); this.availableVolumes = filteredVolumes; @@ -873,7 +878,11 @@ class KubernetesCreateApplicationController { this.state.actionInProgress = true; await this.KubernetesApplicationService.patch(this.savedFormValues, this.formValues, false, this.originalServicePorts); this.Notifications.success('Success', 'Request to update application successfully submitted'); - this.$state.go('kubernetes.applications.application', { name: this.application.Name, namespace: this.application.ResourcePool }); + this.$state.go( + 'kubernetes.applications.application', + { name: this.application.Name, namespace: this.application.ResourcePool, endpointId: this.endpoint.Id }, + { inherit: false } + ); } catch (err) { this.Notifications.error('Failure', err, 'Unable to update application'); } finally { @@ -1087,13 +1096,14 @@ class KubernetesCreateApplicationController { if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) { _.forEach(this.formValues.PersistedFolders, (persistedFolder) => { - const volume = _.find(this.availableVolumes, ['PersistentVolumeClaim.Name', persistedFolder.PersistentVolumeClaimName]); + const volume = _.find(this.availableVolumes, ['PersistentVolumeClaim.Name', persistedFolder.persistentVolumeClaimName]); if (volume) { - persistedFolder.UseNewVolume = false; - persistedFolder.ExistingVolume = volume; + persistedFolder.useNewVolume = false; + persistedFolder.existingVolume = volume; } }); } + this.formValues.OriginalPersistedFolders = this.formValues.PersistedFolders; await this.refreshNamespaceData(namespace); } else { this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(null, this.formValues.ReplicaCount); diff --git a/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html b/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html index 84db9d3f8..b36cb7a43 100644 --- a/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html +++ b/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html @@ -91,7 +91,7 @@ {{ item.Name }} - {{ item.Size }} + {{ item.size }} @@ -102,7 +102,7 @@ {{ item.Name }} - {{ item.Size }} + {{ item.size }} Storage Class - {{ ctrl.volume.PersistentVolumeClaim.StorageClass.Name }} + {{ ctrl.volume.PersistentVolumeClaim.storageClass.Name }} Access Modes @@ -69,7 +69,7 @@ Provisioner {{ - ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner ? ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner : '-' + ctrl.volume.PersistentVolumeClaim.storageClass.Provisioner ? ctrl.volume.PersistentVolumeClaim.storageClass.Provisioner : '-' }} @@ -77,14 +77,14 @@ {{ ctrl.volume.PersistentVolumeClaim.CreationDate | getisodate }} - Size + size {{ ctrl.volume.PersistentVolumeClaim.Storage }} diff --git a/app/kubernetes/views/volumes/edit/volumeController.js b/app/kubernetes/views/volumes/edit/volumeController.js index 09fb1faf5..f9e9c12f6 100644 --- a/app/kubernetes/views/volumes/edit/volumeController.js +++ b/app/kubernetes/views/volumes/edit/volumeController.js @@ -186,12 +186,14 @@ class KubernetesVolumeController { try { await this.getVolume(); await this.getEvents(); - this.state.volumeSharedAccessPolicies = this.volume.PersistentVolumeClaim.AccessModes; - let policies = KubernetesStorageClassAccessPolicies(); - this.state.volumeSharedAccessPolicyTooltips = this.state.volumeSharedAccessPolicies.map((policy) => { - const matchingPolicy = policies.find((p) => p.Name === policy); - return matchingPolicy ? matchingPolicy.Description : undefined; - }); + if (this.volume.PersistentVolumeClaim.storageClass !== undefined) { + this.state.volumeSharedAccessPolicies = this.volume.PersistentVolumeClaim.AccessModes; + let policies = KubernetesStorageClassAccessPolicies(); + this.state.volumeSharedAccessPolicyTooltips = this.state.volumeSharedAccessPolicies.map((policy) => { + const matchingPolicy = policies.find((p) => p.Name === policy); + return matchingPolicy ? matchingPolicy.Description : undefined; + }); + } } catch (err) { this.Notifications.error('Failure', err, 'Unable to load view data'); } finally { diff --git a/app/kubernetes/views/volumes/volumesController.js b/app/kubernetes/views/volumes/volumesController.js index 6e0ab6152..b21062a6d 100644 --- a/app/kubernetes/views/volumes/volumesController.js +++ b/app/kubernetes/views/volumes/volumesController.js @@ -7,9 +7,9 @@ import { confirmDelete } from '@@/modals/confirm'; function buildStorages(storages, volumes) { _.forEach(storages, (s) => { - const filteredVolumes = _.filter(volumes, ['PersistentVolumeClaim.StorageClass.Name', s.Name, 'PersistentVolumeClaim.StorageClass.Provisioner', s.Provisioner]); + const filteredVolumes = _.filter(volumes, ['PersistentVolumeClaim.storageClass.Name', s.Name, 'PersistentVolumeClaim.storageClass.Provisioner', s.Provisioner]); s.Volumes = filteredVolumes; - s.Size = computeSize(filteredVolumes); + s.size = computeSize(filteredVolumes); }); return storages; } @@ -17,7 +17,7 @@ function buildStorages(storages, volumes) { function computeSize(volumes) { const size = _.sumBy(volumes, (v) => filesizeParser(v.PersistentVolumeClaim.Storage, { base: 10 })); const format = KubernetesResourceQuotaHelper.formatBytes(size); - return `${format.Size}${format.SizeUnit}`; + return `${format.size}${format.sizeUnit}`; } class KubernetesVolumesController { diff --git a/app/react-tools/withFormValidation.ts b/app/react-tools/withFormValidation.ts index 53e04de81..71af58d35 100644 --- a/app/react-tools/withFormValidation.ts +++ b/app/react-tools/withFormValidation.ts @@ -129,10 +129,17 @@ function createFormValidatorController( }); } - async $onChanges(changes: { values?: { currentValue: TFormModel } }) { + async $onChanges(changes: { + values?: { currentValue: TFormModel }; + validationData?: { currentValue: TData }; + }) { if (changes.values) { await this.runValidation(changes.values.currentValue); } + // also run validation if validationData changes + if (changes.validationData) { + await this.runValidation(this.values!); + } } }; } diff --git a/app/react/components/form-components/ButtonSelector/ButtonSelector.tsx b/app/react/components/form-components/ButtonSelector/ButtonSelector.tsx index 44a9301e5..1a1e10d6f 100644 --- a/app/react/components/form-components/ButtonSelector/ButtonSelector.tsx +++ b/app/react/components/form-components/ButtonSelector/ButtonSelector.tsx @@ -9,6 +9,7 @@ import styles from './ButtonSelector.module.css'; export interface Option { value: T; label?: ReactNode; + disabled?: boolean; } interface Props { @@ -43,7 +44,7 @@ export function ButtonSelector({ key={option.value.toString()} selected={value === option.value} onChange={() => onChange(option.value)} - disabled={disabled} + disabled={disabled || option.disabled} readOnly={readOnly} > {option.label || option.value.toString()} diff --git a/app/react/components/form-components/FormSection/FormSection.tsx b/app/react/components/form-components/FormSection/FormSection.tsx index 319ecdfa9..ec09cdcea 100644 --- a/app/react/components/form-components/FormSection/FormSection.tsx +++ b/app/react/components/form-components/FormSection/FormSection.tsx @@ -10,6 +10,7 @@ interface Props { titleSize?: 'sm' | 'md' | 'lg'; isFoldable?: boolean; defaultFolded?: boolean; + titleClassName?: string; } export function FormSection({ @@ -18,6 +19,7 @@ export function FormSection({ children, isFoldable = false, defaultFolded = isFoldable, + titleClassName, }: PropsWithChildren) { const [isExpanded, setIsExpanded] = useState(!defaultFolded); @@ -26,6 +28,7 @@ export function FormSection({ {isFoldable && (