mirror of https://github.com/rancher/dashboard.git
378 lines
11 KiB
Vue
378 lines
11 KiB
Vue
<script lang="ts">
|
|
import { PropType } from 'vue';
|
|
import { set, get } from '@shell/utils/object';
|
|
import { _EDIT } from '@shell/config/query-params';
|
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
import KeyValue from '@shell/components/form/KeyValue.vue';
|
|
import { TextAreaAutoGrow } from '@components/Form/TextArea';
|
|
import Taints from '@shell/components/form/Taints.vue';
|
|
import UnitInput from '@shell/components/form/UnitInput.vue';
|
|
import { Banner } from '@components/Banner';
|
|
import { Group, Setting } from '@shell/components/ConfigMapSettings/index.vue';
|
|
|
|
interface Option {
|
|
label: string,
|
|
value: string,
|
|
}
|
|
|
|
type SettingDisplay = Partial<Setting> & {
|
|
children?: Record<string, Setting>,
|
|
name: string,
|
|
options?: Option[]
|
|
label: string,
|
|
description: string,
|
|
tooltipLabel?: string,
|
|
infoLabel?: string,
|
|
placeholderLabel?: string,
|
|
}
|
|
|
|
interface DataType {
|
|
isGroupExpanded: Record<string, boolean>;
|
|
}
|
|
|
|
export default {
|
|
|
|
name: 'Settings',
|
|
|
|
emits: ['update:value'],
|
|
|
|
components: {
|
|
LabeledInput,
|
|
LabeledSelect,
|
|
KeyValue,
|
|
Checkbox,
|
|
Banner,
|
|
Taints,
|
|
TextAreaAutoGrow,
|
|
UnitInput,
|
|
},
|
|
|
|
props: {
|
|
settings: {
|
|
type: Object as PropType<Record<string, Setting>>,
|
|
required: true
|
|
},
|
|
|
|
groups: {
|
|
type: Array as PropType<Group[]>,
|
|
default: () => [],
|
|
},
|
|
|
|
values: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
|
|
labelKeyPrefix: {
|
|
type: String,
|
|
default: 'settings'
|
|
},
|
|
|
|
mode: {
|
|
type: String,
|
|
default: _EDIT
|
|
},
|
|
},
|
|
|
|
data(): DataType {
|
|
return { isGroupExpanded: this.groups.reduce((acc, { name, expanded }) => ({ ...acc, [name]: !!expanded }), {}) };
|
|
},
|
|
|
|
computed: {
|
|
settingsDisplay(): SettingDisplay[] {
|
|
// Init settingsDisplay with flat settings
|
|
const out = Object.keys(this.settings)
|
|
.filter((name) => !this.groups.find((g) => g.children.includes(name)))
|
|
.reduce((acc, name) => [
|
|
...acc,
|
|
{
|
|
...this.settings[name],
|
|
name,
|
|
options: this.settings[name].items?.map(({ value }) => ({ value, label: this.display(`${ name }.options.${ value }`, 'label') })),
|
|
label: this.display(name, 'label'),
|
|
description: this.display(name, 'description'),
|
|
tooltipLabel: this.settings[name].tooltip ? this.display(name, 'tooltip') : '',
|
|
infoLabel: this.settings[name].info ? this.display(name, 'info') : '',
|
|
placeholderLabel: this.settings[name].placeholder ? this.display(name, 'placeholder') : '',
|
|
class: this.settings[name].class || 'span-4'
|
|
}
|
|
], [] as SettingDisplay[]);
|
|
|
|
// Add grouped settings
|
|
this.groups.forEach((group) => {
|
|
out.push({
|
|
...group,
|
|
label: this.display(group.name, 'label'),
|
|
description: this.display(group.name, 'description'),
|
|
children: group.children.reduce((acc, name) => ({
|
|
...acc,
|
|
[name]: this.settings[name]
|
|
}), {})
|
|
});
|
|
});
|
|
|
|
return out.sort((a, b) => (a.weight || 0) - (b.weight || 0) || (a.label || '').localeCompare(b.label || ''));
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
get(item: SettingDisplay) {
|
|
return get(this.values, item.path);
|
|
},
|
|
|
|
set(item: SettingDisplay, value: any) {
|
|
set(this.values, item.path, value);
|
|
|
|
this.update();
|
|
},
|
|
|
|
update() {
|
|
this.$emit('update:value', this.values);
|
|
},
|
|
|
|
display(name: string, key: 'label' | 'description' | 'tooltip' | 'info' | 'placeholder' | 'add') {
|
|
return this.t(`${ this.labelKeyPrefix }.${ name }.${ key }`, {}, true);
|
|
},
|
|
|
|
toggleGroup(item: SettingDisplay) {
|
|
if (item.children) {
|
|
this.isGroupExpanded[item.name] = !this.isGroupExpanded[item.name];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="settings-container">
|
|
<div
|
|
v-for="item in settingsDisplay"
|
|
:key="item.name"
|
|
class="setting-row"
|
|
:class="{ box: item.children }"
|
|
:data-testid="`cm-settings-row-${ item.name }`"
|
|
>
|
|
<div class="header">
|
|
<div
|
|
class="title"
|
|
:class="{ clickable: item.children }"
|
|
:tabindex="item.children ? 0 : undefined"
|
|
:role="item.children ? 'button' : undefined"
|
|
@click="toggleGroup(item)"
|
|
@keydown.space.enter.stop.prevent="toggleGroup(item)"
|
|
>
|
|
<i
|
|
v-if="item.children"
|
|
:class="{
|
|
['icon icon-chevron-right']: !isGroupExpanded[item.name],
|
|
['icon icon-chevron-down']: isGroupExpanded[item.name],
|
|
}"
|
|
/>
|
|
<component
|
|
:is="item.children ? 'h2' : 'h3'"
|
|
v-if="item.label"
|
|
class="label"
|
|
>
|
|
{{ item.label }}
|
|
<i
|
|
v-if="item.tooltip"
|
|
v-clean-tooltip="item.tooltipLabel"
|
|
class="icon icon-info"
|
|
/>
|
|
</component>
|
|
</div>
|
|
|
|
<div
|
|
v-if="item.description && item.type !== 'boolean'"
|
|
class="description mt-10"
|
|
>
|
|
<label
|
|
class="text-label"
|
|
:aria-describedby="item.description"
|
|
>
|
|
{{ item.description }}
|
|
</label>
|
|
</div>
|
|
|
|
<Banner
|
|
v-if="item.info"
|
|
color="warning"
|
|
:label="item.infoLabel"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-if="!item.children || isGroupExpanded[item.name]"
|
|
class="body mt-10"
|
|
>
|
|
<div
|
|
v-if="item.children"
|
|
class="group mt-10"
|
|
:data-testid="`cm-settings-group-body-${ item.name }`"
|
|
>
|
|
<Settings
|
|
:settings="item.children"
|
|
:values="values"
|
|
:mode="mode"
|
|
:label-key-prefix="labelKeyPrefix"
|
|
@update:value="update"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-else
|
|
class="row simple"
|
|
>
|
|
<div
|
|
class="col"
|
|
:class="item.class"
|
|
>
|
|
<template v-if="item.items?.length">
|
|
<LabeledSelect
|
|
:data-testid="`cm-settings-field-${ item.type === 'array' ? 'array' : item.type }-${ item.name }`"
|
|
:value="get(item)"
|
|
:label="item.label"
|
|
:placeholder="item.placeholderLabel"
|
|
:mode="mode"
|
|
:multiple="item.type === 'array'"
|
|
:options="item.options"
|
|
:option-key="'value'"
|
|
:reduce="(opt: Option)=>opt.value"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
</template>
|
|
|
|
<template v-else-if="item.type === 'object'">
|
|
<TextAreaAutoGrow
|
|
v-if="item.handler === 'Textarea'"
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.handler }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item) || ''"
|
|
:min-height="10"
|
|
:mode="mode"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
<KeyValue
|
|
v-else-if="item.handler === 'KeyValue'"
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.handler }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:read-allowed="false"
|
|
:add-icon="'icon-plus'"
|
|
:add-label="display(item.name, 'add')"
|
|
:add-class="'btn-sm'"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
<Taints
|
|
v-else-if="item.handler === 'Taints'"
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.handler }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:title="' '"
|
|
:add-icon="'icon-plus'"
|
|
:add-class="'btn-sm'"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
</template>
|
|
|
|
<template v-else-if="item.type === 'string'">
|
|
<LabeledInput
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:label="item.label"
|
|
:placeholder="item.placeholderLabel"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
</template>
|
|
|
|
<template v-else-if="item.type === 'number'">
|
|
<UnitInput
|
|
v-if="item.handler === 'UnitInput'"
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.handler }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:suffix="t('suffix.sec')"
|
|
:label="item.label"
|
|
:placeholder="item.placeholderLabel"
|
|
min="0"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
<LabeledInput
|
|
v-else
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:label="item.label"
|
|
:placeholder="item.placeholderLabel"
|
|
class="input"
|
|
type="number"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
</template>
|
|
|
|
<template v-else-if="item.type === 'boolean'">
|
|
<Checkbox
|
|
:data-testid="`cm-settings-field-${ item.type }-${ item.name }`"
|
|
:aria-label="t(`${ labelKeyPrefix }.fields.ariaLabel`, { name: item.label })"
|
|
:value="get(item)"
|
|
:mode="mode"
|
|
:label="item.description"
|
|
@update:value="set(item, $event)"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang='scss'>
|
|
.settings-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
|
|
.setting-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.header {
|
|
.title {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 10px;
|
|
width: fit-content;
|
|
|
|
&:focus-visible {
|
|
@include focus-outline;
|
|
}
|
|
|
|
.label {
|
|
margin: 0 !important;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.box {
|
|
border-radius: var(--border-radius);
|
|
border: 1px solid var(--border);
|
|
padding: 15px;
|
|
}
|
|
|
|
.clickable {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|