ui/app/models/namespace.js

309 lines
8.7 KiB
JavaScript

import { computed, get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import Resource from '@rancher/ember-api-store/models/resource';
import { parseExternalId } from 'ui/utils/parse-externalid';
import { convertToMillis } from 'shared/utils/util';
import { parseSi } from 'shared/utils/parse-unit';
import C from 'ui/utils/constants';
import { hasMany, reference } from '@rancher/ember-api-store/utils/denormalize';
import StateCounts from 'ui/mixins/state-counts';
const ISTIO_INJECTION = 'istio-injection'
const ENABLED = 'enabled';
export function convertResourceQuota(key, value) {
let out;
switch (key) {
case 'limitsCpu':
case 'requestsCpu':
out = convertToMillis(value);
break;
case 'limitsMemory':
case 'requestsMemory':
out = parseSi(value, 1024) / 1048576;
break;
case 'requestsStorage':
out = parseSi(value) / (1024 ** 3);
break;
default:
out = parseInt(value, 10);
}
return out;
}
export function activeIcon(ns) {
if ( ns.get('system') ) {
return 'icon icon-gear';
} else {
return 'icon icon-layers';
}
}
export function tagsToArray(str, normalize = true) {
return (str || '').split(/\s*,\s*/)
.map((tag) => {
if (normalize) {
return normalizeTag(tag);
} else {
return tag;
}
})
.filter((tag) => tag.length > 0);
}
export function normalizeTag(name) {
return (name || '').trim().toLowerCase();
}
export function normalizeTags(ary) {
return (ary || []).map(normalizeTag).filter((str) => str.length > 0);
}
var Namespace = Resource.extend(StateCounts, {
k8s: service(),
intl: service(),
modalService: service('modal'),
catalog: service(),
scope: service(),
router: service(),
projectStore: service('store'),
globalStore: service(),
clusterStore: service(),
growl: service(),
pods: hasMany('id', 'pod', 'namespaceId', 'projectStore', null, 'clusterStore'),
configMaps: hasMany('id', 'configMap', 'namespaceId', 'projectStore', null, 'clusterStore'),
workloads: hasMany('id', 'workload', 'namespaceId', 'projectStore', null, 'clusterStore'),
services: hasMany('id', 'service', 'namespaceId', 'projectStore', null, 'clusterStore'),
secrets: hasMany('id', 'namespacedSecret', 'namespaceId', 'projectStore', null, 'clusterStore'),
ingress: hasMany('id', 'ingress', 'namespaceId', 'projectStore', null, 'clusterStore'),
volumes: hasMany('id', 'persistentVolumeClaim', 'namespaceId', 'projectStore', null, 'clusterStore'),
type: 'namespace',
project: reference('projectId', 'project', 'globalStore'),
init() {
this._super(...arguments);
// @TODO-2.0 this.defineStateCounts('services', 'serviceStates', 'serviceCountSort');
},
availableActions: computed('projectId', 'actionLinks.@each.move', 'scope.currentCluster.istioEnabled', 'scope.currentCluster.systemProject', 'autoInjectionEnabled', function() {
let aa = this.actionLinks || {};
let out = [
{
label: 'action.move',
icon: 'icon icon-fork',
action: 'move',
enabled: !!aa.move,
bulkable: true
},
{
label: 'action.enableAutoInject',
icon: 'icon icon-plus-circle',
action: 'enableAutoInject',
enabled: get(this, 'scope.currentCluster.istioEnabled') && !!get(this, 'scope.currentCluster.systemProject') && !this.autoInjectionEnabled,
bulkable: true
},
{
label: 'action.disableAutoInject',
icon: 'icon icon-minus-circle',
action: 'disableAutoInject',
enabled: get(this, 'scope.currentCluster.istioEnabled') && !!get(this, 'scope.currentCluster.systemProject') && this.autoInjectionEnabled,
bulkable: true
},
{ divider: true },
];
return out;
}),
combinedState: computed('state', 'healthState', function() {
var stack = this.state;
var health = this.healthState;
if ( stack === 'active' && health ) {
return health;
} else {
return stack;
}
}),
externalIdInfo: computed('externalId', function() {
return parseExternalId(this.externalId);
}),
isDefault: computed('name', function() {
return (this.name || '').toLowerCase() === 'default';
}),
isEmpty: computed('pods.length', 'workloads.length', function() {
return (get(this, 'pods.length') || 0 + get(this, 'workloads.length') || 0) === 0;
}),
hasProject: computed('project', function() {
return !!this.project;
}),
isFromCatalog: computed('externalIdInfo.kind', function() {
let kind = this.get('externalIdInfo.kind');
return kind === C.EXTERNAL_ID.KIND_CATALOG || kind === C.EXTERNAL_ID.KIND_SYSTEM_CATALOG;
}),
// This only works if the templates have already been loaded elsewhere...
catalogTemplate: computed('externalIdInfo.templateId', function() {
return this.catalog.getTemplateFromCache(this.get('externalIdInfo.templateId'));
}),
icon: computed('catalogTemplate', function() {
let tpl = this.catalogTemplate;
if ( tpl ) {
return tpl.linkFor('icon');
}
return '';
}),
grouping: computed('externalIdInfo.kind', 'group', 'system', function() {
var kind = this.get('externalIdInfo.kind');
if ( kind === C.EXTERNAL_ID.KIND_KUBERNETES || kind === C.EXTERNAL_ID.KIND_LEGACY_KUBERNETES ) {
return C.EXTERNAL_ID.KIND_KUBERNETES;
} else if ( this.system ) {
return C.EXTERNAL_ID.KIND_INFRA;
} else {
return C.EXTERNAL_ID.KIND_USER;
}
}),
normalizedTags: computed('tags.[]', function() {
return normalizeTags(this.tags);
}),
autoInjectionEnabled: computed('labels', function() {
const labels = this.labels
return labels && labels[ISTIO_INJECTION] === ENABLED;
}),
validateResourceQuota(originLimit) {
const intl = this.intl;
let errors = [];
const resourceQuota = get(this, 'resourceQuota.limit') || {};
const total = get(this, 'project.resourceQuota.limit');
const used = get(this, 'project.resourceQuota.usedLimit') || {};
if ( total ) {
Object.keys(resourceQuota).forEach((key) => {
if ( !resourceQuota[key] && parseInt(resourceQuota[key], 10) !== 0 ) {
errors.push(intl.t('formResourceQuota.errors.limitRequired', { resource: intl.t(`formResourceQuota.resources.${ key }`) }));
}
if ( resourceQuota[key] ) {
const t = convertResourceQuota(key, total[key]);
const u = convertResourceQuota(key, used[key] || 0);
const v = convertResourceQuota(key, resourceQuota[key]);
const originValue = originLimit && originLimit[key] ? originLimit[key] : 0;
const o = convertResourceQuota(key, originValue);
const left = t - u + o;
if ( v > left ) {
errors.push(intl.t('formResourceQuota.errors.invalidLimit', {
resource: intl.t(`formResourceQuota.resources.${ key }`),
left,
total: t,
used: u - o,
}));
}
}
});
}
return errors;
},
actions: {
edit() {
this.modalService.toggleModal('modal-edit-namespace', this);
},
delete() {
return this._super().then(() => {
if ( this.get('application.currentRouteName') === 'stack.index' ) {
this.router.transitionTo('containers');
}
});
},
move() {
this.modalService.toggleModal('modal-move-namespace', this);
},
enableAutoInject() {
this.autoInjectToggle()
},
disableAutoInject() {
this.autoInjectToggle()
},
},
hasTags(want) {
if ( !want || !want.length ) {
return true;
}
want = normalizeTags(want);
let have = this.normalizedTags;
for ( let i = 0 ; i < want.length ; i++ ) {
if ( !have.includes(want[i]) ) {
return false;
}
}
return true;
},
autoInjectToggle() {
const labels = this.labels
const clone = this.clone()
if (this.autoInjectionEnabled) {
delete labels['istio-injection']
} else {
labels[ISTIO_INJECTION] = ENABLED;
}
set(clone, 'labels', labels)
clone.save().catch((err) => this.growl.fromError('Error:', err))
},
});
Namespace.reopenClass({
stateMap: {
'active': {
icon: activeIcon,
color: 'text-success'
},
'rolling-back': {
icon: 'icon icon-history',
color: 'text-info'
},
'upgraded': {
icon: 'icon icon-arrow-circle-up',
color: 'text-info'
},
'upgrading': {
icon: 'icon icon-arrow-circle-up',
color: 'text-info'
},
}
});
export default Namespace;