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

refactor(app): persisted folders form section [EE-6235] (#10693)

* refactor(app): persisted folder section [EE-6235]
This commit is contained in:
Ali 2024-01-03 09:46:26 +13:00 committed by GitHub
parent 7a2412b1be
commit e07ee05ee7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 732 additions and 374 deletions

View file

@ -137,9 +137,9 @@
<table-column-header
col-title="'Storage'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'PersistentVolumeClaim.StorageClass.Name'"
is-sorted-desc="$ctrl.state.orderBy === 'PersistentVolumeClaim.StorageClass.Name' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('PersistentVolumeClaim.StorageClass.Name')"
is-sorted="$ctrl.state.orderBy === 'PersistentVolumeClaim.storageClass.Name'"
is-sorted-desc="$ctrl.state.orderBy === 'PersistentVolumeClaim.storageClass.Name' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('PersistentVolumeClaim.storageClass.Name')"
></table-column-header>
</th>
<th>
@ -188,7 +188,7 @@
<span ng-if="!item.Applications.length">-</span>
</td>
<td>
{{ item.PersistentVolumeClaim.StorageClass.Name }}
{{ item.PersistentVolumeClaim.storageClass.Name }}
</td>
<td>
{{ item.PersistentVolumeClaim.Storage }}

View file

@ -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;
}

View file

@ -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;

View file

@ -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();

View file

@ -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 */

View file

@ -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],
};
}
}

View file

@ -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;
}
}

View file

