mirror of https://github.com/portainer/portainer
refactor(app): backport technical changes (#4679)
* refactor(app): backport technical changes * refactor(app): remove EE only features * feat(app): small review changes to match EE codebase layout on some files Co-authored-by: xAt0mZ <baron_l@epitech.eu>pull/4782/head
parent
158bdae10e
commit
ccf6babc02
|
@ -5,7 +5,7 @@ const portPattern = /^([1-9]|[1-5]?[0-9]{2,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655
|
||||||
|
|
||||||
function parsePort(port) {
|
function parsePort(port) {
|
||||||
if (portPattern.test(port)) {
|
if (portPattern.test(port)) {
|
||||||
return parseInt(port);
|
return parseInt(port, 10);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -211,14 +211,14 @@ angular.module('portainer.docker').factory('ContainerHelper', [
|
||||||
_.forEach(portBindingKeysByHostIp, (portBindingKeys, ip) => {
|
_.forEach(portBindingKeysByHostIp, (portBindingKeys, ip) => {
|
||||||
// Sort by host port
|
// Sort by host port
|
||||||
const sortedPortBindingKeys = _.orderBy(portBindingKeys, (portKey) => {
|
const sortedPortBindingKeys = _.orderBy(portBindingKeys, (portKey) => {
|
||||||
return parseInt(_.split(portKey, '/')[0]);
|
return parseInt(_.split(portKey, '/')[0], 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
let previousHostPort = -1;
|
let previousHostPort = -1;
|
||||||
let previousContainerPort = -1;
|
let previousContainerPort = -1;
|
||||||
_.forEach(sortedPortBindingKeys, (portKey) => {
|
_.forEach(sortedPortBindingKeys, (portKey) => {
|
||||||
const portKeySplit = _.split(portKey, '/');
|
const portKeySplit = _.split(portKey, '/');
|
||||||
const containerPort = parseInt(portKeySplit[0]);
|
const containerPort = parseInt(portKeySplit[0], 10);
|
||||||
const portBinding = portBindings[portKey][0];
|
const portBinding = portBindings[portKey][0];
|
||||||
portBindings[portKey].shift();
|
portBindings[portKey].shift();
|
||||||
const hostPort = parsePort(portBinding.HostPort);
|
const hostPort = parsePort(portBinding.HostPort);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import filesizeParser from 'filesize-parser';
|
import filesizeParser from 'filesize-parser';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class KubernetesPersistentVolumeClaimConverter {
|
||||||
res.Name = data.metadata.name;
|
res.Name = data.metadata.name;
|
||||||
res.Namespace = data.metadata.namespace;
|
res.Namespace = data.metadata.namespace;
|
||||||
res.CreationDate = data.metadata.creationTimestamp;
|
res.CreationDate = data.metadata.creationTimestamp;
|
||||||
res.Storage = data.spec.resources.requests.storage.replace('i', 'B');
|
res.Storage = `${data.spec.resources.requests.storage}B`;
|
||||||
res.StorageClass = _.find(storageClasses, { Name: data.spec.storageClassName });
|
res.StorageClass = _.find(storageClasses, { Name: data.spec.storageClassName });
|
||||||
res.Yaml = yaml ? yaml.data : '';
|
res.Yaml = yaml ? yaml.data : '';
|
||||||
res.ApplicationOwner = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationOwnerLabel] : '';
|
res.ApplicationOwner = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationOwnerLabel] : '';
|
||||||
|
@ -35,7 +35,7 @@ class KubernetesPersistentVolumeClaimConverter {
|
||||||
pvc.PreviousName = item.PersistentVolumeClaimName;
|
pvc.PreviousName = item.PersistentVolumeClaimName;
|
||||||
}
|
}
|
||||||
pvc.StorageClass = existantPVC.StorageClass;
|
pvc.StorageClass = existantPVC.StorageClass;
|
||||||
pvc.Storage = existantPVC.Storage.charAt(0) + 'i';
|
pvc.Storage = existantPVC.Storage.charAt(0);
|
||||||
pvc.CreationDate = existantPVC.CreationDate;
|
pvc.CreationDate = existantPVC.CreationDate;
|
||||||
pvc.Id = existantPVC.Id;
|
pvc.Id = existantPVC.Id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,7 +45,7 @@ class KubernetesPersistentVolumeClaimConverter {
|
||||||
} else {
|
} else {
|
||||||
pvc.Name = formValues.Name + '-' + pvc.Name;
|
pvc.Name = formValues.Name + '-' + pvc.Name;
|
||||||
}
|
}
|
||||||
pvc.Storage = '' + item.Size + item.SizeUnit.charAt(0) + 'i';
|
pvc.Storage = '' + item.Size + item.SizeUnit.charAt(0);
|
||||||
pvc.StorageClass = item.StorageClass;
|
pvc.StorageClass = item.StorageClass;
|
||||||
}
|
}
|
||||||
pvc.MountPath = item.ContainerPath;
|
pvc.MountPath = item.ContainerPath;
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
import { KubernetesResourcePool } from 'Kubernetes/models/resource-pool/models';
|
import { KubernetesResourcePool } from 'Kubernetes/models/resource-pool/models';
|
||||||
|
import { KubernetesNamespace } from 'Kubernetes/models/namespace/models';
|
||||||
|
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||||
|
import KubernetesResourceQuotaConverter from './resourceQuota';
|
||||||
|
|
||||||
class KubernetesResourcePoolConverter {
|
class KubernetesResourcePoolConverter {
|
||||||
static apiToResourcePool(namespace) {
|
static apiToResourcePool(namespace) {
|
||||||
|
@ -7,6 +12,24 @@ class KubernetesResourcePoolConverter {
|
||||||
res.Yaml = namespace.Yaml;
|
res.Yaml = namespace.Yaml;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static formValuesToResourcePool(formValues) {
|
||||||
|
const namespace = new KubernetesNamespace();
|
||||||
|
namespace.Name = formValues.Name;
|
||||||
|
namespace.ResourcePoolName = formValues.Name;
|
||||||
|
namespace.ResourcePoolOwner = formValues.Owner;
|
||||||
|
|
||||||
|
const quota = KubernetesResourceQuotaConverter.resourcePoolFormValuesToResourceQuota(formValues);
|
||||||
|
|
||||||
|
const ingMap = _.map(formValues.IngressClasses, (c) => {
|
||||||
|
if (c.Selected) {
|
||||||
|
c.Namespace = namespace.Name;
|
||||||
|
return KubernetesIngressConverter.resourcePoolIngressClassFormValueToIngress(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const ingresses = _.without(ingMap, undefined);
|
||||||
|
return [namespace, quota, ingresses];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesResourcePoolConverter;
|
export default KubernetesResourcePoolConverter;
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
import filesizeParser from 'filesize-parser';
|
import filesizeParser from 'filesize-parser';
|
||||||
|
|
||||||
import { KubernetesResourceQuota } from 'Kubernetes/models/resource-quota/models';
|
import {
|
||||||
import { KubernetesResourceQuotaCreatePayload, KubernetesResourceQuotaUpdatePayload } from 'Kubernetes/models/resource-quota/payloads';
|
KubernetesResourceQuota,
|
||||||
|
KubernetesPortainerResourceQuotaCPULimit,
|
||||||
|
KubernetesPortainerResourceQuotaMemoryLimit,
|
||||||
|
KubernetesPortainerResourceQuotaCPURequest,
|
||||||
|
KubernetesPortainerResourceQuotaMemoryRequest,
|
||||||
|
KubernetesResourceQuotaDefaults,
|
||||||
|
} from 'Kubernetes/models/resource-quota/models';
|
||||||
|
import { KubernetesResourceQuotaCreatePayload } from 'Kubernetes/models/resource-quota/payloads';
|
||||||
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||||
import { KubernetesPortainerResourcePoolNameLabel, KubernetesPortainerResourcePoolOwnerLabel } from 'Kubernetes/models/resource-pool/models';
|
import { KubernetesPortainerResourcePoolNameLabel, KubernetesPortainerResourcePoolOwnerLabel } from 'Kubernetes/models/resource-pool/models';
|
||||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||||
|
import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
|
||||||
|
import { KubernetesResourcePoolFormValues } from 'Kubernetes/models/resource-pool/formValues';
|
||||||
|
|
||||||
class KubernetesResourceQuotaConverter {
|
class KubernetesResourceQuotaConverter {
|
||||||
static apiToResourceQuota(data, yaml) {
|
static apiToResourceQuota(data, yaml) {
|
||||||
|
@ -14,21 +24,21 @@ class KubernetesResourceQuotaConverter {
|
||||||
res.Name = data.metadata.name;
|
res.Name = data.metadata.name;
|
||||||
res.CpuLimit = 0;
|
res.CpuLimit = 0;
|
||||||
res.MemoryLimit = 0;
|
res.MemoryLimit = 0;
|
||||||
if (data.spec.hard && data.spec.hard['limits.cpu']) {
|
if (data.spec.hard && data.spec.hard[KubernetesPortainerResourceQuotaCPULimit]) {
|
||||||
res.CpuLimit = KubernetesResourceReservationHelper.parseCPU(data.spec.hard['limits.cpu']);
|
res.CpuLimit = KubernetesResourceReservationHelper.parseCPU(data.spec.hard[KubernetesPortainerResourceQuotaCPULimit]);
|
||||||
}
|
}
|
||||||
if (data.spec.hard && data.spec.hard['limits.memory']) {
|
if (data.spec.hard && data.spec.hard[KubernetesPortainerResourceQuotaMemoryLimit]) {
|
||||||
res.MemoryLimit = filesizeParser(data.spec.hard['limits.memory'], { base: 10 });
|
res.MemoryLimit = filesizeParser(data.spec.hard[KubernetesPortainerResourceQuotaMemoryLimit], { base: 10 });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.MemoryLimitUsed = 0;
|
res.MemoryLimitUsed = 0;
|
||||||
if (data.status.used && data.status.used['limits.memory']) {
|
if (data.status.used && data.status.used[KubernetesPortainerResourceQuotaMemoryLimit]) {
|
||||||
res.MemoryLimitUsed = filesizeParser(data.status.used['limits.memory'], { base: 10 });
|
res.MemoryLimitUsed = filesizeParser(data.status.used[KubernetesPortainerResourceQuotaMemoryLimit], { base: 10 });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.CpuLimitUsed = 0;
|
res.CpuLimitUsed = 0;
|
||||||
if (data.status.used && data.status.used['limits.cpu']) {
|
if (data.status.used && data.status.used[KubernetesPortainerResourceQuotaCPULimit]) {
|
||||||
res.CpuLimitUsed = KubernetesResourceReservationHelper.parseCPU(data.status.used['limits.cpu']);
|
res.CpuLimitUsed = KubernetesResourceReservationHelper.parseCPU(data.status.used[KubernetesPortainerResourceQuotaCPULimit]);
|
||||||
}
|
}
|
||||||
res.Yaml = yaml ? yaml.data : '';
|
res.Yaml = yaml ? yaml.data : '';
|
||||||
res.ResourcePoolName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerResourcePoolNameLabel] : '';
|
res.ResourcePoolName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerResourcePoolNameLabel] : '';
|
||||||
|
@ -40,48 +50,54 @@ class KubernetesResourceQuotaConverter {
|
||||||
const res = new KubernetesResourceQuotaCreatePayload();
|
const res = new KubernetesResourceQuotaCreatePayload();
|
||||||
res.metadata.name = KubernetesResourceQuotaHelper.generateResourceQuotaName(quota.Namespace);
|
res.metadata.name = KubernetesResourceQuotaHelper.generateResourceQuotaName(quota.Namespace);
|
||||||
res.metadata.namespace = quota.Namespace;
|
res.metadata.namespace = quota.Namespace;
|
||||||
res.spec.hard['requests.cpu'] = quota.CpuLimit;
|
KubernetesCommonHelper.assignOrDeleteIfEmptyOrZero(res, `spec.hard['${KubernetesPortainerResourceQuotaCPURequest}']`, quota.CpuLimit);
|
||||||
res.spec.hard['requests.memory'] = quota.MemoryLimit;
|
KubernetesCommonHelper.assignOrDeleteIfEmptyOrZero(res, `spec.hard['${KubernetesPortainerResourceQuotaMemoryRequest}']`, quota.MemoryLimit);
|
||||||
res.spec.hard['limits.cpu'] = quota.CpuLimit;
|
KubernetesCommonHelper.assignOrDeleteIfEmptyOrZero(res, `spec.hard['${KubernetesPortainerResourceQuotaCPULimit}']`, quota.CpuLimit);
|
||||||
res.spec.hard['limits.memory'] = quota.MemoryLimit;
|
KubernetesCommonHelper.assignOrDeleteIfEmptyOrZero(res, `spec.hard['${KubernetesPortainerResourceQuotaMemoryLimit}']`, quota.MemoryLimit);
|
||||||
res.metadata.labels[KubernetesPortainerResourcePoolNameLabel] = quota.ResourcePoolName;
|
res.metadata.labels[KubernetesPortainerResourcePoolNameLabel] = quota.ResourcePoolName;
|
||||||
if (quota.ResourcePoolOwner) {
|
if (quota.ResourcePoolOwner) {
|
||||||
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = quota.ResourcePoolOwner;
|
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = quota.ResourcePoolOwner;
|
||||||
}
|
}
|
||||||
if (!quota.CpuLimit || quota.CpuLimit === 0) {
|
|
||||||
delete res.spec.hard['requests.cpu'];
|
|
||||||
delete res.spec.hard['limits.cpu'];
|
|
||||||
}
|
|
||||||
if (!quota.MemoryLimit || quota.MemoryLimit === 0) {
|
|
||||||
delete res.spec.hard['requests.memory'];
|
|
||||||
delete res.spec.hard['limits.memory'];
|
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updatePayload(quota) {
|
static updatePayload(quota) {
|
||||||
const res = new KubernetesResourceQuotaUpdatePayload();
|
const res = KubernetesResourceQuotaConverter.createPayload(quota);
|
||||||
res.metadata.name = quota.Name;
|
|
||||||
res.metadata.namespace = quota.Namespace;
|
|
||||||
res.metadata.uid = quota.Id;
|
res.metadata.uid = quota.Id;
|
||||||
res.spec.hard['requests.cpu'] = quota.CpuLimit;
|
|
||||||
res.spec.hard['requests.memory'] = quota.MemoryLimit;
|
|
||||||
res.spec.hard['limits.cpu'] = quota.CpuLimit;
|
|
||||||
res.spec.hard['limits.memory'] = quota.MemoryLimit;
|
|
||||||
res.metadata.labels[KubernetesPortainerResourcePoolNameLabel] = quota.ResourcePoolName;
|
|
||||||
if (quota.ResourcePoolOwner) {
|
|
||||||
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = quota.ResourcePoolOwner;
|
|
||||||
}
|
|
||||||
if (!quota.CpuLimit || quota.CpuLimit === 0) {
|
|
||||||
delete res.spec.hard['requests.cpu'];
|
|
||||||
delete res.spec.hard['limits.cpu'];
|
|
||||||
}
|
|
||||||
if (!quota.MemoryLimit || quota.MemoryLimit === 0) {
|
|
||||||
delete res.spec.hard['requests.memory'];
|
|
||||||
delete res.spec.hard['limits.memory'];
|
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static patchPayload(oldQuota, newQuota) {
|
||||||
|
const oldPayload = KubernetesResourceQuotaConverter.createPayload(oldQuota);
|
||||||
|
const newPayload = KubernetesResourceQuotaConverter.createPayload(newQuota);
|
||||||
|
const payload = JsonPatch.compare(oldPayload, newPayload);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
static quotaToResourcePoolFormValues(quota) {
|
||||||
|
const res = new KubernetesResourcePoolFormValues(KubernetesResourceQuotaDefaults);
|
||||||
|
res.Name = quota.Namespace;
|
||||||
|
res.CpuLimit = quota.CpuLimit;
|
||||||
|
res.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimit);
|
||||||
|
if (res.CpuLimit || res.MemoryLimit) {
|
||||||
|
res.HasQuota = true;
|
||||||
|
}
|
||||||
|
res.StorageClasses = quota.StorageRequests;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static resourcePoolFormValuesToResourceQuota(formValues) {
|
||||||
|
if (formValues.HasQuota) {
|
||||||
|
const quota = new KubernetesResourceQuota(formValues.Name);
|
||||||
|
if (formValues.HasQuota) {
|
||||||
|
quota.CpuLimit = formValues.CpuLimit;
|
||||||
|
quota.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
|
||||||
|
}
|
||||||
|
quota.ResourcePoolName = formValues.Name;
|
||||||
|
quota.ResourcePoolOwner = formValues.Owner;
|
||||||
|
return quota;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesResourceQuotaConverter;
|
export default KubernetesResourceQuotaConverter;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
|
|
||||||
import { KubernetesServiceCreatePayload } from 'Kubernetes/models/service/payloads';
|
import { KubernetesServiceCreatePayload } from 'Kubernetes/models/service/payloads';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
|
|
||||||
import { KubernetesStorageClass } from 'Kubernetes/models/storage-class/models';
|
import { KubernetesStorageClass } from 'Kubernetes/models/storage-class/models';
|
||||||
import { KubernetesStorageClassCreatePayload } from 'Kubernetes/models/storage-class/payload';
|
import { KubernetesStorageClassCreatePayload } from 'Kubernetes/models/storage-class/payload';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
|
||||||
|
|
||||||
class KubernetesStorageClassConverter {
|
class KubernetesStorageClassConverter {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { KubernetesPortMapping, KubernetesPortMappingPort } from 'Kubernetes/models/port/models';
|
import { KubernetesPortMapping, KubernetesPortMappingPort } from 'Kubernetes/models/port/models';
|
||||||
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
import { KubernetesServiceTypes } from 'Kubernetes/models/service/models';
|
||||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||||
|
@ -322,7 +322,7 @@ class KubernetesApplicationHelper {
|
||||||
const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.PersistentVolumeClaimName));
|
const pvc = _.find(persistentVolumeClaims, (item) => _.startsWith(item.Name, folder.PersistentVolumeClaimName));
|
||||||
const res = new KubernetesApplicationPersistedFolderFormValue(pvc.StorageClass);
|
const res = new KubernetesApplicationPersistedFolderFormValue(pvc.StorageClass);
|
||||||
res.PersistentVolumeClaimName = folder.PersistentVolumeClaimName;
|
res.PersistentVolumeClaimName = folder.PersistentVolumeClaimName;
|
||||||
res.Size = parseInt(pvc.Storage.slice(0, -2));
|
res.Size = parseInt(pvc.Storage, 10);
|
||||||
res.SizeUnit = pvc.Storage.slice(-2);
|
res.SizeUnit = pvc.Storage.slice(-2);
|
||||||
res.ContainerPath = folder.MountPath;
|
res.ContainerPath = folder.MountPath;
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -16,5 +16,13 @@ class KubernetesCommonHelper {
|
||||||
label = _.replace(label, /[-_.]*$/g, '');
|
label = _.replace(label, /[-_.]*$/g, '');
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static assignOrDeleteIfEmptyOrZero(obj, path, value) {
|
||||||
|
if (!value || value === 0 || (value instanceof Array && !value.length)) {
|
||||||
|
_.unset(obj, path);
|
||||||
|
} else {
|
||||||
|
_.set(obj, path, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default KubernetesCommonHelper;
|
export default KubernetesCommonHelper;
|
||||||
|
|
|
@ -4,6 +4,28 @@ class KubernetesResourceQuotaHelper {
|
||||||
static generateResourceQuotaName(name) {
|
static generateResourceQuotaName(name) {
|
||||||
return KubernetesPortainerResourceQuotaPrefix + name;
|
return KubernetesPortainerResourceQuotaPrefix + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static formatBytes(bytes, decimals = 0, base10 = true) {
|
||||||
|
const res = {
|
||||||
|
Size: 0,
|
||||||
|
SizeUnit: 'B',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bytes === 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const k = base10 ? 1000 : 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return {
|
||||||
|
Size: parseFloat((bytes / Math.pow(k, i)).toFixed(dm)),
|
||||||
|
SizeUnit: sizes[i],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesResourceQuotaHelper;
|
export default KubernetesResourceQuotaHelper;
|
||||||
|
|
|
@ -26,7 +26,7 @@ class KubernetesResourceReservationHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseCPU(cpu) {
|
static parseCPU(cpu) {
|
||||||
let res = parseInt(cpu);
|
let res = parseInt(cpu, 10);
|
||||||
if (_.endsWith(cpu, 'm')) {
|
if (_.endsWith(cpu, 'm')) {
|
||||||
res /= 1000;
|
res /= 1000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
|
|
||||||
import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
|
import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
|
||||||
|
@ -80,18 +80,18 @@ export class KubernetesIngressConverter {
|
||||||
}
|
}
|
||||||
res.Annotations[KubernetesIngressClassAnnotation] = formValues.IngressClass.Name;
|
res.Annotations[KubernetesIngressClassAnnotation] = formValues.IngressClass.Name;
|
||||||
res.Host = formValues.Host;
|
res.Host = formValues.Host;
|
||||||
|
res.Paths = formValues.Paths;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {KubernetesIngressClass} ics Ingress classes (saved in Portainer DB)
|
* @param {KubernetesIngressClass} ics Ingress classes (saved in Portainer DB)
|
||||||
* @param {KubernetesIngress} ingresses Existing Kubernetes ingresses. Must be empty for RP CREATE VIEW and passed for RP EDIT VIEW
|
* @param {KubernetesIngress[]} ingresses Existing Kubernetes ingresses. Must be empty for RP CREATE VIEW and filled for RP EDIT VIEW
|
||||||
*/
|
*/
|
||||||
static ingressClassesToFormValues(ics, ingresses) {
|
static ingressClassesToFormValues(ics, ingresses) {
|
||||||
const res = _.map(ics, (ic) => {
|
const res = _.map(ics, (ic) => {
|
||||||
const fv = new KubernetesResourcePoolIngressClassFormValue();
|
const fv = new KubernetesResourcePoolIngressClassFormValue(ic);
|
||||||
fv.IngressClass = ic;
|
|
||||||
const ingress = _.find(ingresses, { Name: ic.Name });
|
const ingress = _.find(ingresses, { Name: ic.Name });
|
||||||
if (ingress) {
|
if (ingress) {
|
||||||
fv.Selected = true;
|
fv.Selected = true;
|
||||||
|
@ -110,6 +110,7 @@ export class KubernetesIngressConverter {
|
||||||
});
|
});
|
||||||
fv.Annotations = _.without(annotations, undefined);
|
fv.Annotations = _.without(annotations, undefined);
|
||||||
fv.AdvancedConfig = fv.Annotations.length > 0;
|
fv.AdvancedConfig = fv.Annotations.length > 0;
|
||||||
|
fv.Paths = ingress.Paths;
|
||||||
}
|
}
|
||||||
return fv;
|
return fv;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
export class KubernetesIngressHelper {
|
export class KubernetesIngressHelper {
|
||||||
static findSBoundServiceIngressesRules(ingresses, serviceName) {
|
static findSBoundServiceIngressesRules(ingresses, serviceName) {
|
||||||
|
|
|
@ -1,30 +1,23 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import PortainerError from 'Portainer/error';
|
import PortainerError from 'Portainer/error';
|
||||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||||
import { KubernetesIngressConverter } from './converter';
|
import { KubernetesIngressConverter } from './converter';
|
||||||
|
|
||||||
class KubernetesIngressService {
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor($async, KubernetesIngresses) {
|
export function KubernetesIngressService($async, KubernetesIngresses) {
|
||||||
this.$async = $async;
|
return {
|
||||||
this.KubernetesIngresses = KubernetesIngresses;
|
get,
|
||||||
|
create,
|
||||||
|
patch,
|
||||||
|
delete: _delete,
|
||||||
|
};
|
||||||
|
|
||||||
this.getAsync = this.getAsync.bind(this);
|
async function getOne(namespace, name) {
|
||||||
this.getAllAsync = this.getAllAsync.bind(this);
|
|
||||||
this.createAsync = this.createAsync.bind(this);
|
|
||||||
this.patchAsync = this.patchAsync.bind(this);
|
|
||||||
this.deleteAsync = this.deleteAsync.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET
|
|
||||||
*/
|
|
||||||
async getAsync(namespace, name) {
|
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = name;
|
params.id = name;
|
||||||
const [raw, yaml] = await Promise.all([this.KubernetesIngresses(namespace).get(params).$promise, this.KubernetesIngresses(namespace).getYaml(params).$promise]);
|
const [raw, yaml] = await Promise.all([KubernetesIngresses(namespace).get(params).$promise, KubernetesIngresses(namespace).getYaml(params).$promise]);
|
||||||
const res = {
|
const res = {
|
||||||
Raw: KubernetesIngressConverter.apiToModel(raw),
|
Raw: KubernetesIngressConverter.apiToModel(raw),
|
||||||
Yaml: yaml.data,
|
Yaml: yaml.data,
|
||||||
|
@ -35,9 +28,9 @@ class KubernetesIngressService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAsync(namespace) {
|
async function getAll(namespace) {
|
||||||
try {
|
try {
|
||||||
const data = await this.KubernetesIngresses(namespace).get().$promise;
|
const data = await KubernetesIngresses(namespace).get().$promise;
|
||||||
const res = _.reduce(data.items, (arr, item) => _.concat(arr, KubernetesIngressConverter.apiToModel(item)), []);
|
const res = _.reduce(data.items, (arr, item) => _.concat(arr, KubernetesIngressConverter.apiToModel(item)), []);
|
||||||
return res;
|
return res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -45,36 +38,29 @@ class KubernetesIngressService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace, name) {
|
function get(namespace, name) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return this.$async(this.getAsync, namespace, name);
|
return $async(getOne, namespace, name);
|
||||||
}
|
}
|
||||||
return this.$async(this.getAllAsync, namespace);
|
return $async(getAll, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function create(ingress) {
|
||||||
* CREATE
|
return $async(async () => {
|
||||||
*/
|
|
||||||
async createAsync(ingress) {
|
|
||||||
try {
|
try {
|
||||||
const params = {};
|
const params = {};
|
||||||
const payload = KubernetesIngressConverter.createPayload(ingress);
|
const payload = KubernetesIngressConverter.createPayload(ingress);
|
||||||
const namespace = payload.metadata.namespace;
|
const namespace = payload.metadata.namespace;
|
||||||
const data = await this.KubernetesIngresses(namespace).create(params, payload).$promise;
|
const data = await KubernetesIngresses(namespace).create(params, payload).$promise;
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to create ingress', err);
|
throw new PortainerError('Unable to create ingress', err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create(ingress) {
|
function patch(oldIngress, newIngress) {
|
||||||
return this.$async(this.createAsync, ingress);
|
return $async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PATCH
|
|
||||||
*/
|
|
||||||
async patchAsync(oldIngress, newIngress) {
|
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = newIngress.Name;
|
params.id = newIngress.Name;
|
||||||
|
@ -83,35 +69,26 @@ class KubernetesIngressService {
|
||||||
if (!payload.length) {
|
if (!payload.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = await this.KubernetesIngresses(namespace).patch(params, payload).$promise;
|
const data = await KubernetesIngresses(namespace).patch(params, payload).$promise;
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to patch ingress', err);
|
throw new PortainerError('Unable to patch ingress', err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(oldIngress, newIngress) {
|
function _delete(ingress) {
|
||||||
return this.$async(this.patchAsync, oldIngress, newIngress);
|
return $async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE
|
|
||||||
*/
|
|
||||||
async deleteAsync(ingress) {
|
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = ingress.IngressClass.Name;
|
params.id = ingress.Name;
|
||||||
const namespace = ingress.Namespace;
|
const namespace = ingress.Namespace;
|
||||||
await this.KubernetesIngresses(namespace).delete(params).$promise;
|
await KubernetesIngresses(namespace).delete(params).$promise;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to delete ingress', err);
|
throw new PortainerError('Unable to delete ingress', err);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
delete(ingress) {
|
|
||||||
return this.$async(this.deleteAsync, ingress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesIngressService;
|
|
||||||
angular.module('portainer.kubernetes').service('KubernetesIngressService', KubernetesIngressService);
|
angular.module('portainer.kubernetes').service('KubernetesIngressService', KubernetesIngressService);
|
||||||
|
|
|
@ -153,9 +153,9 @@ export class KubernetesApplicationAutoScalerFormValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KubernetesFormValueDuplicate() {
|
export function KubernetesFormValidationReferences() {
|
||||||
return {
|
return {
|
||||||
refs: {},
|
refs: {},
|
||||||
hasDuplicates: false,
|
hasRefs: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/**
|
export function KubernetesNamespace() {
|
||||||
* KubernetesNamespace Model
|
return {
|
||||||
*/
|
|
||||||
const _KubernetesNamespace = Object.freeze({
|
|
||||||
Id: '',
|
Id: '',
|
||||||
Name: '',
|
Name: '',
|
||||||
CreationDate: '',
|
CreationDate: '',
|
||||||
|
@ -9,10 +7,5 @@ const _KubernetesNamespace = Object.freeze({
|
||||||
Yaml: '',
|
Yaml: '',
|
||||||
ResourcePoolName: '',
|
ResourcePoolName: '',
|
||||||
ResourcePoolOwner: '',
|
ResourcePoolOwner: '',
|
||||||
});
|
};
|
||||||
|
|
||||||
export class KubernetesNamespace {
|
|
||||||
constructor() {
|
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesNamespace)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
export function KubernetesResourcePoolFormValues(defaults) {
|
export function KubernetesResourcePoolFormValues(defaults) {
|
||||||
return {
|
return {
|
||||||
|
Name: '',
|
||||||
MemoryLimit: defaults.MemoryLimit,
|
MemoryLimit: defaults.MemoryLimit,
|
||||||
CpuLimit: defaults.CpuLimit,
|
CpuLimit: defaults.CpuLimit,
|
||||||
HasQuota: true,
|
HasQuota: false,
|
||||||
IngressClasses: [], // KubernetesResourcePoolIngressClassFormValue
|
IngressClasses: [], // KubernetesResourcePoolIngressClassFormValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,6 +21,7 @@ export function KubernetesResourcePoolIngressClassFormValue(ingressClass) {
|
||||||
Selected: false,
|
Selected: false,
|
||||||
WasSelected: false,
|
WasSelected: false,
|
||||||
AdvancedConfig: false,
|
AdvancedConfig: false,
|
||||||
|
Paths: [], // will be filled to save IngressClass.Paths inside ingressClassesToFormValues() on RP EDIT
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||||
|
|
||||||
export const KubernetesPortainerResourceQuotaPrefix = 'portainer-rq-';
|
export const KubernetesPortainerResourceQuotaPrefix = 'portainer-rq-';
|
||||||
|
export const KubernetesPortainerResourceQuotaCPULimit = 'limits.cpu';
|
||||||
|
export const KubernetesPortainerResourceQuotaMemoryLimit = 'limits.memory';
|
||||||
|
export const KubernetesPortainerResourceQuotaCPURequest = 'requests.cpu';
|
||||||
|
export const KubernetesPortainerResourceQuotaMemoryRequest = 'requests.memory';
|
||||||
|
|
||||||
export const KubernetesResourceQuotaDefaults = {
|
export const KubernetesResourceQuotaDefaults = {
|
||||||
CpuLimit: 0,
|
CpuLimit: 0,
|
||||||
MemoryLimit: 0,
|
MemoryLimit: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export function KubernetesResourceQuota(namespace) {
|
||||||
* KubernetesResourceQuota Model
|
return {
|
||||||
*/
|
|
||||||
const _KubernetesResourceQuota = Object.freeze({
|
|
||||||
Id: '',
|
Id: '',
|
||||||
Namespace: '',
|
Namespace: namespace ? namespace : '',
|
||||||
Name: '',
|
Name: namespace ? KubernetesResourceQuotaHelper.generateResourceQuotaName(namespace) : '',
|
||||||
CpuLimit: KubernetesResourceQuotaDefaults.CpuLimit,
|
CpuLimit: KubernetesResourceQuotaDefaults.CpuLimit,
|
||||||
MemoryLimit: KubernetesResourceQuotaDefaults.MemoryLimit,
|
MemoryLimit: KubernetesResourceQuotaDefaults.MemoryLimit,
|
||||||
CpuLimitUsed: KubernetesResourceQuotaDefaults.CpuLimit,
|
CpuLimitUsed: KubernetesResourceQuotaDefaults.CpuLimit,
|
||||||
|
@ -21,14 +23,5 @@ const _KubernetesResourceQuota = Object.freeze({
|
||||||
Yaml: '',
|
Yaml: '',
|
||||||
ResourcePoolName: '',
|
ResourcePoolName: '',
|
||||||
ResourcePoolOwner: '',
|
ResourcePoolOwner: '',
|
||||||
});
|
};
|
||||||
|
|
||||||
export class KubernetesResourceQuota {
|
|
||||||
constructor(namespace) {
|
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesResourceQuota)));
|
|
||||||
if (namespace) {
|
|
||||||
this.Name = KubernetesResourceQuotaHelper.generateResourceQuotaName(namespace);
|
|
||||||
this.Namespace = namespace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,21 @@
|
||||||
import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloads';
|
import { KubernetesCommonMetadataPayload } from 'Kubernetes/models/common/payloads';
|
||||||
|
import {
|
||||||
|
KubernetesPortainerResourceQuotaCPURequest,
|
||||||
|
KubernetesPortainerResourceQuotaMemoryRequest,
|
||||||
|
KubernetesPortainerResourceQuotaCPULimit,
|
||||||
|
KubernetesPortainerResourceQuotaMemoryLimit,
|
||||||
|
} from './models';
|
||||||
|
|
||||||
/**
|
export function KubernetesResourceQuotaCreatePayload() {
|
||||||
* KubernetesResourceQuotaCreatePayload Model
|
return {
|
||||||
*/
|
|
||||||
const _KubernetesResourceQuotaCreatePayload = Object.freeze({
|
|
||||||
metadata: new KubernetesCommonMetadataPayload(),
|
metadata: new KubernetesCommonMetadataPayload(),
|
||||||
spec: {
|
spec: {
|
||||||
hard: {
|
hard: {
|
||||||
'requests.cpu': 0,
|
[KubernetesPortainerResourceQuotaCPURequest]: 0,
|
||||||
'requests.memory': 0,
|
[KubernetesPortainerResourceQuotaMemoryRequest]: 0,
|
||||||
'limits.cpu': 0,
|
[KubernetesPortainerResourceQuotaCPULimit]: 0,
|
||||||
'limits.memory': 0,
|
[KubernetesPortainerResourceQuotaMemoryLimit]: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
export class KubernetesResourceQuotaCreatePayload {
|
|
||||||
constructor() {
|
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesResourceQuotaCreatePayload)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* KubernetesResourceQuotaUpdatePayload Model
|
|
||||||
*/
|
|
||||||
const _KubernetesResourceQuotaUpdatePayload = Object.freeze({
|
|
||||||
metadata: new KubernetesCommonMetadataPayload(),
|
|
||||||
spec: {
|
|
||||||
hard: {
|
|
||||||
'requests.cpu': 0,
|
|
||||||
'requests.memory': 0,
|
|
||||||
'limits.cpu': 0,
|
|
||||||
'limits.memory': 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export class KubernetesResourceQuotaUpdatePayload {
|
|
||||||
constructor() {
|
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesResourceQuotaUpdatePayload)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,12 @@ angular.module('portainer.kubernetes').factory('KubernetesResourceQuotas', [
|
||||||
},
|
},
|
||||||
create: { method: 'POST' },
|
create: { method: 'POST' },
|
||||||
update: { method: 'PUT' },
|
update: { method: 'PUT' },
|
||||||
|
patch: {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json-patch+json',
|
||||||
|
},
|
||||||
|
},
|
||||||
delete: { method: 'DELETE' },
|
delete: { method: 'DELETE' },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import PortainerError from 'Portainer/error';
|
import PortainerError from 'Portainer/error';
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,22 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { KubernetesResourceQuota } from 'Kubernetes/models/resource-quota/models';
|
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import KubernetesResourcePoolConverter from 'Kubernetes/converters/resourcePool';
|
import KubernetesResourcePoolConverter from 'Kubernetes/converters/resourcePool';
|
||||||
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||||
import { KubernetesNamespace } from 'Kubernetes/models/namespace/models';
|
|
||||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
|
||||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
|
||||||
import KubernetesCommonHelper from 'Kubernetes/helpers/commonHelper';
|
|
||||||
|
|
||||||
class KubernetesResourcePoolService {
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor($async, KubernetesNamespaceService, KubernetesResourceQuotaService, KubernetesIngressService) {
|
export function KubernetesResourcePoolService($async, KubernetesNamespaceService, KubernetesResourceQuotaService, KubernetesIngressService) {
|
||||||
this.$async = $async;
|
return {
|
||||||
this.KubernetesNamespaceService = KubernetesNamespaceService;
|
get,
|
||||||
this.KubernetesResourceQuotaService = KubernetesResourceQuotaService;
|
create,
|
||||||
this.KubernetesIngressService = KubernetesIngressService;
|
patch,
|
||||||
|
delete: _delete,
|
||||||
|
};
|
||||||
|
|
||||||
this.getAsync = this.getAsync.bind(this);
|
async function getOne(name) {
|
||||||
this.getAllAsync = this.getAllAsync.bind(this);
|
|
||||||
this.createAsync = this.createAsync.bind(this);
|
|
||||||
this.deleteAsync = this.deleteAsync.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET
|
|
||||||
*/
|
|
||||||
async getAsync(name) {
|
|
||||||
try {
|
try {
|
||||||
const namespace = await this.KubernetesNamespaceService.get(name);
|
const namespace = await KubernetesNamespaceService.get(name);
|
||||||
const [quotaAttempt] = await Promise.allSettled([this.KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||||
if (quotaAttempt.status === 'fulfilled') {
|
if (quotaAttempt.status === 'fulfilled') {
|
||||||
pool.Quota = quotaAttempt.value;
|
pool.Quota = quotaAttempt.value;
|
||||||
|
@ -41,13 +28,13 @@ class KubernetesResourcePoolService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAsync() {
|
async function getAll() {
|
||||||
try {
|
try {
|
||||||
const namespaces = await this.KubernetesNamespaceService.get();
|
const namespaces = await KubernetesNamespaceService.get();
|
||||||
const pools = await Promise.all(
|
const pools = await Promise.all(
|
||||||
_.map(namespaces, async (namespace) => {
|
_.map(namespaces, async (namespace) => {
|
||||||
const name = namespace.Name;
|
const name = namespace.Name;
|
||||||
const [quotaAttempt] = await Promise.allSettled([this.KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
const [quotaAttempt] = await Promise.allSettled([KubernetesResourceQuotaService.get(name, KubernetesResourceQuotaHelper.generateResourceQuotaName(name))]);
|
||||||
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
const pool = KubernetesResourcePoolConverter.apiToResourcePool(namespace);
|
||||||
if (quotaAttempt.status === 'fulfilled') {
|
if (quotaAttempt.status === 'fulfilled') {
|
||||||
pool.Quota = quotaAttempt.value;
|
pool.Quota = quotaAttempt.value;
|
||||||
|
@ -62,66 +49,75 @@ class KubernetesResourcePoolService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(name) {
|
function get(name) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return this.$async(this.getAsync, name);
|
return $async(getOne, name);
|
||||||
}
|
}
|
||||||
return this.$async(this.getAllAsync);
|
return $async(getAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function create(formValues) {
|
||||||
* CREATE
|
return $async(async () => {
|
||||||
* @param {KubernetesResourcePoolFormValues} formValues
|
|
||||||
*/
|
|
||||||
async createAsync(formValues) {
|
|
||||||
formValues.Owner = KubernetesCommonHelper.ownerToLabel(formValues.Owner);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const namespace = new KubernetesNamespace();
|
const [namespace, quota, ingresses] = KubernetesResourcePoolConverter.formValuesToResourcePool(formValues);
|
||||||
namespace.Name = formValues.Name;
|
await KubernetesNamespaceService.create(namespace);
|
||||||
namespace.ResourcePoolName = formValues.Name;
|
|
||||||
namespace.ResourcePoolOwner = formValues.Owner;
|
if (quota) {
|
||||||
await this.KubernetesNamespaceService.create(namespace);
|
await KubernetesResourceQuotaService.create(quota);
|
||||||
if (formValues.HasQuota) {
|
|
||||||
const quota = new KubernetesResourceQuota(formValues.Name);
|
|
||||||
quota.CpuLimit = formValues.CpuLimit;
|
|
||||||
quota.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
|
|
||||||
quota.ResourcePoolName = formValues.Name;
|
|
||||||
quota.ResourcePoolOwner = formValues.Owner;
|
|
||||||
await this.KubernetesResourceQuotaService.create(quota);
|
|
||||||
}
|
}
|
||||||
const ingressPromises = _.map(formValues.IngressClasses, (c) => {
|
const ingressPromises = _.map(ingresses, (i) => KubernetesIngressService.create(i));
|
||||||
if (c.Selected) {
|
|
||||||
c.Namespace = namespace.Name;
|
|
||||||
const ingress = KubernetesIngressConverter.resourcePoolIngressClassFormValueToIngress(c);
|
|
||||||
return this.KubernetesIngressService.create(ingress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Promise.all(ingressPromises);
|
await Promise.all(ingressPromises);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create(formValues) {
|
function patch(oldFormValues, newFormValues) {
|
||||||
return this.$async(this.createAsync, formValues);
|
return $async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE
|
|
||||||
*/
|
|
||||||
async deleteAsync(pool) {
|
|
||||||
try {
|
try {
|
||||||
await this.KubernetesNamespaceService.delete(pool.Namespace);
|
const [oldNamespace, oldQuota, oldIngresses] = KubernetesResourcePoolConverter.formValuesToResourcePool(oldFormValues);
|
||||||
|
const [newNamespace, newQuota, newIngresses] = KubernetesResourcePoolConverter.formValuesToResourcePool(newFormValues);
|
||||||
|
void oldNamespace, newNamespace;
|
||||||
|
|
||||||
|
if (oldQuota && newQuota) {
|
||||||
|
await KubernetesResourceQuotaService.patch(oldQuota, newQuota);
|
||||||
|
} else if (!oldQuota && newQuota) {
|
||||||
|
await KubernetesResourceQuotaService.create(newQuota);
|
||||||
|
} else if (oldQuota && !newQuota) {
|
||||||
|
await KubernetesResourceQuotaService.delete(oldQuota);
|
||||||
|
}
|
||||||
|
|
||||||
|
const create = _.filter(newIngresses, (ing) => !_.find(oldIngresses, { Name: ing.Name }));
|
||||||
|
const del = _.filter(oldIngresses, (ing) => !_.find(newIngresses, { Name: ing.Name }));
|
||||||
|
const patch = _.without(newIngresses, ...create);
|
||||||
|
|
||||||
|
const createPromises = _.map(create, (i) => KubernetesIngressService.create(i));
|
||||||
|
const delPromises = _.map(del, (i) => KubernetesIngressService.delete(i));
|
||||||
|
const patchPromises = _.map(patch, (ing) => {
|
||||||
|
const old = _.find(oldIngresses, { Name: ing.Name });
|
||||||
|
ing.Paths = angular.copy(old.Paths);
|
||||||
|
ing.PreviousHost = old.Host;
|
||||||
|
return KubernetesIngressService.patch(old, ing);
|
||||||
|
});
|
||||||
|
|
||||||
|
const promises = _.flatten([createPromises, delPromises, patchPromises]);
|
||||||
|
await Promise.all(promises);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(pool) {
|
function _delete(pool) {
|
||||||
return this.$async(this.deleteAsync, pool);
|
return $async(async () => {
|
||||||
|
try {
|
||||||
|
await KubernetesNamespaceService.delete(pool.Namespace);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesResourcePoolService;
|
|
||||||
angular.module('portainer.kubernetes').service('KubernetesResourcePoolService', KubernetesResourcePoolService);
|
angular.module('portainer.kubernetes').service('KubernetesResourcePoolService', KubernetesResourcePoolService);
|
||||||
|
|
|
@ -5,105 +5,85 @@ import PortainerError from 'Portainer/error';
|
||||||
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
|
||||||
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
||||||
|
|
||||||
class KubernetesResourceQuotaService {
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor($async, KubernetesResourceQuotas) {
|
export function KubernetesResourceQuotaService($async, KubernetesResourceQuotas) {
|
||||||
this.$async = $async;
|
return {
|
||||||
this.KubernetesResourceQuotas = KubernetesResourceQuotas;
|
get,
|
||||||
|
create,
|
||||||
|
patch,
|
||||||
|
delete: _delete,
|
||||||
|
};
|
||||||
|
|
||||||
this.getAsync = this.getAsync.bind(this);
|
async function getOne(namespace, name) {
|
||||||
this.getAllAsync = this.getAllAsync.bind(this);
|
|
||||||
this.createAsync = this.createAsync.bind(this);
|
|
||||||
this.updateAsync = this.updateAsync.bind(this);
|
|
||||||
this.deleteAsync = this.deleteAsync.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET
|
|
||||||
*/
|
|
||||||
async getAsync(namespace, name) {
|
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = name;
|
params.id = name;
|
||||||
const [raw, yaml] = await Promise.all([this.KubernetesResourceQuotas(namespace).get(params).$promise, this.KubernetesResourceQuotas(namespace).getYaml(params).$promise]);
|
const [raw, yaml] = await Promise.all([KubernetesResourceQuotas(namespace).get(params).$promise, KubernetesResourceQuotas(namespace).getYaml(params).$promise]);
|
||||||
return KubernetesResourceQuotaConverter.apiToResourceQuota(raw, yaml);
|
return KubernetesResourceQuotaConverter.apiToResourceQuota(raw, yaml);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to retrieve resource quota', err);
|
throw new PortainerError('Unable to retrieve resource quota', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllAsync(namespace) {
|
async function getAll(namespace) {
|
||||||
try {
|
try {
|
||||||
const data = await this.KubernetesResourceQuotas(namespace).get().$promise;
|
const data = await KubernetesResourceQuotas(namespace).get().$promise;
|
||||||
return _.map(data.items, (item) => KubernetesResourceQuotaConverter.apiToResourceQuota(item));
|
return _.map(data.items, (item) => KubernetesResourceQuotaConverter.apiToResourceQuota(item));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to retrieve resource quotas', err);
|
throw new PortainerError('Unable to retrieve resource quotas', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(namespace, name) {
|
function get(namespace, name) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return this.$async(this.getAsync, namespace, name);
|
return $async(getOne, namespace, name);
|
||||||
}
|
}
|
||||||
return this.$async(this.getAllAsync, namespace);
|
return $async(getAll, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function create(quota) {
|
||||||
* CREATE
|
return $async(async () => {
|
||||||
*/
|
|
||||||
async createAsync(quota) {
|
|
||||||
try {
|
try {
|
||||||
const payload = KubernetesResourceQuotaConverter.createPayload(quota);
|
const payload = KubernetesResourceQuotaConverter.createPayload(quota);
|
||||||
const namespace = payload.metadata.namespace;
|
const namespace = payload.metadata.namespace;
|
||||||
const params = {};
|
const params = {};
|
||||||
const data = await this.KubernetesResourceQuotas(namespace).create(params, payload).$promise;
|
const data = await KubernetesResourceQuotas(namespace).create(params, payload).$promise;
|
||||||
return KubernetesResourceQuotaConverter.apiToResourceQuota(data);
|
return KubernetesResourceQuotaConverter.apiToResourceQuota(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to create quota', err);
|
throw new PortainerError('Unable to create quota', err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create(quota) {
|
function patch(oldQuota, newQuota) {
|
||||||
return this.$async(this.createAsync, quota);
|
return $async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UPDATE
|
|
||||||
*/
|
|
||||||
async updateAsync(quota) {
|
|
||||||
try {
|
try {
|
||||||
const payload = KubernetesResourceQuotaConverter.updatePayload(quota);
|
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = payload.metadata.name;
|
params.id = newQuota.Name;
|
||||||
const namespace = payload.metadata.namespace;
|
const namespace = newQuota.Namespace;
|
||||||
const data = await this.KubernetesResourceQuotas(namespace).update(params, payload).$promise;
|
const payload = KubernetesResourceQuotaConverter.patchPayload(oldQuota, newQuota);
|
||||||
|
if (!payload.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await KubernetesResourceQuotas(namespace).patch(params, payload).$promise;
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to update resource quota', err);
|
throw new PortainerError('Unable to update resource quota', err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update(quota) {
|
function _delete(quota) {
|
||||||
return this.$async(this.updateAsync, quota);
|
return $async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE
|
|
||||||
*/
|
|
||||||
async deleteAsync(quota) {
|
|
||||||
try {
|
try {
|
||||||
const params = new KubernetesCommonParams();
|
const params = new KubernetesCommonParams();
|
||||||
params.id = quota.Name;
|
params.id = quota.Name;
|
||||||
await this.KubernetesResourceQuotas(quota.Namespace).delete(params).$promise;
|
await KubernetesResourceQuotas(quota.Namespace).delete(params).$promise;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new PortainerError('Unable to delete quota', err);
|
throw new PortainerError('Unable to delete quota', err);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
delete(quota) {
|
|
||||||
return this.$async(this.deleteAsync, quota);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KubernetesResourceQuotaService;
|
|
||||||
angular.module('portainer.kubernetes').service('KubernetesResourceQuotaService', KubernetesResourceQuotaService);
|
angular.module('portainer.kubernetes').service('KubernetesResourceQuotaService', KubernetesResourceQuotaService);
|
||||||
|
|
|
@ -21,7 +21,7 @@ class KubernetesVolumeService {
|
||||||
*/
|
*/
|
||||||
async getAsync(namespace, name) {
|
async getAsync(namespace, name) {
|
||||||
try {
|
try {
|
||||||
const [pvc, pool] = await Promise.all([await this.KubernetesPersistentVolumeClaimService.get(namespace, name), await this.KubernetesResourcePoolService.get(namespace)]);
|
const [pvc, pool] = await Promise.all([this.KubernetesPersistentVolumeClaimService.get(namespace, name), this.KubernetesResourcePoolService.get(namespace)]);
|
||||||
return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
|
return KubernetesVolumeConverter.pvcToVolume(pvc, pool);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -30,7 +30,8 @@ class KubernetesVolumeService {
|
||||||
|
|
||||||
async getAllAsync(namespace) {
|
async getAllAsync(namespace) {
|
||||||
try {
|
try {
|
||||||
const pools = await this.KubernetesResourcePoolService.get(namespace);
|
const data = await this.KubernetesResourcePoolService.get(namespace);
|
||||||
|
const pools = data instanceof Array ? data : [data];
|
||||||
const res = await Promise.all(
|
const res = await Promise.all(
|
||||||
_.map(pools, async (pool) => {
|
_.map(pools, async (pool) => {
|
||||||
const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
|
const pvcs = await this.KubernetesPersistentVolumeClaimService.get(pool.Namespace.Name);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require('../../templates/advancedDeploymentPanel.html');
|
require('../../templates/advancedDeploymentPanel.html');
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import KubernetesStackHelper from 'Kubernetes/helpers/stackHelper';
|
import KubernetesStackHelper from 'Kubernetes/helpers/stackHelper';
|
||||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,8 @@
|
||||||
|
|
||||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||||
<div ng-repeat="envVar in ctrl.formValues.EnvironmentVariables | orderBy: 'NameIndex'" style="margin-top: 2px;">
|
<div ng-repeat="envVar in ctrl.formValues.EnvironmentVariables | orderBy: 'NameIndex'" style="margin-top: 2px;">
|
||||||
<div class="col-sm-4 input-group input-group-sm" style="vertical-align: top;">
|
<div style="margin-top: 2px;">
|
||||||
|
<div class="col-sm-4 input-group input-group-sm">
|
||||||
<div class="input-group col-sm-12 input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
<div class="input-group col-sm-12 input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
||||||
<span class="input-group-addon">name</span>
|
<span class="input-group-addon">name</span>
|
||||||
<input
|
<input
|
||||||
|
@ -158,6 +159,35 @@
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-4 input-group input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
||||||
|
<span class="input-group-addon">value</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="environment_variable_value_{{ $index }}"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="envVar.Value"
|
||||||
|
placeholder="bar"
|
||||||
|
ng-disabled="ctrl.formValues.Containers.length > 1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-2 input-group input-group-sm" ng-if="ctrl.formValues.Containers.length <= 1">
|
||||||
|
<button ng-if="!envVar.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeEnvironmentVariable(envVar)">
|
||||||
|
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button ng-if="envVar.NeedsDeletion" class="btn btn-sm btn-primary" type="button" ng-click="ctrl.restoreEnvironmentVariable(envVar)">
|
||||||
|
<i class="fa fa-trash-restore" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ng-show="
|
||||||
|
kubernetesApplicationCreationForm['environment_variable_name_' + $index].$invalid || ctrl.state.duplicates.environmentVariables.refs[$index] !== undefined
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="col-sm-4 input-group input-group-sm">
|
||||||
<div
|
<div
|
||||||
class="small text-warning"
|
class="small text-warning"
|
||||||
style="margin-top: 5px;"
|
style="margin-top: 5px;"
|
||||||
|
@ -177,26 +207,8 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-4 input-group input-group-sm"></div>
|
||||||
<div class="input-group col-sm-4 input-group-sm" ng-class="{ striked: envVar.NeedsDeletion }">
|
<div class="col-sm-2 input-group input-group-sm"></div>
|
||||||
<span class="input-group-addon">value</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="environment_variable_value_{{ $index }}"
|
|
||||||
class="form-control"
|
|
||||||
ng-model="envVar.Value"
|
|
||||||
placeholder="bar"
|
|
||||||
ng-disabled="ctrl.formValues.Containers.length > 1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="input-group col-sm-2 input-group-sm" ng-if="ctrl.formValues.Containers.length <= 1">
|
|
||||||
<button ng-if="!envVar.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeEnvironmentVariable(envVar)">
|
|
||||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<button ng-if="envVar.NeedsDeletion" class="btn btn-sm btn-primary" type="button" ng-click="ctrl.restoreEnvironmentVariable(envVar)">
|
|
||||||
<i class="fa fa-trash-restore" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -272,18 +284,15 @@
|
||||||
<!-- has-override -->
|
<!-- has-override -->
|
||||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;" ng-if="config.Overriden">
|
<div class="col-sm-12 form-inline" style="margin-top: 10px;" ng-if="config.Overriden">
|
||||||
<div ng-repeat="(keyIndex, overridenKey) in config.OverridenKeys" style="margin-top: 2px;">
|
<div ng-repeat="(keyIndex, overridenKey) in config.OverridenKeys" style="margin-top: 2px;">
|
||||||
<div class="col-md-1 col-sm-2" style="margin-left: 3px;" style="vertical-align: top;"></div>
|
<div style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-3 input-group-sm" style="vertical-align: top;">
|
<div class="col-sm-1 input-group input-group-sm" style="margin-left: 3px;"></div>
|
||||||
|
<div class="col-sm-3 input-group input-group-sm">
|
||||||
<span class="input-group-addon">configuration key</span>
|
<span class="input-group-addon">configuration key</span>
|
||||||
<input type="text" class="form-control" ng-value="overridenKey.Key" disabled />
|
<input type="text" class="form-control" ng-value="overridenKey.Key" disabled />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="col-sm-3 input-group input-group-sm" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||||
class="col-sm-3 input-group input-group-sm"
|
<div class="col-sm-12 input-group input-group-sm">
|
||||||
style="vertical-align: top;"
|
|
||||||
ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM"
|
|
||||||
>
|
|
||||||
<div class="input-group col-sm-12 input-group-sm">
|
|
||||||
<span class="input-group-addon">path on disk</span>
|
<span class="input-group-addon">path on disk</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -296,6 +305,27 @@
|
||||||
ng-change="ctrl.onChangeConfigurationPath()"
|
ng-change="ctrl.onChangeConfigurationPath()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group col-sm-4 btn-group btn-group-sm">
|
||||||
|
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT">
|
||||||
|
<i class="fa fa-list" aria-hidden="true"></i> Environment
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||||
|
<i class="fa fa-file" aria-hidden="true"></i> Filesystem
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ng-show="
|
||||||
|
kubernetesApplicationCreationForm['overriden_key_path_' + index + '_' + keyIndex].$invalid ||
|
||||||
|
ctrl.state.duplicates.configurationPaths.refs[index + '_' + keyIndex] !== undefined
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="col-sm-1 input-group input-group-sm" style="margin-left: 3px;"></div>
|
||||||
|
<div class="col-sm-3 input-group input-group-sm"></div>
|
||||||
|
<div class="col-sm-3 input-group input-group-sm" ng-if="overridenKey.Type === ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
||||||
<div
|
<div
|
||||||
class="small text-warning"
|
class="small text-warning"
|
||||||
style="margin-top: 5px;"
|
style="margin-top: 5px;"
|
||||||
|
@ -312,14 +342,7 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-4 input-group input-group-sm"></div>
|
||||||
<div class="input-group col-sm-3 btn-group btn-group-sm" style="vertical-align: top;">
|
|
||||||
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT">
|
|
||||||
<i class="fa fa-list" aria-hidden="true"></i> Environment
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-primary" ng-model="overridenKey.Type" uib-btn-radio="ctrl.ApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM">
|
|
||||||
<i class="fa fa-file" aria-hidden="true"></i> Filesystem
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -342,12 +365,7 @@
|
||||||
<div class="form-group" ng-if="ctrl.storageClassAvailable()">
|
<div class="form-group" ng-if="ctrl.storageClassAvailable()">
|
||||||
<div class="col-sm-12" style="margin-top: 5px;">
|
<div class="col-sm-12" style="margin-top: 5px;">
|
||||||
<label class="control-label text-left">Persisted folders</label>
|
<label class="control-label text-left">Persisted folders</label>
|
||||||
<span
|
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPersistedFolder()" ng-if="ctrl.isAddPersistentFolderButtonShowed()">
|
||||||
class="label label-default interactive"
|
|
||||||
style="margin-left: 10px;"
|
|
||||||
ng-click="ctrl.addPersistedFolder()"
|
|
||||||
ng-if="!ctrl.isEditAndStatefulSet() && ctrl.formValues.Containers.length <= 1"
|
|
||||||
>
|
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add persisted folder
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> add persisted folder
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -383,7 +401,7 @@
|
||||||
ng-model="persistedFolder.UseNewVolume"
|
ng-model="persistedFolder.UseNewVolume"
|
||||||
uib-btn-radio="true"
|
uib-btn-radio="true"
|
||||||
ng-change="ctrl.useNewVolume($index)"
|
ng-change="ctrl.useNewVolume($index)"
|
||||||
ng-disabled="ctrl.isEditAndExistingPersistedFolder($index)"
|
ng-disabled="ctrl.isNewVolumeButtonDisabled($index)"
|
||||||
>New volume</label
|
>New volume</label
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
@ -391,7 +409,7 @@
|
||||||
ng-model="persistedFolder.UseNewVolume"
|
ng-model="persistedFolder.UseNewVolume"
|
||||||
uib-btn-radio="false"
|
uib-btn-radio="false"
|
||||||
ng-change="ctrl.useExistingVolume($index)"
|
ng-change="ctrl.useExistingVolume($index)"
|
||||||
ng-disabled="ctrl.availableVolumes.length === 0 || ctrl.application.ApplicationType === ctrl.ApplicationTypes.STATEFULSET"
|
ng-disabled="ctrl.isExistingVolumeButtonDisabled()"
|
||||||
>Existing volume</label
|
>Existing volume</label
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
@ -419,12 +437,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="input-group col-sm-2 input-group-sm" ng-class="{ striked: persistedFolder.NeedsDeletion }" ng-if="persistedFolder.UseNewVolume">
|
||||||
class="input-group col-sm-2 input-group-sm"
|
|
||||||
ng-class="{ striked: persistedFolder.NeedsDeletion }"
|
|
||||||
style="vertical-align: top;"
|
|
||||||
ng-if="persistedFolder.UseNewVolume"
|
|
||||||
>
|
|
||||||
<span class="input-group-addon">storage</span>
|
<span class="input-group-addon">storage</span>
|
||||||
<select
|
<select
|
||||||
ng-if="ctrl.hasMultipleStorageClassesAvailable()"
|
ng-if="ctrl.hasMultipleStorageClassesAvailable()"
|
||||||
|
@ -452,7 +465,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group col-sm-1 input-group-sm">
|
<div class="input-group col-sm-1 input-group-sm">
|
||||||
<div style="vertical-align: top;" ng-if="!ctrl.isEditAndStatefulSet() && !ctrl.state.useExistingVolume[$index] && ctrl.formValues.Containers.length <= 1">
|
<div ng-if="!ctrl.isEditAndStatefulSet() && !ctrl.state.useExistingVolume[$index] && ctrl.formValues.Containers.length <= 1">
|
||||||
<button ng-if="!persistedFolder.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePersistedFolder($index)">
|
<button ng-if="!persistedFolder.NeedsDeletion" class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePersistedFolder($index)">
|
||||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -489,7 +502,7 @@
|
||||||
|
|
||||||
<div class="input-group col-sm-2 input-group-sm"></div>
|
<div class="input-group col-sm-2 input-group-sm"></div>
|
||||||
|
|
||||||
<div class="input-group col-sm-2 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<div class="small text-warning" style="margin-top: 5px;" ng-show="kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$invalid">
|
<div class="small text-warning" style="margin-top: 5px;" ng-show="kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$invalid">
|
||||||
<ng-messages for="kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$error">
|
<ng-messages for="kubernetesApplicationCreationForm['persisted_folder_size_' + $index].$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Size is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Size is required.</p>
|
||||||
|
@ -509,8 +522,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group col-sm-3 input-group-sm"> </div>
|
|
||||||
|
|
||||||
<div class="input-group col-sm-1 input-group-sm"> </div>
|
<div class="input-group col-sm-1 input-group-sm"> </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -569,7 +580,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ng-if="
|
ng-if="
|
||||||
(!ctrl.state.isEdit && !ctrl.state.PersistedFoldersUseExistingVolumes) ||
|
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
|
||||||
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED)
|
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
@ -590,7 +601,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="color: #767676;"
|
style="color: #767676;"
|
||||||
ng-if="(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.PersistedFoldersUseExistingVolumes"
|
ng-if="(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.persistedFoldersUseExistingVolumes"
|
||||||
>
|
>
|
||||||
<input type="radio" id="data_access_isolated" disabled />
|
<input type="radio" id="data_access_isolated" disabled />
|
||||||
<label
|
<label
|
||||||
|
@ -679,7 +690,8 @@
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="kubernetesApplicationCreationForm.memory_limit.$error">
|
<div ng-messages="kubernetesApplicationCreationForm.memory_limit.$error">
|
||||||
<p
|
<p
|
||||||
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value must be between {{ ctrl.state.sliders.memory.min }} and {{ ctrl.state.sliders.memory.max }}
|
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value must be between {{ ctrl.state.sliders.memory.min }} and
|
||||||
|
{{ ctrl.state.sliders.memory.max }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -786,7 +798,7 @@
|
||||||
style="margin-left: 20px;"
|
style="margin-left: 20px;"
|
||||||
ng-model="ctrl.formValues.ReplicaCount"
|
ng-model="ctrl.formValues.ReplicaCount"
|
||||||
ng-disabled="!ctrl.supportScalableReplicaDeployment()"
|
ng-disabled="!ctrl.supportScalableReplicaDeployment()"
|
||||||
ng-change="ctrl.onChangeVolumeRequestedSize()"
|
ng-change="ctrl.enforceReplicaCountMinimum()"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -807,7 +819,8 @@
|
||||||
>
|
>
|
||||||
<div class="col-sm-12 small text-muted">
|
<div class="col-sm-12 small text-muted">
|
||||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
This application will reserve the following resources: <b>{{ ctrl.formValues.CpuLimit * ctrl.formValues.ReplicaCount | kubernetesApplicationCPUValue }} CPU</b> and
|
This application will reserve the following resources:
|
||||||
|
<b>{{ ctrl.formValues.CpuLimit * ctrl.formValues.ReplicaCount | kubernetesApplicationCPUValue }} CPU</b> and
|
||||||
<b>{{ ctrl.formValues.MemoryLimit * ctrl.formValues.ReplicaCount }} MB</b> of memory.
|
<b>{{ ctrl.formValues.MemoryLimit * ctrl.formValues.ReplicaCount }} MB</b> of memory.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import filesizeParser from 'filesize-parser';
|
import filesizeParser from 'filesize-parser';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import {
|
||||||
KubernetesApplicationPersistedFolderFormValue,
|
KubernetesApplicationPersistedFolderFormValue,
|
||||||
KubernetesApplicationPublishedPortFormValue,
|
KubernetesApplicationPublishedPortFormValue,
|
||||||
KubernetesApplicationPlacementFormValue,
|
KubernetesApplicationPlacementFormValue,
|
||||||
KubernetesFormValueDuplicate,
|
KubernetesFormValidationReferences,
|
||||||
} from 'Kubernetes/models/application/formValues';
|
} from 'Kubernetes/models/application/formValues';
|
||||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||||
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
import KubernetesApplicationConverter from 'Kubernetes/converters/application';
|
||||||
|
@ -75,15 +75,8 @@ class KubernetesCreateApplicationController {
|
||||||
this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes;
|
this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes;
|
||||||
this.ServiceTypes = KubernetesServiceTypes;
|
this.ServiceTypes = KubernetesServiceTypes;
|
||||||
|
|
||||||
this.onInit = this.onInit.bind(this);
|
|
||||||
this.updateApplicationAsync = this.updateApplicationAsync.bind(this);
|
this.updateApplicationAsync = this.updateApplicationAsync.bind(this);
|
||||||
this.deployApplicationAsync = this.deployApplicationAsync.bind(this);
|
this.deployApplicationAsync = this.deployApplicationAsync.bind(this);
|
||||||
this.updateSlidersAsync = this.updateSlidersAsync.bind(this);
|
|
||||||
this.refreshStacksAsync = this.refreshStacksAsync.bind(this);
|
|
||||||
this.refreshConfigurationsAsync = this.refreshConfigurationsAsync.bind(this);
|
|
||||||
this.refreshApplicationsAsync = this.refreshApplicationsAsync.bind(this);
|
|
||||||
this.refreshNamespaceDataAsync = this.refreshNamespaceDataAsync.bind(this);
|
|
||||||
this.getApplicationAsync = this.getApplicationAsync.bind(this);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -92,7 +85,7 @@ class KubernetesCreateApplicationController {
|
||||||
this.state.alreadyExists = (this.state.isEdit && existingApplication && this.application.Id !== existingApplication.Id) || (!this.state.isEdit && existingApplication);
|
this.state.alreadyExists = (this.state.isEdit && existingApplication && this.application.Id !== existingApplication.Id) || (!this.state.isEdit && existingApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region AUTO SCLAER UI MANAGEMENT */
|
/* #region AUTO SCALER UI MANAGEMENT */
|
||||||
unselectAutoScaler() {
|
unselectAutoScaler() {
|
||||||
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL) {
|
if (this.formValues.DeploymentType === this.ApplicationDeploymentTypes.GLOBAL) {
|
||||||
this.formValues.AutoScaler.IsUsed = false;
|
this.formValues.AutoScaler.IsUsed = false;
|
||||||
|
@ -156,7 +149,7 @@ class KubernetesCreateApplicationController {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.state.duplicates.configurationPaths.hasDuplicates = Object.keys(this.state.duplicates.configurationPaths.refs).length > 0;
|
this.state.duplicates.configurationPaths.hasRefs = Object.keys(this.state.duplicates.configurationPaths.refs).length > 0;
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -184,7 +177,7 @@ class KubernetesCreateApplicationController {
|
||||||
|
|
||||||
onChangeEnvironmentName() {
|
onChangeEnvironmentName() {
|
||||||
this.state.duplicates.environmentVariables.refs = KubernetesFormValidationHelper.getDuplicates(_.map(this.formValues.EnvironmentVariables, 'Name'));
|
this.state.duplicates.environmentVariables.refs = KubernetesFormValidationHelper.getDuplicates(_.map(this.formValues.EnvironmentVariables, 'Name'));
|
||||||
this.state.duplicates.environmentVariables.hasDuplicates = Object.keys(this.state.duplicates.environmentVariables.refs).length > 0;
|
this.state.duplicates.environmentVariables.hasRefs = Object.keys(this.state.duplicates.environmentVariables.refs).length > 0;
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -195,12 +188,14 @@ class KubernetesCreateApplicationController {
|
||||||
storageClass = this.storageClasses[0];
|
storageClass = this.storageClasses[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formValues.PersistedFolders.push(new KubernetesApplicationPersistedFolderFormValue(storageClass));
|
const newPf = new KubernetesApplicationPersistedFolderFormValue(storageClass);
|
||||||
|
this.formValues.PersistedFolders.push(newPf);
|
||||||
this.resetDeploymentType();
|
this.resetDeploymentType();
|
||||||
}
|
}
|
||||||
|
|
||||||
restorePersistedFolder(index) {
|
restorePersistedFolder(index) {
|
||||||
this.formValues.PersistedFolders[index].NeedsDeletion = false;
|
this.formValues.PersistedFolders[index].NeedsDeletion = false;
|
||||||
|
this.validatePersistedFolders();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPersistedFolders() {
|
resetPersistedFolders() {
|
||||||
|
@ -208,6 +203,7 @@ class KubernetesCreateApplicationController {
|
||||||
persistedFolder.ExistingVolume = null;
|
persistedFolder.ExistingVolume = null;
|
||||||
persistedFolder.UseNewVolume = true;
|
persistedFolder.UseNewVolume = true;
|
||||||
});
|
});
|
||||||
|
this.validatePersistedFolders();
|
||||||
}
|
}
|
||||||
|
|
||||||
removePersistedFolder(index) {
|
removePersistedFolder(index) {
|
||||||
|
@ -216,6 +212,29 @@ class KubernetesCreateApplicationController {
|
||||||
} else {
|
} else {
|
||||||
this.formValues.PersistedFolders.splice(index, 1);
|
this.formValues.PersistedFolders.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
this.validatePersistedFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.validatePersistedFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
useExistingVolume(index) {
|
||||||
|
this.formValues.PersistedFolders[index].UseNewVolume = false;
|
||||||
|
this.state.persistedFoldersUseExistingVolumes = _.find(this.formValues.PersistedFolders, { UseNewVolume: false }) ? true : false;
|
||||||
|
if (this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED) {
|
||||||
|
this.formValues.DataAccessPolicy = this.ApplicationDataAccessPolicies.SHARED;
|
||||||
|
this.resetDeploymentType();
|
||||||
|
}
|
||||||
|
this.validatePersistedFolders();
|
||||||
|
}
|
||||||
|
/* #endregion */
|
||||||
|
|
||||||
|
/* #region PERSISTENT FOLDERS ON CHANGE VALIDATION */
|
||||||
|
validatePersistedFolders() {
|
||||||
this.onChangePersistedFolderPath();
|
this.onChangePersistedFolderPath();
|
||||||
this.onChangeExistingVolumeSelection();
|
this.onChangeExistingVolumeSelection();
|
||||||
}
|
}
|
||||||
|
@ -229,31 +248,19 @@ class KubernetesCreateApplicationController {
|
||||||
return persistedFolder.ContainerPath;
|
return persistedFolder.ContainerPath;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.state.duplicates.persistedFolders.hasDuplicates = Object.keys(this.state.duplicates.persistedFolders.refs).length > 0;
|
this.state.duplicates.persistedFolders.hasRefs = Object.keys(this.state.duplicates.persistedFolders.refs).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeExistingVolumeSelection() {
|
onChangeExistingVolumeSelection() {
|
||||||
this.state.duplicates.existingVolumes.refs = KubernetesFormValidationHelper.getDuplicates(
|
this.state.duplicates.existingVolumes.refs = KubernetesFormValidationHelper.getDuplicates(
|
||||||
_.map(this.formValues.PersistedFolders, (persistedFolder) => {
|
_.map(this.formValues.PersistedFolders, (persistedFolder) => {
|
||||||
|
if (persistedFolder.NeedsDeletion) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return persistedFolder.ExistingVolume ? persistedFolder.ExistingVolume.PersistentVolumeClaim.Name : '';
|
return persistedFolder.ExistingVolume ? persistedFolder.ExistingVolume.PersistentVolumeClaim.Name : '';
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.state.duplicates.existingVolumes.hasDuplicates = Object.keys(this.state.duplicates.existingVolumes.refs).length > 0;
|
this.state.duplicates.existingVolumes.hasRefs = Object.keys(this.state.duplicates.existingVolumes.refs).length > 0;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
useExistingVolume(index) {
|
|
||||||
this.formValues.PersistedFolders[index].UseNewVolume = false;
|
|
||||||
this.state.PersistedFoldersUseExistingVolumes = _.find(this.formValues.PersistedFolders, { UseNewVolume: false }) ? true : false;
|
|
||||||
if (this.formValues.DataAccessPolicy === this.ApplicationDataAccessPolicies.ISOLATED) {
|
|
||||||
this.formValues.DataAccessPolicy = this.ApplicationDataAccessPolicies.SHARED;
|
|
||||||
this.resetDeploymentType();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -296,7 +303,7 @@ class KubernetesCreateApplicationController {
|
||||||
const source = _.map(this.formValues.Placements, (p) => (p.NeedsDeletion ? undefined : p.Label.Key));
|
const source = _.map(this.formValues.Placements, (p) => (p.NeedsDeletion ? undefined : p.Label.Key));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
@ -351,10 +358,10 @@ class KubernetesCreateApplicationController {
|
||||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.ContainerPort + p.Protocol));
|
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.ContainerPort + p.Protocol));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
} else {
|
} else {
|
||||||
state.refs = {};
|
state.refs = {};
|
||||||
state.hasDuplicates = false;
|
state.hasRefs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,10 +371,10 @@ class KubernetesCreateApplicationController {
|
||||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.NodePort));
|
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.NodePort));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
} else {
|
} else {
|
||||||
state.refs = {};
|
state.refs = {};
|
||||||
state.hasDuplicates = false;
|
state.hasRefs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,9 +389,9 @@ class KubernetesCreateApplicationController {
|
||||||
const state = this.state.duplicates.publishedPorts.ingressRoutes;
|
const state = this.state.duplicates.publishedPorts.ingressRoutes;
|
||||||
|
|
||||||
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.INGRESS) {
|
||||||
const newRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.IsNew && p.IngressRoute ? (p.IngressHost || p.IngressName) + p.IngressRoute : undefined));
|
const newRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.IsNew && p.IngressRoute ? `${p.IngressHost || p.IngressName}${p.IngressRoute}` : undefined));
|
||||||
const toDelRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion && p.IngressRoute ? (p.IngressHost || p.IngressName) + p.IngressRoute : undefined));
|
const toDelRoutes = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion && p.IngressRoute ? `${p.IngressHost || p.IngressName}${p.IngressRoute}` : undefined));
|
||||||
const allRoutes = _.flatMap(this.ingresses, (i) => _.map(i.Paths, (p) => (p.Host || i.Name) + p.Path));
|
const allRoutes = _.flatMap(this.ingresses, (i) => _.map(i.Paths, (p) => `${p.Host || i.Name}${p.Path}`));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(newRoutes);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(newRoutes);
|
||||||
_.forEach(newRoutes, (route, idx) => {
|
_.forEach(newRoutes, (route, idx) => {
|
||||||
if (_.includes(allRoutes, route) && !_.includes(toDelRoutes, route)) {
|
if (_.includes(allRoutes, route) && !_.includes(toDelRoutes, route)) {
|
||||||
|
@ -392,10 +399,10 @@ class KubernetesCreateApplicationController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
} else {
|
} else {
|
||||||
state.refs = {};
|
state.refs = {};
|
||||||
state.hasDuplicates = false;
|
state.hasRefs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,10 +412,10 @@ class KubernetesCreateApplicationController {
|
||||||
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.LoadBalancerPort));
|
const source = _.map(this.formValues.PublishedPorts, (p) => (p.NeedsDeletion ? undefined : p.LoadBalancerPort));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
} else {
|
} else {
|
||||||
state.refs = {};
|
state.refs = {};
|
||||||
state.hasDuplicates = false;
|
state.hasRefs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,14 +435,14 @@ class KubernetesCreateApplicationController {
|
||||||
isValid() {
|
isValid() {
|
||||||
return (
|
return (
|
||||||
!this.state.alreadyExists &&
|
!this.state.alreadyExists &&
|
||||||
!this.state.duplicates.environmentVariables.hasDuplicates &&
|
!this.state.duplicates.environmentVariables.hasRefs &&
|
||||||
!this.state.duplicates.persistedFolders.hasDuplicates &&
|
!this.state.duplicates.persistedFolders.hasRefs &&
|
||||||
!this.state.duplicates.configurationPaths.hasDuplicates &&
|
!this.state.duplicates.configurationPaths.hasRefs &&
|
||||||
!this.state.duplicates.existingVolumes.hasDuplicates &&
|
!this.state.duplicates.existingVolumes.hasRefs &&
|
||||||
!this.state.duplicates.publishedPorts.containerPorts.hasDuplicates &&
|
!this.state.duplicates.publishedPorts.containerPorts.hasRefs &&
|
||||||
!this.state.duplicates.publishedPorts.nodePorts.hasDuplicates &&
|
!this.state.duplicates.publishedPorts.nodePorts.hasRefs &&
|
||||||
!this.state.duplicates.publishedPorts.ingressRoutes.hasDuplicates &&
|
!this.state.duplicates.publishedPorts.ingressRoutes.hasRefs &&
|
||||||
!this.state.duplicates.publishedPorts.loadBalancerPorts.hasDuplicates
|
!this.state.duplicates.publishedPorts.loadBalancerPorts.hasRefs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,12 +508,20 @@ class KubernetesCreateApplicationController {
|
||||||
|
|
||||||
if (folder.StorageClass && _.isEqual(folder.StorageClass.AccessModes, ['RWO'])) {
|
if (folder.StorageClass && _.isEqual(folder.StorageClass.AccessModes, ['RWO'])) {
|
||||||
storageOptions.push(folder.StorageClass.Name);
|
storageOptions.push(folder.StorageClass.Name);
|
||||||
|
} else {
|
||||||
|
storageOptions.push('<no storage option available>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.uniq(storageOptions).join(', ');
|
return _.uniq(storageOptions).join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enforceReplicaCountMinimum() {
|
||||||
|
if (this.formValues.ReplicaCount === null) {
|
||||||
|
this.formValues.ReplicaCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resourceQuotaCapacityExceeded() {
|
resourceQuotaCapacityExceeded() {
|
||||||
return !this.state.sliders.memory.max || !this.state.sliders.cpu.max;
|
return !this.state.sliders.memory.max || !this.state.sliders.cpu.max;
|
||||||
}
|
}
|
||||||
|
@ -562,9 +577,29 @@ class KubernetesCreateApplicationController {
|
||||||
return !this.editChanges.length;
|
return !this.editChanges.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* #region PERSISTED FOLDERS */
|
||||||
|
/* #region BUTTONS STATES */
|
||||||
|
isAddPersistentFolderButtonShowed() {
|
||||||
|
return !this.isEditAndStatefulSet() && this.formValues.Containers.length <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNewVolumeButtonDisabled(index) {
|
||||||
|
return this.isEditAndExistingPersistedFolder(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
isExistingVolumeButtonDisabled() {
|
||||||
|
return !this.hasAvailableVolumes() || (this.isEdit && this.application.ApplicationType === this.ApplicationTypes.STATEFULSET);
|
||||||
|
}
|
||||||
|
/* #endregion */
|
||||||
|
|
||||||
|
hasAvailableVolumes() {
|
||||||
|
return this.availableVolumes.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
isEditAndExistingPersistedFolder(index) {
|
isEditAndExistingPersistedFolder(index) {
|
||||||
return this.state.isEdit && this.formValues.PersistedFolders[index].PersistentVolumeClaimName;
|
return this.state.isEdit && this.formValues.PersistedFolders[index].PersistentVolumeClaimName;
|
||||||
}
|
}
|
||||||
|
/* #endregion */
|
||||||
|
|
||||||
isEditAndNotNewPublishedPort(index) {
|
isEditAndNotNewPublishedPort(index) {
|
||||||
return this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew;
|
return this.state.isEdit && !this.formValues.PublishedPorts[index].IsNew;
|
||||||
|
@ -639,16 +674,17 @@ class KubernetesCreateApplicationController {
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region DATA AUTO REFRESH */
|
/* #region DATA AUTO REFRESH */
|
||||||
async updateSlidersAsync() {
|
updateSliders() {
|
||||||
try {
|
this.state.resourcePoolHasQuota = false;
|
||||||
|
|
||||||
const quota = this.formValues.ResourcePool.Quota;
|
const quota = this.formValues.ResourcePool.Quota;
|
||||||
let minCpu,
|
let minCpu,
|
||||||
maxCpu,
|
maxCpu,
|
||||||
minMemory,
|
minMemory,
|
||||||
maxMemory = 0;
|
maxMemory = 0;
|
||||||
if (quota) {
|
if (quota) {
|
||||||
this.state.resourcePoolHasQuota = true;
|
|
||||||
if (quota.CpuLimit) {
|
if (quota.CpuLimit) {
|
||||||
|
this.state.resourcePoolHasQuota = true;
|
||||||
minCpu = KubernetesApplicationQuotaDefaults.CpuLimit;
|
minCpu = KubernetesApplicationQuotaDefaults.CpuLimit;
|
||||||
maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
|
maxCpu = quota.CpuLimit - quota.CpuLimitUsed;
|
||||||
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
|
if (this.state.isEdit && this.savedFormValues.CpuLimit) {
|
||||||
|
@ -659,6 +695,7 @@ class KubernetesCreateApplicationController {
|
||||||
maxCpu = this.state.nodes.cpu;
|
maxCpu = this.state.nodes.cpu;
|
||||||
}
|
}
|
||||||
if (quota.MemoryLimit) {
|
if (quota.MemoryLimit) {
|
||||||
|
this.state.resourcePoolHasQuota = true;
|
||||||
minMemory = KubernetesApplicationQuotaDefaults.MemoryLimit;
|
minMemory = KubernetesApplicationQuotaDefaults.MemoryLimit;
|
||||||
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
|
maxMemory = quota.MemoryLimit - quota.MemoryLimitUsed;
|
||||||
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
|
if (this.state.isEdit && this.savedFormValues.MemoryLimit) {
|
||||||
|
@ -669,7 +706,6 @@ class KubernetesCreateApplicationController {
|
||||||
maxMemory = this.state.nodes.memory;
|
maxMemory = this.state.nodes.memory;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.state.resourcePoolHasQuota = false;
|
|
||||||
minCpu = 0;
|
minCpu = 0;
|
||||||
maxCpu = this.state.nodes.cpu;
|
maxCpu = this.state.nodes.cpu;
|
||||||
minMemory = 0;
|
minMemory = 0;
|
||||||
|
@ -683,70 +719,72 @@ class KubernetesCreateApplicationController {
|
||||||
this.formValues.CpuLimit = minCpu;
|
this.formValues.CpuLimit = minCpu;
|
||||||
this.formValues.MemoryLimit = minMemory;
|
this.formValues.MemoryLimit = minMemory;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
this.Notifications.error('Failure', err, 'Unable to update resources selector');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSliders() {
|
refreshStacks(namespace) {
|
||||||
return this.$async(this.updateSlidersAsync);
|
return this.$async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
async refreshStacksAsync(namespace) {
|
|
||||||
try {
|
try {
|
||||||
this.stacks = await this.KubernetesStackService.get(namespace);
|
this.stacks = await this.KubernetesStackService.get(namespace);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve stacks');
|
this.Notifications.error('Failure', err, 'Unable to retrieve stacks');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshStacks(namespace) {
|
refreshConfigurations(namespace) {
|
||||||
return this.$async(this.refreshStacksAsync, namespace);
|
return this.$async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
async refreshConfigurationsAsync(namespace) {
|
|
||||||
try {
|
try {
|
||||||
this.configurations = await this.KubernetesConfigurationService.get(namespace);
|
this.configurations = await this.KubernetesConfigurationService.get(namespace);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve configurations');
|
this.Notifications.error('Failure', err, 'Unable to retrieve configurations');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshConfigurations(namespace) {
|
refreshApplications(namespace) {
|
||||||
return this.$async(this.refreshConfigurationsAsync, namespace);
|
return this.$async(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
async refreshApplicationsAsync(namespace) {
|
|
||||||
try {
|
try {
|
||||||
this.applications = await this.KubernetesApplicationService.get(namespace);
|
this.applications = await this.KubernetesApplicationService.get(namespace);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve applications');
|
this.Notifications.error('Failure', err, 'Unable to retrieve applications');
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
refreshApplications(namespace) {
|
|
||||||
return this.$async(this.refreshApplicationsAsync, namespace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshVolumes(namespace) {
|
refreshVolumes(namespace) {
|
||||||
|
return this.$async(async () => {
|
||||||
|
try {
|
||||||
|
const volumes = await this.KubernetesVolumeService.get(namespace);
|
||||||
|
_.forEach(volumes, (volume) => {
|
||||||
|
volume.Applications = KubernetesVolumeHelper.getUsingApplications(volume, this.applications);
|
||||||
|
});
|
||||||
|
this.volumes = volumes;
|
||||||
const filteredVolumes = _.filter(this.volumes, (volume) => {
|
const filteredVolumes = _.filter(this.volumes, (volume) => {
|
||||||
const isSameNamespace = volume.ResourcePool.Namespace.Name === namespace;
|
|
||||||
const isUnused = !KubernetesVolumeHelper.isUsed(volume);
|
const isUnused = !KubernetesVolumeHelper.isUsed(volume);
|
||||||
const isRWX = volume.PersistentVolumeClaim.StorageClass && _.find(volume.PersistentVolumeClaim.StorageClass.AccessModes, (am) => am === 'RWX');
|
const isRWX = volume.PersistentVolumeClaim.StorageClass && _.includes(volume.PersistentVolumeClaim.StorageClass.AccessModes, 'RWX');
|
||||||
return isSameNamespace && (isUnused || isRWX);
|
return isUnused || isRWX;
|
||||||
});
|
});
|
||||||
this.availableVolumes = filteredVolumes;
|
this.availableVolumes = filteredVolumes;
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshIngresses(namespace) {
|
refreshIngresses(namespace) {
|
||||||
this.filteredIngresses = _.filter(this.ingresses, { Namespace: namespace });
|
this.filteredIngresses = _.filter(this.ingresses, { Namespace: namespace });
|
||||||
if (!this.publishViaIngressEnabled()) {
|
if (!this.publishViaIngressEnabled()) {
|
||||||
this.formValues.PublishingType = KubernetesApplicationPublishingTypes.INTERNAL;
|
if (this.savedFormValues) {
|
||||||
|
this.formValues.PublishingType = this.savedFormValues.PublishingType;
|
||||||
|
} else {
|
||||||
|
this.formValues.PublishingType = this.ApplicationPublishingTypes.INTERNAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.formValues.OriginalIngresses = this.filteredIngresses;
|
this.formValues.OriginalIngresses = this.filteredIngresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshNamespaceDataAsync(namespace) {
|
refreshNamespaceData(namespace) {
|
||||||
|
return this.$async(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.refreshStacks(namespace),
|
this.refreshStacks(namespace),
|
||||||
this.refreshConfigurations(namespace),
|
this.refreshConfigurations(namespace),
|
||||||
|
@ -755,10 +793,7 @@ class KubernetesCreateApplicationController {
|
||||||
this.refreshVolumes(namespace),
|
this.refreshVolumes(namespace),
|
||||||
]);
|
]);
|
||||||
this.onChangeName();
|
this.onChangeName();
|
||||||
}
|
});
|
||||||
|
|
||||||
refreshNamespaceData(namespace) {
|
|
||||||
return this.$async(this.refreshNamespaceDataAsync, namespace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFormValues() {
|
resetFormValues() {
|
||||||
|
@ -768,10 +803,12 @@ class KubernetesCreateApplicationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
onResourcePoolSelectionChange() {
|
onResourcePoolSelectionChange() {
|
||||||
|
return this.$async(async () => {
|
||||||
const namespace = this.formValues.ResourcePool.Namespace.Name;
|
const namespace = this.formValues.ResourcePool.Namespace.Name;
|
||||||
this.updateSliders();
|
this.updateSliders();
|
||||||
this.refreshNamespaceData(namespace);
|
await this.refreshNamespaceData(namespace);
|
||||||
this.resetFormValues();
|
this.resetFormValues();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -818,7 +855,8 @@ class KubernetesCreateApplicationController {
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region APPLICATION - used on edit context only */
|
/* #region APPLICATION - used on edit context only */
|
||||||
async getApplicationAsync() {
|
getApplication() {
|
||||||
|
return this.$async(async () => {
|
||||||
try {
|
try {
|
||||||
const namespace = this.state.params.namespace;
|
const namespace = this.state.params.namespace;
|
||||||
[this.application, this.persistentVolumeClaims] = await Promise.all([
|
[this.application, this.persistentVolumeClaims] = await Promise.all([
|
||||||
|
@ -828,15 +866,13 @@ class KubernetesCreateApplicationController {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve application details');
|
this.Notifications.error('Failure', err, 'Unable to retrieve application details');
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
getApplication() {
|
|
||||||
return this.$async(this.getApplicationAsync);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region ON INIT */
|
/* #region ON INIT */
|
||||||
async onInit() {
|
$onInit() {
|
||||||
|
return this.$async(async () => {
|
||||||
try {
|
try {
|
||||||
this.state = {
|
this.state = {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
@ -861,31 +897,31 @@ class KubernetesCreateApplicationController {
|
||||||
availableSizeUnits: ['MB', 'GB', 'TB'],
|
availableSizeUnits: ['MB', 'GB', 'TB'],
|
||||||
alreadyExists: false,
|
alreadyExists: false,
|
||||||
duplicates: {
|
duplicates: {
|
||||||
environmentVariables: new KubernetesFormValueDuplicate(),
|
environmentVariables: new KubernetesFormValidationReferences(),
|
||||||
persistedFolders: new KubernetesFormValueDuplicate(),
|
persistedFolders: new KubernetesFormValidationReferences(),
|
||||||
configurationPaths: new KubernetesFormValueDuplicate(),
|
configurationPaths: new KubernetesFormValidationReferences(),
|
||||||
existingVolumes: new KubernetesFormValueDuplicate(),
|
existingVolumes: new KubernetesFormValidationReferences(),
|
||||||
publishedPorts: {
|
publishedPorts: {
|
||||||
containerPorts: new KubernetesFormValueDuplicate(),
|
containerPorts: new KubernetesFormValidationReferences(),
|
||||||
nodePorts: new KubernetesFormValueDuplicate(),
|
nodePorts: new KubernetesFormValidationReferences(),
|
||||||
ingressRoutes: new KubernetesFormValueDuplicate(),
|
ingressRoutes: new KubernetesFormValidationReferences(),
|
||||||
loadBalancerPorts: new KubernetesFormValueDuplicate(),
|
loadBalancerPorts: new KubernetesFormValidationReferences(),
|
||||||
},
|
},
|
||||||
placements: new KubernetesFormValueDuplicate(),
|
placements: new KubernetesFormValidationReferences(),
|
||||||
},
|
},
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
params: {
|
params: {
|
||||||
namespace: this.$transition$.params().namespace,
|
namespace: this.$transition$.params().namespace,
|
||||||
name: this.$transition$.params().name,
|
name: this.$transition$.params().name,
|
||||||
},
|
},
|
||||||
PersistedFoldersUseExistingVolumes: false,
|
persistedFoldersUseExistingVolumes: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isAdmin = this.Authentication.isAdmin();
|
this.isAdmin = this.Authentication.isAdmin();
|
||||||
|
|
||||||
this.editChanges = [];
|
this.editChanges = [];
|
||||||
|
|
||||||
if (this.$transition$.params().namespace && this.$transition$.params().name) {
|
if (this.state.params.namespace && this.state.params.name) {
|
||||||
this.state.isEdit = true;
|
this.state.isEdit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -906,17 +942,8 @@ class KubernetesCreateApplicationController {
|
||||||
|
|
||||||
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
this.resourcePools = _.filter(resourcePools, (resourcePool) => !this.KubernetesNamespaceHelper.isSystemNamespace(resourcePool.Namespace.Name));
|
||||||
this.formValues.ResourcePool = this.resourcePools[0];
|
this.formValues.ResourcePool = this.resourcePools[0];
|
||||||
|
if (!this.formValues.ResourcePool) {
|
||||||
// TODO: refactor @Max
|
return;
|
||||||
// Don't pull all volumes and applications across all namespaces
|
|
||||||
// Use refreshNamespaceData flow (triggered on Init + on Namespace change)
|
|
||||||
// and query only accross the selected namespace
|
|
||||||
if (this.storageClassAvailable()) {
|
|
||||||
const [applications, volumes] = await Promise.all([this.KubernetesApplicationService.get(), this.KubernetesVolumeService.get()]);
|
|
||||||
this.volumes = volumes;
|
|
||||||
_.forEach(this.volumes, (volume) => {
|
|
||||||
volume.Applications = KubernetesVolumeHelper.getUsingApplications(volume, applications);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_.forEach(nodes, (item) => {
|
_.forEach(nodes, (item) => {
|
||||||
|
@ -943,29 +970,28 @@ class KubernetesCreateApplicationController {
|
||||||
|
|
||||||
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
|
if (this.application.ApplicationType !== KubernetesApplicationTypes.STATEFULSET) {
|
||||||
_.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
|
_.forEach(this.formValues.PersistedFolders, (persistedFolder) => {
|
||||||
const volume = _.find(this.availableVolumes, (vol) => vol.PersistentVolumeClaim.Name === persistedFolder.PersistentVolumeClaimName);
|
const volume = _.find(this.availableVolumes, ['PersistentVolumeClaim.Name', persistedFolder.PersistentVolumeClaimName]);
|
||||||
if (volume) {
|
if (volume) {
|
||||||
persistedFolder.UseNewVolume = false;
|
persistedFolder.UseNewVolume = false;
|
||||||
persistedFolder.ExistingVolume = volume;
|
persistedFolder.ExistingVolume = volume;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await this.refreshNamespaceData(namespace);
|
||||||
} else {
|
} else {
|
||||||
this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(null, this.formValues.ReplicaCount);
|
this.formValues.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(null, this.formValues.ReplicaCount);
|
||||||
this.formValues.OriginalIngressClasses = angular.copy(this.ingresses);
|
this.formValues.OriginalIngressClasses = angular.copy(this.ingresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateSliders();
|
this.updateSliders();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||||
} finally {
|
} finally {
|
||||||
this.state.viewReady = true;
|
this.state.viewReady = true;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
return this.$async(this.onInit);
|
|
||||||
}
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||||
|
@ -67,8 +67,8 @@ function computeAffinities(nodes, application) {
|
||||||
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.DOES_NOT_EXIST && !exists) ||
|
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.DOES_NOT_EXIST && !exists) ||
|
||||||
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.IN && isIn) ||
|
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.IN && isIn) ||
|
||||||
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.NOT_IN && !isIn) ||
|
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.NOT_IN && !isIn) ||
|
||||||
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.GREATER_THAN && exists && parseInt(n.Labels[e.key]) > parseInt(e.values[0])) ||
|
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.GREATER_THAN && exists && parseInt(n.Labels[e.key], 10) > parseInt(e.values[0], 10)) ||
|
||||||
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.LOWER_THAN && exists && parseInt(n.Labels[e.key]) < parseInt(e.values[0]))
|
(e.operator === KubernetesPodNodeAffinityNodeSelectorRequirementOperators.LOWER_THAN && exists && parseInt(n.Labels[e.key], 10) < parseInt(e.values[0], 10))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('KubernetesApplicationPlacementsDatatableController', function ($scope, $controller, DatatableService, Authentication) {
|
angular.module('portainer.docker').controller('KubernetesApplicationPlacementsDatatableController', function ($scope, $controller, DatatableService, Authentication) {
|
||||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||||
|
|
|
@ -56,7 +56,6 @@
|
||||||
id="resource-pool-selector"
|
id="resource-pool-selector"
|
||||||
ng-model="ctrl.formValues.ResourcePool"
|
ng-model="ctrl.formValues.ResourcePool"
|
||||||
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
ng-options="resourcePool.Namespace.Name for resourcePool in ctrl.resourcePools"
|
||||||
ng-change="ctrl.onResourcePoolSelectionChange()"
|
|
||||||
></select>
|
></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import { KubernetesStorageClass, KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
import { KubernetesStorageClass, KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||||
import { KubernetesFormValueDuplicate } from 'Kubernetes/models/application/formValues';
|
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
|
||||||
import { KubernetesIngressClass } from 'Kubernetes/ingress/models';
|
import { KubernetesIngressClass } from 'Kubernetes/ingress/models';
|
||||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||||
|
@ -82,7 +82,7 @@ class KubernetesConfigureController {
|
||||||
const source = _.map(this.formValues.IngressClasses, (ic) => (ic.NeedsDeletion ? undefined : ic.Name));
|
const source = _.map(this.formValues.IngressClasses, (ic) => (ic.NeedsDeletion ? undefined : ic.Name));
|
||||||
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeIngressClassName(index) {
|
onChangeIngressClassName(index) {
|
||||||
|
@ -212,7 +212,7 @@ class KubernetesConfigureController {
|
||||||
viewReady: false,
|
viewReady: false,
|
||||||
endpointId: this.$stateParams.id,
|
endpointId: this.$stateParams.id,
|
||||||
duplicates: {
|
duplicates: {
|
||||||
ingressClasses: new KubernetesFormValueDuplicate(),
|
ingressClasses: new KubernetesFormValidationReferences(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceRese
|
||||||
import { KubernetesResourcePoolFormValues, KubernetesResourcePoolIngressClassAnnotationFormValue } from 'Kubernetes/models/resource-pool/formValues';
|
import { KubernetesResourcePoolFormValues, KubernetesResourcePoolIngressClassAnnotationFormValue } from 'Kubernetes/models/resource-pool/formValues';
|
||||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||||
import { KubernetesFormValueDuplicate } from 'Kubernetes/models/application/formValues';
|
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
|
||||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||||
|
|
||||||
class KubernetesCreateResourcePoolController {
|
class KubernetesCreateResourcePoolController {
|
||||||
|
@ -44,7 +44,7 @@ class KubernetesCreateResourcePoolController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region ANNOTATIONS MANAGEMENT */
|
/* #region ANNOTATIONS MANAGEMENT */
|
||||||
|
@ -58,7 +58,7 @@ class KubernetesCreateResourcePoolController {
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
isCreateButtonDisabled() {
|
isCreateButtonDisabled() {
|
||||||
return this.state.actionInProgress || (this.formValues.HasQuota && !this.isQuotaValid()) || this.state.isAlreadyExist || this.state.duplicates.ingressHosts.hasDuplicates;
|
return this.state.actionInProgress || (this.formValues.HasQuota && !this.isQuotaValid()) || this.state.isAlreadyExist || this.state.duplicates.ingressHosts.hasRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeName() {
|
onChangeName() {
|
||||||
|
@ -109,13 +109,10 @@ class KubernetesCreateResourcePoolController {
|
||||||
|
|
||||||
/* #region GET INGRESSES */
|
/* #region GET INGRESSES */
|
||||||
async getIngressesAsync() {
|
async getIngressesAsync() {
|
||||||
this.state.ingressesLoading = true;
|
|
||||||
try {
|
try {
|
||||||
this.allIngresses = await this.KubernetesIngressService.get();
|
this.allIngresses = await this.KubernetesIngressService.get();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve ingresses.');
|
this.Notifications.error('Failure', err, 'Unable to retrieve ingresses.');
|
||||||
} finally {
|
|
||||||
this.state.ingressesLoading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +151,7 @@ class KubernetesCreateResourcePoolController {
|
||||||
isAlreadyExist: false,
|
isAlreadyExist: false,
|
||||||
canUseIngress: endpoint.Kubernetes.Configuration.IngressClasses.length,
|
canUseIngress: endpoint.Kubernetes.Configuration.IngressClasses.length,
|
||||||
duplicates: {
|
duplicates: {
|
||||||
ingressHosts: new KubernetesFormValueDuplicate(),
|
ingressHosts: new KubernetesFormValidationReferences(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,17 @@
|
||||||
<p> <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-right: 2px;"></i> At least a single limit must be set for the quota to be valid. </p>
|
<p> <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-right: 2px;"></i> At least a single limit must be set for the quota to be valid. </p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-if="ctrl.formValues.HasQuota">
|
||||||
|
<kubernetes-resource-reservation
|
||||||
|
ng-if="ctrl.pool.Quota"
|
||||||
|
description="Resource reservation represents the total amount of resource assigned to all the applications deployed inside this resource pool."
|
||||||
|
cpu="ctrl.state.cpuUsed"
|
||||||
|
memory="ctrl.state.memoryUsed"
|
||||||
|
cpu-limit="ctrl.formValues.CpuLimit"
|
||||||
|
memory-limit="ctrl.formValues.MemoryLimit"
|
||||||
|
>
|
||||||
|
</kubernetes-resource-reservation>
|
||||||
|
</div>
|
||||||
<!-- !quotas-switch -->
|
<!-- !quotas-switch -->
|
||||||
<div ng-if="ctrl.formValues.HasQuota && ctrl.isAdmin && ctrl.isEditable">
|
<div ng-if="ctrl.formValues.HasQuota && ctrl.isAdmin && ctrl.isEditable">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
@ -50,7 +61,7 @@
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<slider
|
<slider
|
||||||
model="ctrl.formValues.MemoryLimit"
|
model="ctrl.formValues.MemoryLimit"
|
||||||
floor="ctrl.defaults.MemoryLimit"
|
floor="ctrl.ResourceQuotaDefaults.MemoryLimit"
|
||||||
ceil="ctrl.state.sliderMaxMemory"
|
ceil="ctrl.state.sliderMaxMemory"
|
||||||
step="128"
|
step="128"
|
||||||
ng-if="ctrl.state.sliderMaxMemory"
|
ng-if="ctrl.state.sliderMaxMemory"
|
||||||
|
@ -60,7 +71,7 @@
|
||||||
<input
|
<input
|
||||||
name="memory_limit"
|
name="memory_limit"
|
||||||
type="number"
|
type="number"
|
||||||
min="{{ ctrl.defaults.MemoryLimit }}"
|
min="{{ ctrl.ResourceQuotaDefaults.MemoryLimit }}"
|
||||||
max="{{ ctrl.state.sliderMaxMemory }}"
|
max="{{ ctrl.state.sliderMaxMemory }}"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
ng-model="ctrl.formValues.MemoryLimit"
|
ng-model="ctrl.formValues.MemoryLimit"
|
||||||
|
@ -78,7 +89,7 @@
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="resourcePoolEditForm.pool_name.$error">
|
<div ng-messages="resourcePoolEditForm.pool_name.$error">
|
||||||
<p
|
<p
|
||||||
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value must be between {{ ctrl.defaults.MemoryLimit }} and
|
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Value must be between {{ ctrl.ResourceQuotaDefaults.MemoryLimit }} and
|
||||||
{{ ctrl.state.sliderMaxMemory }}</p
|
{{ ctrl.state.sliderMaxMemory }}</p
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,7 +104,7 @@
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-5">
|
||||||
<slider
|
<slider
|
||||||
model="ctrl.formValues.CpuLimit"
|
model="ctrl.formValues.CpuLimit"
|
||||||
floor="ctrl.defaults.CpuLimit"
|
floor="ctrl.ResourceQuotaDefaults.CpuLimit"
|
||||||
ceil="ctrl.state.sliderMaxCpu"
|
ceil="ctrl.state.sliderMaxCpu"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
precision="2"
|
precision="2"
|
||||||
|
@ -109,17 +120,31 @@
|
||||||
<!-- !cpu-limit-input -->
|
<!-- !cpu-limit-input -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="ctrl.formValues.HasQuota">
|
<!-- #region LOADBALANCERS -->
|
||||||
<kubernetes-resource-reservation
|
<div class="col-sm-12 form-section-title">
|
||||||
ng-if="ctrl.pool.Quota"
|
Load balancers
|
||||||
description="Resource reservation represents the total amount of resource assigned to all the applications deployed inside this resource pool."
|
|
||||||
cpu="ctrl.state.cpuUsed"
|
|
||||||
memory="ctrl.state.memoryUsed"
|
|
||||||
cpu-limit="ctrl.formValues.CpuLimit"
|
|
||||||
memory-limit="ctrl.formValues.MemoryLimit"
|
|
||||||
>
|
|
||||||
</kubernetes-resource-reservation>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<span class="col-sm-12 text-muted small">
|
||||||
|
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
You can set a quota on the amount of external load balancers that can be created inside this resource pool. Set this quota to 0 to effectively disable the use
|
||||||
|
of load balancers in this resource pool.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label class="control-label text-left">
|
||||||
|
Load Balancer quota
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
|
||||||
|
<span class="text-muted small" style="margin-left: 15px;">
|
||||||
|
<i class="fa fa-user" aria-hidden="true"></i>
|
||||||
|
This feature is available in <a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-lbquota" target="_blank"> Portainer Business Edition</a>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- #endregion -->
|
||||||
<div ng-if="ctrl.isAdmin && ctrl.isEditable && ctrl.state.canUseIngress">
|
<div ng-if="ctrl.isAdmin && ctrl.isEditable && ctrl.state.canUseIngress">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Ingresses
|
Ingresses
|
||||||
|
@ -250,33 +275,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- #endregion -->
|
<!-- #endregion -->
|
||||||
</div>
|
</div>
|
||||||
<!-- #region LOAD-BALANCERS -->
|
<!-- #region STORAGES -->
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Load balancers
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<span class="col-sm-12 text-muted small">
|
|
||||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
You can set a quota on the amount of external load balancers that can be created inside this resource pool. Set this quota to 0 to effectively disable the use
|
|
||||||
of load balancers in this resource pool.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<label class="control-label text-left">
|
|
||||||
Load Balancer quota
|
|
||||||
</label>
|
|
||||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
|
|
||||||
<span class="text-muted small" style="margin-left: 15px;">
|
|
||||||
<i class="fa fa-user" aria-hidden="true"></i>
|
|
||||||
This feature is available in <a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-lbquota" target="_blank"> Portainer Business Edition</a>.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- #endregion -->
|
|
||||||
|
|
||||||
<!-- #region LOAD-BALANCERS -->
|
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Storages
|
Storages
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import filesizeParser from 'filesize-parser';
|
import filesizeParser from 'filesize-parser';
|
||||||
import { KubernetesResourceQuota, KubernetesResourceQuotaDefaults } from 'Kubernetes/models/resource-quota/models';
|
import { KubernetesResourceQuota, KubernetesResourceQuotaDefaults } from 'Kubernetes/models/resource-quota/models';
|
||||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||||
import { KubernetesResourcePoolFormValues, KubernetesResourcePoolIngressClassAnnotationFormValue } from 'Kubernetes/models/resource-pool/formValues';
|
import { KubernetesResourcePoolFormValues, KubernetesResourcePoolIngressClassAnnotationFormValue } from 'Kubernetes/models/resource-pool/formValues';
|
||||||
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
import { KubernetesIngressConverter } from 'Kubernetes/ingress/converter';
|
||||||
import { KubernetesFormValueDuplicate } from 'Kubernetes/models/application/formValues';
|
import { KubernetesFormValidationReferences } from 'Kubernetes/models/application/formValues';
|
||||||
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHelper';
|
||||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||||
|
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
||||||
|
|
||||||
class KubernetesResourcePoolController {
|
class KubernetesResourcePoolController {
|
||||||
/* #region CONSTRUCTOR */
|
/* #region CONSTRUCTOR */
|
||||||
|
@ -28,7 +29,8 @@ class KubernetesResourcePoolController {
|
||||||
KubernetesPodService,
|
KubernetesPodService,
|
||||||
KubernetesApplicationService,
|
KubernetesApplicationService,
|
||||||
KubernetesNamespaceHelper,
|
KubernetesNamespaceHelper,
|
||||||
KubernetesIngressService
|
KubernetesIngressService,
|
||||||
|
KubernetesVolumeService
|
||||||
) {
|
) {
|
||||||
this.$async = $async;
|
this.$async = $async;
|
||||||
this.$state = $state;
|
this.$state = $state;
|
||||||
|
@ -46,18 +48,17 @@ class KubernetesResourcePoolController {
|
||||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||||
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
this.KubernetesNamespaceHelper = KubernetesNamespaceHelper;
|
||||||
this.KubernetesIngressService = KubernetesIngressService;
|
this.KubernetesIngressService = KubernetesIngressService;
|
||||||
|
this.KubernetesVolumeService = KubernetesVolumeService;
|
||||||
|
|
||||||
this.IngressClassTypes = KubernetesIngressClassTypes;
|
this.IngressClassTypes = KubernetesIngressClassTypes;
|
||||||
|
this.ResourceQuotaDefaults = KubernetesResourceQuotaDefaults;
|
||||||
|
|
||||||
this.onInit = this.onInit.bind(this);
|
this.onInit = this.onInit.bind(this);
|
||||||
this.createResourceQuotaAsync = this.createResourceQuotaAsync.bind(this);
|
this.createResourceQuotaAsync = this.createResourceQuotaAsync.bind(this);
|
||||||
this.updateResourcePoolAsync = this.updateResourcePoolAsync.bind(this);
|
this.updateResourcePoolAsync = this.updateResourcePoolAsync.bind(this);
|
||||||
this.getEvents = this.getEvents.bind(this);
|
this.getEvents = this.getEvents.bind(this);
|
||||||
this.getEventsAsync = this.getEventsAsync.bind(this);
|
|
||||||
this.getApplications = this.getApplications.bind(this);
|
this.getApplications = this.getApplications.bind(this);
|
||||||
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
|
|
||||||
this.getIngresses = this.getIngresses.bind(this);
|
this.getIngresses = this.getIngresses.bind(this);
|
||||||
this.getIngressesAsync = this.getIngressesAsync.bind(this);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ class KubernetesResourcePoolController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.refs = duplicates;
|
state.refs = duplicates;
|
||||||
state.hasDuplicates = Object.keys(duplicates).length > 0;
|
state.hasRefs = Object.keys(duplicates).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region ANNOTATIONS MANAGEMENT */
|
/* #region ANNOTATIONS MANAGEMENT */
|
||||||
|
@ -92,7 +93,7 @@ class KubernetesResourcePoolController {
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdateButtonDisabled() {
|
isUpdateButtonDisabled() {
|
||||||
return this.state.actionInProgress || (this.formValues.HasQuota && !this.isQuotaValid()) || this.state.duplicates.ingressHosts.hasDuplicates;
|
return this.state.actionInProgress || (this.formValues.HasQuota && !this.isQuotaValid()) || this.state.duplicates.ingressHosts.hasRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
isQuotaValid() {
|
isQuotaValid() {
|
||||||
|
@ -107,11 +108,11 @@ class KubernetesResourcePoolController {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDefaults() {
|
checkDefaults() {
|
||||||
if (this.formValues.CpuLimit < this.defaults.CpuLimit) {
|
if (this.formValues.CpuLimit < KubernetesResourceQuotaDefaults.CpuLimit) {
|
||||||
this.formValues.CpuLimit = this.defaults.CpuLimit;
|
this.formValues.CpuLimit = KubernetesResourceQuotaDefaults.CpuLimit;
|
||||||
}
|
}
|
||||||
if (this.formValues.MemoryLimit < KubernetesResourceReservationHelper.megaBytesValue(this.defaults.MemoryLimit)) {
|
if (this.formValues.MemoryLimit < KubernetesResourceReservationHelper.megaBytesValue(KubernetesResourceQuotaDefaults.MemoryLimit)) {
|
||||||
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(this.defaults.MemoryLimit);
|
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(KubernetesResourceQuotaDefaults.MemoryLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,45 +141,12 @@ class KubernetesResourcePoolController {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* #region UPDATE RESOURCE POOL */
|
||||||
async updateResourcePoolAsync() {
|
async updateResourcePoolAsync() {
|
||||||
this.state.actionInProgress = true;
|
this.state.actionInProgress = true;
|
||||||
try {
|
try {
|
||||||
this.checkDefaults();
|
this.checkDefaults();
|
||||||
const namespace = this.pool.Namespace.Name;
|
await this.KubernetesResourcePoolService.patch(this.savedFormValues, this.formValues);
|
||||||
const cpuLimit = this.formValues.CpuLimit;
|
|
||||||
const memoryLimit = KubernetesResourceReservationHelper.bytesValue(this.formValues.MemoryLimit);
|
|
||||||
const owner = this.pool.Namespace.ResourcePoolOwner;
|
|
||||||
const quota = this.pool.Quota;
|
|
||||||
|
|
||||||
if (this.formValues.HasQuota) {
|
|
||||||
if (quota) {
|
|
||||||
quota.CpuLimit = cpuLimit;
|
|
||||||
quota.MemoryLimit = memoryLimit;
|
|
||||||
await this.KubernetesResourceQuotaService.update(quota);
|
|
||||||
} else {
|
|
||||||
await this.createResourceQuotaAsync(namespace, owner, cpuLimit, memoryLimit);
|
|
||||||
}
|
|
||||||
} else if (quota) {
|
|
||||||
await this.KubernetesResourceQuotaService.delete(quota);
|
|
||||||
}
|
|
||||||
|
|
||||||
const promises = _.map(this.formValues.IngressClasses, (c) => {
|
|
||||||
c.Namespace = namespace;
|
|
||||||
if (c.WasSelected === false && c.Selected === true) {
|
|
||||||
const ingress = KubernetesIngressConverter.resourcePoolIngressClassFormValueToIngress(c);
|
|
||||||
return this.KubernetesIngressService.create(ingress);
|
|
||||||
} else if (c.WasSelected === true && c.Selected === false) {
|
|
||||||
return this.KubernetesIngressService.delete(c);
|
|
||||||
} else if (c.WasSelected === true && c.Selected === true) {
|
|
||||||
const oldIngress = _.find(this.ingresses, { Name: c.IngressClass.Name });
|
|
||||||
const newIngress = KubernetesIngressConverter.resourcePoolIngressClassFormValueToIngress(c);
|
|
||||||
newIngress.Paths = angular.copy(oldIngress.Paths);
|
|
||||||
newIngress.PreviousHost = oldIngress.Host;
|
|
||||||
return this.KubernetesIngressService.patch(oldIngress, newIngress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
this.Notifications.success('Resource pool successfully updated', this.pool.Namespace.Name);
|
this.Notifications.success('Resource pool successfully updated', this.pool.Namespace.Name);
|
||||||
this.$state.reload();
|
this.$state.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -212,13 +180,15 @@ class KubernetesResourcePoolController {
|
||||||
return this.$async(this.updateResourcePoolAsync);
|
return this.$async(this.updateResourcePoolAsync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* #endregion */
|
||||||
|
|
||||||
hasEventWarnings() {
|
hasEventWarnings() {
|
||||||
return this.state.eventWarningCount;
|
return this.state.eventWarningCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #region GET EVENTS */
|
/* #region GET EVENTS */
|
||||||
async getEventsAsync() {
|
getEvents() {
|
||||||
|
return this.$async(async () => {
|
||||||
try {
|
try {
|
||||||
this.state.eventsLoading = true;
|
this.state.eventsLoading = true;
|
||||||
this.events = await this.KubernetesEventService.get(this.pool.Namespace.Name);
|
this.events = await this.KubernetesEventService.get(this.pool.Namespace.Name);
|
||||||
|
@ -228,15 +198,13 @@ class KubernetesResourcePoolController {
|
||||||
} finally {
|
} finally {
|
||||||
this.state.eventsLoading = false;
|
this.state.eventsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
getEvents() {
|
|
||||||
return this.$async(this.getEventsAsync);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region GET APPLICATIONS */
|
/* #region GET APPLICATIONS */
|
||||||
async getApplicationsAsync() {
|
getApplications() {
|
||||||
|
return this.$async(async () => {
|
||||||
try {
|
try {
|
||||||
this.state.applicationsLoading = true;
|
this.state.applicationsLoading = true;
|
||||||
this.applications = await this.KubernetesApplicationService.get(this.pool.Namespace.Name);
|
this.applications = await this.KubernetesApplicationService.get(this.pool.Namespace.Name);
|
||||||
|
@ -251,15 +219,13 @@ class KubernetesResourcePoolController {
|
||||||
} finally {
|
} finally {
|
||||||
this.state.applicationsLoading = false;
|
this.state.applicationsLoading = false;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
getApplications() {
|
|
||||||
return this.$async(this.getApplicationsAsync);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region GET INGRESSES */
|
/* #region GET INGRESSES */
|
||||||
async getIngressesAsync() {
|
getIngresses() {
|
||||||
|
return this.$async(async () => {
|
||||||
this.state.ingressesLoading = true;
|
this.state.ingressesLoading = true;
|
||||||
try {
|
try {
|
||||||
const namespace = this.pool.Namespace.Name;
|
const namespace = this.pool.Namespace.Name;
|
||||||
|
@ -277,10 +243,7 @@ class KubernetesResourcePoolController {
|
||||||
} finally {
|
} finally {
|
||||||
this.state.ingressesLoading = false;
|
this.state.ingressesLoading = false;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
getIngresses() {
|
|
||||||
return this.$async(this.getIngressesAsync);
|
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -290,9 +253,6 @@ class KubernetesResourcePoolController {
|
||||||
const endpoint = this.EndpointProvider.currentEndpoint();
|
const endpoint = this.EndpointProvider.currentEndpoint();
|
||||||
this.endpoint = endpoint;
|
this.endpoint = endpoint;
|
||||||
this.isAdmin = this.Authentication.isAdmin();
|
this.isAdmin = this.Authentication.isAdmin();
|
||||||
this.defaults = KubernetesResourceQuotaDefaults;
|
|
||||||
this.formValues = new KubernetesResourcePoolFormValues(this.defaults);
|
|
||||||
this.formValues.HasQuota = false;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
@ -312,7 +272,7 @@ class KubernetesResourcePoolController {
|
||||||
eventWarningCount: 0,
|
eventWarningCount: 0,
|
||||||
canUseIngress: endpoint.Kubernetes.Configuration.IngressClasses.length,
|
canUseIngress: endpoint.Kubernetes.Configuration.IngressClasses.length,
|
||||||
duplicates: {
|
duplicates: {
|
||||||
ingressHosts: new KubernetesFormValueDuplicate(),
|
ingressHosts: new KubernetesFormValidationReferences(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -320,9 +280,11 @@ class KubernetesResourcePoolController {
|
||||||
|
|
||||||
const name = this.$transition$.params().id;
|
const name = this.$transition$.params().id;
|
||||||
|
|
||||||
const [nodes, pool] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get(name)]);
|
const [nodes, pools] = await Promise.all([this.KubernetesNodeService.get(), this.KubernetesResourcePoolService.get()]);
|
||||||
|
|
||||||
this.pool = pool;
|
this.pool = _.find(pools, { Namespace: { Name: name } });
|
||||||
|
this.formValues = new KubernetesResourcePoolFormValues(KubernetesResourceQuotaDefaults);
|
||||||
|
this.formValues.Name = this.pool.Namespace.Name;
|
||||||
|
|
||||||
_.forEach(nodes, (item) => {
|
_.forEach(nodes, (item) => {
|
||||||
this.state.sliderMaxMemory += filesizeParser(item.Memory);
|
this.state.sliderMaxMemory += filesizeParser(item.Memory);
|
||||||
|
@ -330,13 +292,10 @@ class KubernetesResourcePoolController {
|
||||||
});
|
});
|
||||||
this.state.sliderMaxMemory = KubernetesResourceReservationHelper.megaBytesValue(this.state.sliderMaxMemory);
|
this.state.sliderMaxMemory = KubernetesResourceReservationHelper.megaBytesValue(this.state.sliderMaxMemory);
|
||||||
|
|
||||||
const quota = pool.Quota;
|
const quota = this.pool.Quota;
|
||||||
if (quota) {
|
if (quota) {
|
||||||
this.oldQuota = angular.copy(quota);
|
this.oldQuota = angular.copy(quota);
|
||||||
this.formValues.HasQuota = true;
|
this.formValues = KubernetesResourceQuotaConverter.quotaToResourcePoolFormValues(quota);
|
||||||
this.formValues.CpuLimit = quota.CpuLimit;
|
|
||||||
this.formValues.MemoryLimit = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimit);
|
|
||||||
|
|
||||||
this.state.cpuUsed = quota.CpuLimitUsed;
|
this.state.cpuUsed = quota.CpuLimitUsed;
|
||||||
this.state.memoryUsed = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimitUsed);
|
this.state.memoryUsed = KubernetesResourceReservationHelper.megaBytesValue(quota.MemoryLimitUsed);
|
||||||
}
|
}
|
||||||
|
@ -354,6 +313,7 @@ class KubernetesResourcePoolController {
|
||||||
const ingressClasses = endpoint.Kubernetes.Configuration.IngressClasses;
|
const ingressClasses = endpoint.Kubernetes.Configuration.IngressClasses;
|
||||||
this.formValues.IngressClasses = KubernetesIngressConverter.ingressClassesToFormValues(ingressClasses, this.ingresses);
|
this.formValues.IngressClasses = KubernetesIngressConverter.ingressClassesToFormValues(ingressClasses, this.ingresses);
|
||||||
}
|
}
|
||||||
|
this.savedFormValues = angular.copy(this.formValues);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to load view data');
|
this.Notifications.error('Failure', err, 'Unable to load view data');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
ng-model="ctrl.state.volumeSize"
|
ng-model="ctrl.state.volumeSize"
|
||||||
placeholder="20"
|
placeholder="20"
|
||||||
ng-min="0"
|
ng-min="0"
|
||||||
|
min="0"
|
||||||
ng-change="ctrl.onChangeSize()"
|
ng-change="ctrl.onChangeSize()"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -100,11 +101,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-inline">
|
<div class="form-inline">
|
||||||
<div class="small text-warning" style="margin-top: 5px;" ng-show="ctrl.state.volumeSizeError || kubernetesVolumeUpdateForm.size.$invalid">
|
<div class="small text-warning" style="margin-top: 5px;" ng-show="ctrl.state.errors.volumeSize || kubernetesVolumeUpdateForm.size.$invalid">
|
||||||
<div ng-messages="kubernetesVolumeUpdateForm.size.$error">
|
<div ng-messages="kubernetesVolumeUpdateForm.size.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
<p ng-show="ctrl.state.volumeSizeError && !kubernetesVolumeUpdateForm.size.$invalid"
|
<p ng-show="ctrl.state.errors.volumeSize && !kubernetesVolumeUpdateForm.size.$invalid"
|
||||||
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> The new size must be greater than the actual size.</p
|
><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> The new size must be greater than the actual size.</p
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -64,18 +64,17 @@ class KubernetesVolumeController {
|
||||||
|
|
||||||
onChangeSize() {
|
onChangeSize() {
|
||||||
if (this.state.volumeSize) {
|
if (this.state.volumeSize) {
|
||||||
const size = filesizeParser(this.state.volumeSize + this.state.volumeSizeUnit);
|
const size = filesizeParser(this.state.volumeSize + this.state.volumeSizeUnit, { base: 10 });
|
||||||
if (this.state.oldVolumeSize > size) {
|
if (this.state.oldVolumeSize > size) {
|
||||||
this.state.volumeSizeError = true;
|
this.state.errors.volumeSize = true;
|
||||||
} else {
|
} else {
|
||||||
this.volume.PersistentVolumeClaim.Storage = size;
|
this.state.errors.volumeSize = false;
|
||||||
this.state.volumeSizeError = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeIsValid() {
|
sizeIsValid() {
|
||||||
return !this.state.volumeSizeError && this.state.oldVolumeSize !== this.volume.PersistentVolumeClaim.Storage;
|
return !this.state.errors.volumeSize && this.state.volumeSize && this.state.oldVolumeSize !== filesizeParser(this.state.volumeSize + this.state.volumeSizeUnit, { base: 10 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,7 +83,7 @@ class KubernetesVolumeController {
|
||||||
|
|
||||||
async updateVolumeAsync(redeploy) {
|
async updateVolumeAsync(redeploy) {
|
||||||
try {
|
try {
|
||||||
this.volume.PersistentVolumeClaim.Storage = this.state.volumeSize + this.state.volumeSizeUnit.charAt(0) + 'i';
|
this.volume.PersistentVolumeClaim.Storage = this.state.volumeSize + this.state.volumeSizeUnit.charAt(0);
|
||||||
await this.KubernetesPersistentVolumeClaimService.patch(this.oldVolume.PersistentVolumeClaim, this.volume.PersistentVolumeClaim);
|
await this.KubernetesPersistentVolumeClaimService.patch(this.oldVolume.PersistentVolumeClaim, this.volume.PersistentVolumeClaim);
|
||||||
this.Notifications.success('Volume successfully updated');
|
this.Notifications.success('Volume successfully updated');
|
||||||
|
|
||||||
|
@ -126,9 +125,9 @@ class KubernetesVolumeController {
|
||||||
volume.Applications = KubernetesVolumeHelper.getUsingApplications(volume, applications);
|
volume.Applications = KubernetesVolumeHelper.getUsingApplications(volume, applications);
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
this.oldVolume = angular.copy(volume);
|
this.oldVolume = angular.copy(volume);
|
||||||
this.state.volumeSize = parseInt(volume.PersistentVolumeClaim.Storage.slice(0, -2));
|
this.state.volumeSize = parseInt(volume.PersistentVolumeClaim.Storage.slice(0, -2), 10);
|
||||||
this.state.volumeSizeUnit = volume.PersistentVolumeClaim.Storage.slice(-2);
|
this.state.volumeSizeUnit = volume.PersistentVolumeClaim.Storage.slice(-2);
|
||||||
this.state.oldVolumeSize = filesizeParser(volume.PersistentVolumeClaim.Storage);
|
this.state.oldVolumeSize = filesizeParser(volume.PersistentVolumeClaim.Storage, { base: 10 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve volume');
|
this.Notifications.error('Failure', err, 'Unable to retrieve volume');
|
||||||
}
|
}
|
||||||
|
@ -179,9 +178,11 @@ class KubernetesVolumeController {
|
||||||
increaseSize: false,
|
increaseSize: false,
|
||||||
volumeSize: 0,
|
volumeSize: 0,
|
||||||
volumeSizeUnit: 'GB',
|
volumeSizeUnit: 'GB',
|
||||||
volumeSizeError: false,
|
|
||||||
volumeSharedAccessPolicy: '',
|
volumeSharedAccessPolicy: '',
|
||||||
volumeSharedAccessPolicyTooltip: '',
|
volumeSharedAccessPolicyTooltip: '',
|
||||||
|
errors: {
|
||||||
|
volumeSize: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state.activeTab = this.LocalStorage.getActiveTab('volume');
|
this.state.activeTab = this.LocalStorage.getActiveTab('volume');
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
require('../../templates/advancedDeploymentPanel.html');
|
require('../../templates/advancedDeploymentPanel.html');
|
||||||
|
|
||||||
import * as _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import filesizeParser from 'filesize-parser';
|
import filesizeParser from 'filesize-parser';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||||
|
import KubernetesResourceQuotaHelper from 'Kubernetes/helpers/resourceQuotaHelper';
|
||||||
|
|
||||||
function buildStorages(storages, volumes) {
|
function buildStorages(storages, volumes) {
|
||||||
_.forEach(storages, (s) => {
|
_.forEach(storages, (s) => {
|
||||||
|
@ -15,28 +16,9 @@ function buildStorages(storages, volumes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSize(volumes) {
|
function computeSize(volumes) {
|
||||||
let hasT,
|
const size = _.sumBy(volumes, (v) => filesizeParser(v.PersistentVolumeClaim.Storage, { base: 10 }));
|
||||||
hasG,
|
const format = KubernetesResourceQuotaHelper.formatBytes(size);
|
||||||
hasM = false;
|
return `${format.Size}${format.SizeUnit}`;
|
||||||
const size = _.sumBy(volumes, (v) => {
|
|
||||||
const storage = v.PersistentVolumeClaim.Storage;
|
|
||||||
if (!hasT && _.endsWith(storage, 'TB')) {
|
|
||||||
hasT = true;
|
|
||||||
} else if (!hasG && _.endsWith(storage, 'GB')) {
|
|
||||||
hasG = true;
|
|
||||||
} else if (!hasM && _.endsWith(storage, 'MB')) {
|
|
||||||
hasM = true;
|
|
||||||
}
|
|
||||||
return filesizeParser(storage, { base: 10 });
|
|
||||||
});
|
|
||||||
if (hasT) {
|
|
||||||
return size / 1000 / 1000 / 1000 / 1000 + 'TB';
|
|
||||||
} else if (hasG) {
|
|
||||||
return size / 1000 / 1000 / 1000 + 'GB';
|
|
||||||
} else if (hasM) {
|
|
||||||
return size / 1000 / 1000 + 'MB';
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KubernetesVolumesController {
|
class KubernetesVolumesController {
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"start:server": "grunt clean:server && grunt start:server",
|
"start:server": "grunt clean:server && grunt start:server",
|
||||||
"start:client": "grunt clean:client && grunt start:client",
|
"start:client": "grunt clean:client && grunt start:client",
|
||||||
"dev:client": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.develop.js",
|
"dev:client": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.develop.js",
|
||||||
|
"dev:client:prod": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.production.js",
|
||||||
"dev:nodl": "grunt clean:server && grunt clean:client && grunt build:server && grunt copy:assets && grunt start:client",
|
"dev:nodl": "grunt clean:server && grunt clean:client && grunt build:server && grunt copy:assets && grunt start:client",
|
||||||
"start:toolkit": "grunt start:toolkit",
|
"start:toolkit": "grunt start:toolkit",
|
||||||
"build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer",
|
"build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer",
|
||||||
|
|
|
@ -60,6 +60,15 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
devServer: {
|
||||||
|
contentBase: path.join(__dirname, '.tmp'),
|
||||||
|
compress: true,
|
||||||
|
port: 8999,
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://localhost:9000',
|
||||||
|
},
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './app/index.html',
|
template: './app/index.html',
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const path = require('path');
|
|
||||||
const webpackMerge = require('webpack-merge');
|
const webpackMerge = require('webpack-merge');
|
||||||
const commonConfig = require('./webpack.common.js');
|
const commonConfig = require('./webpack.common.js');
|
||||||
|
|
||||||
|
@ -18,13 +17,4 @@ module.exports = webpackMerge(commonConfig, {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
devServer: {
|
|
||||||
contentBase: path.join(__dirname, '.tmp'),
|
|
||||||
compress: true,
|
|
||||||
port: 8999,
|
|
||||||
proxy: {
|
|
||||||
'/api': 'http://localhost:9000',
|
|
||||||
},
|
|
||||||
open: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue