mirror of https://github.com/rancher/dashboard.git
383 lines
8.7 KiB
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>
|