@ -69,7 +69,7 @@ export class HelmApplication {
*/
const _KubernetesApplicationPersistedFolder = Object.freeze({
MountPath: '',
PersistentVolumeClaimName: '',
persistentVolumeClaimName: '',
HostPath: '',
});

View file

@ -8,7 +8,7 @@ const _KubernetesPersistentVolumeClaim = Object.freeze({
PreviousName: '',
Namespace: '',
Storage: 0,
StorageClass: {}, // KubernetesStorageClass
storageClass: {}, // KubernetesStorageClass
CreationDate: '',
ApplicationOwner: '',
AccessModes: [],

View file

@ -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
);

View file

@ -410,250 +410,25 @@
></secrets-form-section>
<!-- #endregion -->
<!-- #region PERSISTED FOLDERS -->
<div class="form-group">
<div class="col-sm-12 vertical-center mb-2 pt-2.5" style="margin-top: 5px">
<label class="control-label !pt-0 text-left !text-sm">Persisted folders</label>
</div>
<div class="col-sm-12 small text-muted vertical-center mt-1" ng-if="!ctrl.storageClassAvailable()">
<pr-icon icon="'info'" mode="'primary'"></pr-icon>
No storage option is available to persist data, contact your administrator to enable a storage option.
</div>
<div class="row" ng-if="ctrl.storageClassAvailable()">
<div class="col-sm-12" style="margin-top: 5px" ng-if="ctrl.allQuotasExhaustedAndNoVolumesAvailable()">
<span class="small text-muted vertical-center">
<pr-icon icon="'alert-circle'" mode="'warning'"></pr-icon>
This namespace has exhausted its storage capacity. Contact your administrator to expand the capacity of the namespace.
</span>
</div>
<div class="col-sm-12 form-inline" style="margin-top: 10px" ng-repeat="persistedFolder in ctrl.formValues.PersistedFolders">
<div style="margin-top: 2px">
<div class="input-group col-sm-3 input-group-sm" ng-class="{ striked: persistedFolder.NeedsDeletion }">
<span class="input-group-addon required">path in container</span>
<input
type="text"
class="form-control"
name="persisted_folder_path_{{ $index }}"
ng-model="persistedFolder.ContainerPath"
ng-change="ctrl.onChangePersistedFolderPath()"
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
placeholder="/data"
required
data-cy="k8sAppCreate-containerPathInput_{{ $index }}"
/>
</div>
<div
class="input-group col-sm-2 input-group-sm"
ng-if="
!ctrl.isEditAndExistingPersistedFolder($index) &&
ctrl.application.ApplicationType !== ctrl.ApplicationTypes.STATEFULSET &&
ctrl.formValues.Containers.length <= 1
"
>
<span class="btn-group btn-group-sm" ng-class="{ striked: persistedFolder.NeedsDeletion }">
<label
class="btn btn-light"
ng-model="persistedFolder.UseNewVolume"
uib-btn-radio="true"
ng-change="ctrl.useNewVolume($index)"
ng-disabled="ctrl.isNewVolumeButtonDisabled($index)"
>New volume</label
>
<label
class="btn btn-light"
ng-model="persistedFolder.UseNewVolume"
uib-btn-radio="false"
ng-change="ctrl.useExistingVolume($index)"
ng-disabled="ctrl.isExistingVolumeButtonDisabled()"
>Existing volume</label
>
</span>
</div>
<div class="input-group col-sm-3 input-group-sm" ng-class="{ striked: persistedFolder.NeedsDeletion }" ng-if="persistedFolder.UseNewVolume">
<span class="input-group-addon required">requested size</span>
<input
type="number"
class="form-control !rounded-none"
name="persisted_folder_size_{{ $index }}"
ng-model="persistedFolder.Size"
placeholder="20"
min="0"
required
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
ng-change="ctrl.onChangeVolumeRequestedSize()"
/>
<span class="input-group-addon !rounded-r-[5px] !p-0">
<select
class="form-control !h-[28px] w-12 !rounded-r-[5px] !border-none text-xs"
ng-model="persistedFolder.SizeUnit"
ng-style="{ height: '100%', cursor: ctrl.isEditAndExistingPersistedFolder($index) ? 'not-allowed' : 'auto' }"
ng-options="unit for unit in ctrl.state.availableSizeUnits"
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
ng-change="ctrl.onChangeVolumeRequestedSize()"
></select>
</span>
</div>
<div class="input-group col-sm-2 input-group-sm" ng-class="{ striked: persistedFolder.NeedsDeletion }" ng-if="persistedFolder.UseNewVolume">
<span class="input-group-addon">storage</span>
<select
ng-if="ctrl.hasMultipleStorageClassesAvailable()"
class="form-control"
ng-model="persistedFolder.StorageClass"
ng-options="storageClass as storageClass.Name for storageClass in ctrl.storageClasses"
ng-disabled="ctrl.state.isEdit || ctrl.formValues.Containers.length > 1"
data-cy="k8sAppCreate-storageSelect_{{ $index }}"
></select>
<input
ng-if="!ctrl.hasMultipleStorageClassesAvailable()"
type="text"
class="form-control"
disabled
ng-model="persistedFolder.StorageClass.Name"
data-cy="k8sAppCreate-storageClassNameInput_{{ $index }}"
/>
</div>
<div class="input-group col-sm-5 input-group-sm" ng-if="!persistedFolder.UseNewVolume" ng-class="{ striked: persistedFolder.NeedsDeletion }">
<span class="input-group-addon">volume</span>
<select
class="form-control"
name="existing_volumes_{{ $index }}"
ng-model="ctrl.formValues.PersistedFolders[$index].ExistingVolume"
ng-options="vol as vol.PersistentVolumeClaim.Name for vol in ctrl.availableVolumes"
ng-change="ctrl.onChangeExistingVolumeSelection()"
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index) || ctrl.formValues.Containers.length > 1"
required
>
<option selected disabled hidden value="">Select a volume</option>
</select>
</div>
<div class="input-group col-sm-1 input-group-sm">
<div ng-if="!ctrl.isEditAndStatefulSet() && !ctrl.state.useExistingVolume[$index] && ctrl.formValues.Containers.length <= 1">
<button
ng-if="!persistedFolder.NeedsDeletion"
class="btn btn-sm btn-dangerlight !ml-0 h-[30px]"
type="button"
ng-click="ctrl.removePersistedFolder($index)"
data-cy="k8sAppCreate-rmPersistentFolderButton"
>
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
</button>
<button
ng-if="persistedFolder.NeedsDeletion"
class="btn btn-sm btn-primary"
type="button"
ng-click="ctrl.restorePersistedFolder($index)"
data-cy="k8sAppCreate-restorePersistentButton"
>
<pr-icon icon="'rotate-cw'"></pr-icon>
</button>
</div>
</div>
</div>
<div
class="flex flex-row gap-x-1"
ng-show="
kubernetesApplicationCreationForm['persisted_folder_path_' + $index].$invalid ||
ctrl.state.duplicates.persistedFolders.refs[$index] !== undefined ||
kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$invalid ||
ctrl.state.exceeded.persistedFolders.refs[$index] !== undefined ||
kubernetesApplicationCreationForm['existing_volumes_' + $index].$invalid ||
ctrl.state.duplicates.existingVolumes.refs[$index] !== undefined
"
>
<div class="input-group col-sm-3 input-group-sm">
<div
class="small text-warning"
style="margin-top: 5px"
ng-show="
kubernetesApplicationCreationForm['persisted_folder_path_' + $index].$invalid || ctrl.state.duplicates.persistedFolders.refs[$index] !== undefined
"
>
<ng-messages for="kubernetesApplicationCreationForm['persisted_folder_path_' + $index].$error">
<p class="vertical-center" ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Path is required.</p>
</ng-messages>
<p class="vertical-center" ng-if="ctrl.state.duplicates.persistedFolders.refs[$index] !== undefined"
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This path is already defined.</p
>
</div>
</div>
<div class="input-group col-sm-offset-3 col-sm-3 input-group-sm">
<div
class="small text-warning"
style="margin-top: 5px"
ng-show="
kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$invalid || ctrl.state.exceeded.persistedFolders.refs[$index] !== undefined
"
>
<ng-messages for="kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$error">
<p class="vertical-center" ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Size is required.</p>
<p class="vertical-center" ng-message="min"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This value must be greater than zero.</p>
</ng-messages>
<p class="vertical-center" ng-if="ctrl.state.exceeded.persistedFolders.refs[$index] !== undefined">
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
You can only request up to
{{ ctrl.state.storages.availabilities[persistedFolder.StorageClass.Name] | kubernetesAppStorageRequestSizeHumanReadable }} for
{{ persistedFolder.StorageClass.Name }}
</p>
</div>
<div
class="small text-warning"
ng-show="kubernetesApplicationCreationForm['existing_volumes_' + $index].$invalid || ctrl.state.duplicates.existingVolumes.refs[$index] !== undefined"
>
<ng-messages for="kubernetesApplicationCreationForm['existing_volumes_' + $index].$error">
<p ng-message="required"><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Volume is required.</p>
</ng-messages>
<p ng-if="ctrl.state.duplicates.existingVolumes.refs[$index] !== undefined"
><pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> This volume is already used.</p
>
</div>
</div>
<div class="input-group col-sm-1 input-group-sm"> </div>
</div>
</div>
<div class="col-sm-12 mt-2">
<span
class="btn btn-primary btn-sm btn btn-sm btn-light mb-2 !ml-0"
ng-click="ctrl.addPersistedFolder()"
ng-if="ctrl.isAddPersistentFolderButtonShowed()"
data-cy="k8sAppCreate-addPersistentFolderButton"
>
<pr-icon icon="'plus'" size="'sm'"></pr-icon> Add persisted folder
</span>
</div>
</div>
</div>
<!-- #endregion -->
<persisted-folders-form-section
values="ctrl.formValues.PersistedFolders"
initial-values="ctrl.formValues.OriginalPersistedFolders"
on-change="(ctrl.onChangePersistedFolder)"
is-edit="ctrl.state.isEdit"
application-values="ctrl.formValues"
is-add-persistent-folder-button-shown="ctrl.isAddPersistentFolderButtonShown()"
available-volumes="ctrl.availableVolumes"
validation-data="{ namespaceQuotas: ctrl.formValues.ResourcePool.Quota, persistedFolders: ctrl.formValues.PersistedFolders, storageAvailabilities: ctrl.state.storages.availabilities }"
></persisted-folders-form-section>
<!-- #region DATA ACCESS POLICY -->
<div ng-if="ctrl.showDataAccessPolicySection()">
<div class="form-group">
<div class="col-sm-12">
<label class="control-label text-left">Data access policy</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-12 small text-muted"> Specify how the data will be used across instances. </div>
</div>
<kube-application-access-policy-selector
<data-access-policy-form-section
value="ctrl.formValues.DataAccessPolicy"
on-change="(ctrl.onDataAccessPolicyChange)"
is-edit="ctrl.state.isEdit"
persisted-folders-use-existing-volumes="ctrl.state.persistedFoldersUseExistingVolumes"
></kube-application-access-policy-selector>
></data-access-policy-form-section>
</div>
<!-- #endregion -->

View file

@ -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('<no storage option available>');
}
@ -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);

