ui/lib/shared/addon/mixins/manage-labels.js

377 lines
8.7 KiB
JavaScript

import { isArray } from '@ember/array';
import EmberObject, { set, setProperties, computed } from '@ember/object';
import Mixin from '@ember/object/mixin';
import C from 'ui/utils/constants';
import { debouncedObserver } from 'ui/utils/debounce';
import { isEmpty } from '@ember/utils';
const USER = 'user';
const SYSTEM = 'system';
const AFFINITY = 'affinity';
export const TYPE = {
USER,
SYSTEM,
AFFINITY
}
export const K3S_LABELS_TO_IGNORE = [
C.LABEL.K3S_NODE_ARGS,
C.LABEL.K3S_NODE_CONFIG_HASH,
C.LABEL.K3S_NODE_ENV
];
export function flattenLabelArrays(...lists) {
let out = {};
function flatten(row) {
if ( row.value === undefined ) {
delete out[row.key];
} else {
out[row.key] = row.value;
}
}
for ( let i = 0 ; i < lists.length ; i++ ) {
(lists[i] || []).forEach(flatten);
}
return out;
}
function isSoftUser(type, key) {
// Include actual user labels
if ( type === USER ) {
return true;
}
// Don't include any affinity labels
if ( type === AFFINITY ) {
return false;
}
// Don't include any system labels that are blacklisted (because they have their own controls, like global)
if ( C.SYSTEM_LABELS_WITH_CONTROL.indexOf(key) >= 0 ) {
return false;
}
// Include anything else
return true;
}
export default Mixin.create({
labelArray: null,
k3sLabelsToIgnore: K3S_LABELS_TO_IGNORE,
actions: {
addUserLabel() {
this.get('labelArray').pushObject(EmberObject.create({
type: USER,
key: '',
value: '',
}));
},
addSystemLabel() {
this.get('labelArray').pushObject(EmberObject.create({
type: SYSTEM,
key: '',
value: '',
}));
},
addAffinityLabel() {
this.get('labelArray').pushObject(EmberObject.create({
type: AFFINITY,
key: C.LABEL.SCHED_HOST_LABEL,
value: '',
}));
},
removeLabel(obj) {
this.get('labelArray').removeObject(obj);
},
pastedLabels(str, target) {
let ary = this.get('labelArray');
str = str.trim();
if ( str.indexOf('=') === -1 && str.indexOf(':') === -1) {
// Just pasting a key
$(target).val(str); // eslint-disable-line
return;
}
let lines = str.split(/\r?\n/);
lines.forEach((line) => {
line = line.trim();
if ( !line ) {
return;
}
let idx = line.indexOf('=');
if ( idx === -1 ) {
idx = line.indexOf(':');
}
let key = '';
let val = '';
if ( idx > 0 ) {
key = line.substr(0, idx).trim();
val = line.substr(idx + 1).trim();
} else {
key = line.trim();
val = '';
}
let existing = ary.filterBy('key', key)[0];
if ( existing ) {
set(existing, 'value', val);
} else {
ary.pushObject(EmberObject.create({
key,
value: val,
type: USER
}));
}
});
// Clean up empty user entries
let toRemove = [];
ary.filterBy('type', USER).forEach((item) => {
if ( !item.get('key') && !item.get('value') ) {
toRemove.push(item);
}
});
ary.removeObjects(toRemove);
},
},
// User labels are actual user ones, plus system ones that have no controls in the UI so they are manually entered.
userLabelArray: computed('labelArray.@each.type', function() {
return (this.get('labelArray') || []).filter((item) => {
return isSoftUser(item.get('type'), item.get('key'));
});
}),
strictUserLabelArray: computed('labelArray.@each.type', function() {
return (this.get('labelArray') || []).filterBy('type', USER);
}),
systemLabelArray: computed('labelArray.@each.type', function() {
return (this.get('labelArray') || []).filterBy('type', SYSTEM);
}),
affinityLabelArray: computed('labelArray.@each.type', function() {
return (this.get('labelArray') || []).filterBy('type', AFFINITY);
}),
getLabelObj(key) {
let lcKey = (key || '').toLowerCase();
let ary = this.get('labelArray');
let item;
// Try specific case first
for ( let i = 0 ; i < ary.get('length') ; i++ ) {
item = ary.objectAt(i);
if ( item.get('key') === key ) {
return item;
}
}
// Then case-insensitive
for ( var i = 0 ; i < ary.get('length') ; i++ ) {
item = ary.objectAt(i);
if ( item.get('key').toLowerCase() === lcKey ) {
return item;
}
}
return null;
},
getLabel(key) {
let obj = this.getLabelObj(key);
if ( obj ) {
return obj.get('value');
}
return null;
},
setLabel(key, value) {
let lcKey = (key || '').toLowerCase();
let type = 'user';
// Rancher keys are always lowercase
if ( lcKey.indexOf(C.LABEL.AFFINITY_PREFIX) === 0 ) {
type = 'affinity';
key = lcKey;
} else if ( lcKey.indexOf(C.LABEL.SYSTEM_PREFIX) === 0 ) {
type = 'system';
key = lcKey;
}
let existing = this.getLabelObj(key);
if ( existing ) {
if ( existing.get('value') !== value ) {
setProperties(existing, {
value,
type,
});
}
} else {
existing = this.get('labelArray').pushObject(EmberObject.create({
key,
value,
type,
}));
}
return existing;
},
removeLabel(key, soft = false) {
if ( soft ) {
this.setLabel(key, undefined);
} else {
let existing = this.getLabelObj(key);
if ( existing ) {
this.get('labelArray').removeObject(existing);
}
}
},
initLabels(obj, onlyOfType, onlyKeys, readonlyKeys, labelsToIgnore) {
let out = [];
let ignoredLabels = [...C.LABELS_TO_IGNORE];
if (!isEmpty(labelsToIgnore)) {
ignoredLabels.pushObjects(labelsToIgnore);
}
if ( onlyKeys && !isArray(onlyKeys) ) {
onlyKeys = [onlyKeys];
}
if ( readonlyKeys && !isArray(readonlyKeys) ) {
readonlyKeys = [readonlyKeys];
}
Object.keys(obj || {}).forEach((key) => {
let type = 'user';
if ( key.indexOf(C.LABEL.AFFINITY_PREFIX) === 0 ) {
type = 'affinity';
} else if ( key.indexOf(C.LABEL.SYSTEM_PREFIX) === 0 ) {
type = 'system';
}
if ( ignoredLabels.indexOf(key) >= 0 ) {
// Skip ignored labels
return;
}
if ( onlyOfType ) {
// Strict User, only those with type actually == user
if ( onlyOfType === 'strictUser' && type !== 'user' ) {
return;
}
if ( onlyOfType === 'user' ) {
// Soft user := user + system things that don't have UI controls
if ( !isSoftUser(type, key) ) {
return;
}
} else if ( onlyOfType !== type ) {
// Generally the wrong type, for system or affinity
return;
}
}
if ( onlyKeys && onlyKeys.indexOf(key) === -1 ) {
// Skip labels of keys we don't care about
return;
}
out.push(EmberObject.create({
key,
value: obj[key] || '',
type,
readonly: readonlyKeys && readonlyKeys.indexOf(key) >= 0
}));
});
this.set('labelArray', out);
this.labelsChanged();
},
labelsChanged: debouncedObserver('labelArray.@each.{type,key,value}', function() {
// Make a map of the keys we care about, and combine multiple values together
let map = {};
(this.get('labelArray') || []).forEach((row) => {
let key = row.get('key') || '';
let type = row.get('type') || '';
// System and Affinity labels are always lowercase.
if ( type !== USER ) {
key = key.toLowerCase();
}
// Pass undefined through, for soft-delete
if ( row.get('value') === undefined ) {
map[key] = undefined;
return;
}
let value = row.get('value') || '';
// Skip empty keys, and system/affinity labels with no value
if ( !key || (type !== USER && value === '')) {
return;
}
// System and Affinity values used to be always lowercase.
// if ( type !== USER )
// {
// value = value.toLowerCase();
// }
// Affinity & System labels can be concatenated, Users just overwrite the previous value.
if ( map[key] && type !== USER ) {
map[key] = `${ map[key] },${ value }`;
} else {
map[key] = value;
}
});
// Then turn them back into an array because Ember hates maps.
let out = [];
Object.keys(map).forEach((key) => {
out.push({
key,
value: map[key]
});
});
this.updateLabels(out);
}),
updateLabels(/* labels*/) {
// Override me to do something
},
});