mirror of https://github.com/rancher/dashboard.git
225 lines
5.7 KiB
Vue
225 lines
5.7 KiB
Vue
<script lang="ts">
|
|
import { pickBy, omitBy, mapValues } from 'lodash';
|
|
import { matchesSomeRegex } from '@shell/utils/string';
|
|
import { LABELS_TO_IGNORE_REGEX, ANNOTATIONS_TO_IGNORE_REGEX } from '@shell/config/labels-annotations';
|
|
import KeyValue from '@shell/components/form/KeyValue.vue';
|
|
import { ToggleSwitch } from '@components/Form/ToggleSwitch';
|
|
import { _VIEW } from '@shell/config/query-params';
|
|
|
|
export class Factory {
|
|
private protectedKeys: string[] = [];
|
|
private protectedRegexes: RegExp[] = [];
|
|
private protectedWarning = '';
|
|
|
|
private isProtected(key: string) {
|
|
return this.protectedKeys.includes(key) || matchesSomeRegex(key, this.protectedRegexes);
|
|
}
|
|
|
|
private omitProtected(obj: object) {
|
|
return omitBy(obj, (_, key) => this.isProtected(key));
|
|
}
|
|
|
|
private pickProtected(obj: object) {
|
|
return pickBy(obj, (_, key) => this.isProtected(key));
|
|
}
|
|
|
|
private keyErrorMap(elems: object) {
|
|
return mapValues(this.pickProtected(elems), () => this.protectedWarning);
|
|
}
|
|
|
|
constructor(protectedKeys: string[], protectedRegexes: RegExp[], msg: string, initValue: object) {
|
|
// Init privates
|
|
this.protectedKeys = protectedKeys || [];
|
|
this.protectedRegexes = protectedRegexes || [];
|
|
this.protectedWarning = msg || '';
|
|
|
|
this.initValue = initValue || {};
|
|
this.value = this.omitProtected(this.initValue);
|
|
this.keyErrors = this.keyErrorMap(this.value);
|
|
this.hasProtectedKeys = Object.keys(this.pickProtected(this.initValue)).length > 0;
|
|
}
|
|
|
|
initValue: object = {};
|
|
value: object = {};
|
|
keyErrors: object = {};
|
|
hasProtectedKeys = false;
|
|
|
|
/**
|
|
* Updates resource's model and discard new protected keys
|
|
* Old protected keys remain untouched on edit
|
|
*
|
|
* @param value edited labels/annotations
|
|
* @param callbackFn function to set model's labels/annotations
|
|
*/
|
|
update(value: Record<string, string>, callbackFn: (value: object) => void) {
|
|
const neu = value || {};
|
|
|
|
callbackFn({
|
|
...this.omitProtected(neu),
|
|
...this.pickProtected(this.initValue),
|
|
});
|
|
|
|
this.value = neu;
|
|
this.keyErrors = this.keyErrorMap(neu);
|
|
}
|
|
}
|
|
|
|
interface DataType {
|
|
labels: Factory,
|
|
annotations: Factory,
|
|
toggler: boolean,
|
|
}
|
|
|
|
export default {
|
|
components: {
|
|
ToggleSwitch,
|
|
KeyValue
|
|
},
|
|
|
|
props: {
|
|
value: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
|
|
mode: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
|
|
displaySideBySide: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
|
|
defaultContainerClass: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
|
|
defaultSectionClass: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
|
|
labelTitleTooltip: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
|
|
annotationTitleTooltip: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
|
|
showAnnotations: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
|
|
showLabelTitle: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
|
|
addIcon: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
},
|
|
|
|
data(): DataType {
|
|
const protectedWarning = this.t('labels.protectedWarning');
|
|
|
|
return {
|
|
labels: new Factory(this.value.systemLabels, LABELS_TO_IGNORE_REGEX, protectedWarning, this.value.labels),
|
|
annotations: new Factory(this.value.systemAnnotations, ANNOTATIONS_TO_IGNORE_REGEX, protectedWarning, this.value.annotations),
|
|
toggler: false
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
containerClass() {
|
|
return `${ this.displaySideBySide ? 'row' : '' } ${ this.defaultContainerClass }`.trim();
|
|
},
|
|
|
|
sectionClass() {
|
|
return `${ this.displaySideBySide ? 'col span-6' : 'row' } ${ this.defaultSectionClass }`.trim();
|
|
},
|
|
|
|
columnsClass() {
|
|
return `${ this.displaySideBySide ? 'col span-6' : 'row' }`.trim();
|
|
},
|
|
|
|
showToggler() {
|
|
return this.mode === _VIEW && (this.labels.hasProtectedKeys || this.annotations.hasProtectedKeys);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
<template>
|
|
<div :class="containerClass">
|
|
<div :class="defaultSectionClass">
|
|
<div class="labels">
|
|
<div class="labels__header">
|
|
<h3 v-if="showLabelTitle">
|
|
<t k="labels.labels.title" />
|
|
</h3>
|
|
<ToggleSwitch
|
|
v-if="showToggler"
|
|
v-model:value="toggler"
|
|
name="label-system-toggle"
|
|
:on-label="t('labels.labels.show')"
|
|
/>
|
|
</div>
|
|
<p class="mt-10 mb-10">
|
|
<t k="labels.labels.description" />
|
|
</p>
|
|
<div :class="columnsClass">
|
|
<slot name="labels">
|
|
<KeyValue
|
|
key="labels"
|
|
:value="toggler ? labels.initValue : labels.value"
|
|
:add-label="t('labels.addLabel')"
|
|
:add-icon="addIcon"
|
|
:mode="mode"
|
|
:read-allowed="false"
|
|
:value-can-be-empty="true"
|
|
:key-errors="labels.keyErrors"
|
|
@update:value="labels.update($event, (x) => value.setLabels(x))"
|
|
/>
|
|
</slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="spacer" />
|
|
<div
|
|
v-if="showAnnotations"
|
|
:class="sectionClass"
|
|
>
|
|
<KeyValue
|
|
key="annotations"
|
|
:value="toggler ? annotations.initValue : annotations.value"
|
|
:add-label="t('labels.addAnnotation')"
|
|
:add-icon="addIcon"
|
|
:mode="mode"
|
|
:title="t('labels.annotations.title')"
|
|
:title-protip="annotationTitleTooltip"
|
|
:read-allowed="false"
|
|
:value-can-be-empty="true"
|
|
:key-errors="annotations.keyErrors"
|
|
@update:value="annotations.update($event, (x) => value.setAnnotations(x))"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.labels {
|
|
&__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
}
|
|
</style>
|