dashboard/components/form/NameNsDescription.vue

383 lines
8.7 KiB
Vue

<script>
import { mapGetters } from 'vuex';
import { get, set } from '@/utils/object';
import { sortBy } from '@/utils/sort';
import { NAMESPACE } from '@/config/types';
import { DESCRIPTION } from '@/config/labels-annotations';
import { _VIEW, _EDIT } from '@/config/query-params';
import LabeledInput from '@/components/form/LabeledInput';
import InputWithSelect from '@/components/form/InputWithSelect';
export function normalizeName(str) {
return (str || '')
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '');
}
export default {
components: { LabeledInput, InputWithSelect },
props: {
value: {
type: Object,
required: true,
},
mode: {
type: String,
required: true,
},
nameNsHidden: {
type: Boolean,
default: false,
},
descriptionHidden: {
type: Boolean,
default: false,
},
extraColumns: {
type: Array,
default: () => [],
},
nameLabel: {
type: String,
default: 'nameNsDescription.name.label',
},
nameEditable: {
type: Boolean,
default: false,
},
namePlaceholder: {
type: String,
default: 'nameNsDescription.name.placeholder',
},
nameDisabled: {
type: Boolean,
default: false,
},
nameRequired: {
type: Boolean,
default: true,
},
nameNormalized: {
type: Boolean,
default: true,
},
namespaced: {
type: Boolean,
default: true,
},
namespaceType: {
type: String,
default: NAMESPACE,
},
namespaceLabel: {
type: String,
default: 'nameNsDescription.namespace.label',
},
namespacePlaceholder: {
type: String,
default: 'nameNsDescription.namespace.placeholder',
},
namespaceDisabled: {
type: Boolean,
default: false,
},
namespaceNewAllowed: {
type: Boolean,
default: false,
},
descriptionLabel: {
type: String,
default: 'nameNsDescription.description.label',
},
descriptionPlaceholder: {
type: String,
default: 'nameNsDescription.description.placeholder',
},
// Use specific fields on the value instead of the normal metadata locations
nameKey: {
type: String,
default: null,
},
namespaceKey: {
type: String,
default: null,
},
descriptionKey: {
type: String,
default: null,
},
forceNamespace: {
type: String,
default: null,
},
showSpacer: {
type: Boolean,
default: true
},
horizontal: {
type: Boolean,
default: true,
}
},
data() {
const v = this.value;
const metadata = v.metadata;
let namespace, name, description;
if (this.nameKey) {
name = get(v, this.nameKey);
} else {
name = metadata.name;
}
if (this.namespaced) {
if (this.forceNamespace) {
namespace = this.forceNamespace;
this.updateNamespace(namespace);
} else if (this.namespaceKey) {
namespace = get(v, this.namespaceKey);
} else {
namespace = metadata?.namespace;
}
if (!namespace) {
namespace = this.$store.getters['defaultNamespace'];
if (metadata) {
metadata.namespace = namespace;
}
}
}
if (this.descriptionKey) {
description = get(v, this.descriptionKey);
} else {
description = metadata?.annotations?.[DESCRIPTION];
}
return {
namespace,
name,
description,
};
},
computed: {
...mapGetters(['isVirtualCluster']),
namespaceReallyDisabled() {
return (
!!this.forceNamespace || this.namespaceDisabled || this.mode === _EDIT
); // namespace is never editable
},
nameReallyDisabled() {
return this.nameDisabled || (this.mode === _EDIT && !this.nameEditable);
},
namespaces() {
const inStore = this.$store.getters['currentStore'](this.namespaceType);
const choices = this.$store.getters[`${ inStore }/all`](this.namespaceType);
const out = sortBy(
choices.filter( (N) => {
const isSettingSystemNamespace = this.$store.getters['systemNamespaces'].includes(N.metadata.name);
return this.isVirtualCluster ? !N.isSystem && !N.isFleetManaged && !isSettingSystemNamespace : true;
}).map((obj) => {
return {
label: obj.nameDisplay,
value: obj.id,
};
}),
'label'
);
if (this.forceNamespace) {
out.unshift({
label: this.forceNamespace,
value: this.forceNamespace,
});
}
return out;
},
isView() {
return this.mode === _VIEW;
},
colSpan() {
if (!this.horizontal) {
return `span-8`;
}
let cols = (this.nameNsHidden ? 0 : 1) + (this.descriptionHidden ? 0 : 1) + this.extraColumns.length;
cols = Math.max(2, cols); // If there's only one column, make it render half-width as if there were two
const span = 12 / cols; // If there's 5, 7, or more columns this will break; don't do that.
return `span-${ span }`;
},
},
watch: {
name(val) {
if ( this.normalizeName ) {
val = normalizeName(val);
}
if (this.nameKey) {
set(this.value, this.nameKey, val);
} else {
this.$set(this.value.metadata, 'name', val);
}
this.$emit('change');
},
namespace(val) {
this.updateNamespace(val);
this.$emit('change');
},
description(val) {
if (this.descriptionKey) {
set(this.value, this.descriptionKey, val);
} else {
this.value.setAnnotation(DESCRIPTION, val);
}
this.$emit('change');
},
},
mounted() {
this.$nextTick(() => {
if (this.$refs.name) {
this.$refs.name.focus();
}
});
},
methods: {
updateNamespace(val) {
if (this.forceNamespace) {
val = this.forceNamespace;
}
if (this.namespaced) {
this.$emit('isNamespaceNew', this.namespaces && !this.namespaces.find(n => n.value === val));
}
if (this.namespaceKey) {
set(this.value, this.namespaceKey, val);
} else {
this.value.metadata.namespace = val;
}
},
changeNameAndNamespace(e) {
this.name = (e.text || '').toLowerCase();
this.namespace = e.selected;
},
},
};
</script>
<template>
<div>
<div class="name-ns-description" :class="{'flip-direction': !horizontal, row: true}">
<div v-show="!nameNsHidden" :class="{ col: true, [colSpan]: true }">
<slot :namespaces="namespaces" name="namespace">
<InputWithSelect
v-if="namespaced"
ref="name"
class="namespace-select"
:mode="mode"
:disabled="namespaceReallyDisabled"
:text-label="t(nameLabel)"
:text-placeholder="t(namePlaceholder)"
:text-value="name"
:text-required="nameRequired"
:select-label="t(namespaceLabel)"
:select-placeholder="t(namespacePlaceholder)"
:select-value="namespace"
:options="namespaces"
:searchable="true"
:taggable="namespaceNewAllowed"
@input="changeNameAndNamespace($event)"
/>
<LabeledInput
v-else
ref="name"
key="name"
v-model="name"
:label="t(nameLabel)"
:placeholder="t(namePlaceholder)"
:disabled="nameReallyDisabled"
:mode="mode"
:min-height="30"
:required="nameRequired"
/>
</slot>
</div>
<div v-show="!descriptionHidden" :class="{ col: true, [colSpan]: true }">
<LabeledInput
key="description"
v-model="description"
:mode="mode"
:label="t(descriptionLabel)"
:placeholder="t(descriptionPlaceholder)"
:min-height="30"
/>
</div>
<div
v-for="slot in extraColumns"
:key="slot"
:class="{ col: true, [colSpan]: true }"
>
<slot :name="slot">
</slot>
</div>
</div>
<div v-if="showSpacer" class="spacer"></div>
</div>
</template>
<style lang="scss" scoped>
.row {
&.name-ns-description {
max-height: $input-height;
}
.namespace-select ::v-deep {
.labeled-select {
min-width: 40%;
.v-select.inline {
&.vs--single {
padding-bottom: 2px;
}
}
}
}
&.flip-direction {
flex-direction: column;
&.name-ns-description {
max-height: initial;
}
& > div > * {
margin-bottom: 20px;
}
}
}
</style>