View file

@ -91,7 +91,7 @@
</div>
</td>
<td>{{ item.Name }}</td>
<td>{{ item.Size }}</td>
<td>{{ item.size }}</td>
</tr>
<tr
dir-paginate-end

View file

@ -81,9 +81,9 @@
<table-column-header
col-title="'Usage'"
can-sort="true"
is-sorted="$ctrl.state.orderBy === 'Size'"
is-sorted-desc="$ctrl.state.orderBy === 'Size' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('Size')"
is-sorted="$ctrl.state.orderBy === 'size'"
is-sorted-desc="$ctrl.state.orderBy === 'size' && $ctrl.state.reverseOrder"
ng-click="$ctrl.changeOrderBy('size')"
></table-column-header>
</th>
</tr>
@ -102,7 +102,7 @@
</div>
</td>
<td>{{ item.Name }}</td>
<td>{{ item.Size }}</td>
<td>{{ item.size }}</td>
</tr>
<tr
dir-paginate-end

View file

@ -50,7 +50,7 @@
</tr>
<tr>
<td>Storage Class</td>
<td data-cy="k8sVolDetail-volStorageClassname">{{ ctrl.volume.PersistentVolumeClaim.StorageClass.Name }}</td>
<td data-cy="k8sVolDetail-volStorageClassname">{{ ctrl.volume.PersistentVolumeClaim.storageClass.Name }}</td>
</tr>
<tr>
<td>Access Modes</td>
@ -69,7 +69,7 @@
<tr>
<td>Provisioner</td>
<td data-cy="k8sVolDetail-volProvisioner">{{
ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner ? ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner : '-'
ctrl.volume.PersistentVolumeClaim.storageClass.Provisioner ? ctrl.volume.PersistentVolumeClaim.storageClass.Provisioner : '-'
}}</td>
</tr>
<tr>
@ -77,14 +77,14 @@
<td data-cy="k8sVolDetail-volCreatedAt">{{ ctrl.volume.PersistentVolumeClaim.CreationDate | getisodate }}</td>
</tr>
<tr>
<td>Size</td>
<td>size</td>
<td ng-if="!ctrl.state.increaseSize">
{{ ctrl.volume.PersistentVolumeClaim.Storage }}
<button
type="button"
class="btn btn-sm btn-primary"
ng-click="ctrl.state.increaseSize = true"
ng-if="ctrl.volume.PersistentVolumeClaim.StorageClass.AllowVolumeExpansion"
ng-if="ctrl.volume.PersistentVolumeClaim.storageClass.AllowVolumeExpansion"
data-cy="k8sVolDetail-increaseSizeButton"
>Increase size</button
>

View file

@ -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 {

View file

@ -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 {