mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 03:36:04 +08:00
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import FileSelector, { createOnSelected } from '@shell/components/form/FileSelector';
|
||||
import { TextAreaAutoGrow } from '@components/Form/TextArea';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterAdditionalCA',
|
||||
|
||||
components: { FileSelector, TextAreaAutoGrow },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
methods: { onKeySelected: createOnSelected('value.value') },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<FileSelector
|
||||
class="btn btn-sm bg-primary mb-10"
|
||||
:label="t('generic.readFromFile')"
|
||||
@selected="onKeySelected"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<TextAreaAutoGrow
|
||||
v-model="value.value"
|
||||
:min-height="254"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,85 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import UnitInput from '@shell/components/form/UnitInput';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterAutoRotateRKE2Certs',
|
||||
|
||||
components: { RadioGroup, UnitInput },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return { parseDefaultValue };
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<RadioGroup
|
||||
v-model="parseDefaultValue.enable"
|
||||
class="mb-20"
|
||||
name="model"
|
||||
:options="[true,false]"
|
||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||
@input="update"
|
||||
/>
|
||||
<UnitInput
|
||||
v-if="parseDefaultValue.enable"
|
||||
v-model="parseDefaultValue.expiringInHours"
|
||||
v-int-number
|
||||
class="mb-20"
|
||||
:min="1"
|
||||
:max="8759"
|
||||
:required="true"
|
||||
:suffix="parseDefaultValue.expiringInHours === 1 ? 'Hour' : 'Hours'"
|
||||
:label="t('harvester.setting.autoRotateRKE2Certs.expiringInHours')"
|
||||
:mode="mode"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .radio-group {
|
||||
display: flex;
|
||||
.radio-container {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,199 @@
|
||||
<script>
|
||||
import Tip from '@shell/components/Tip';
|
||||
import { HCI_SETTING } from '../../config/settings';
|
||||
import Password from '@shell/components/form/Password';
|
||||
import MessageLink from '@shell/components/MessageLink';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditBackupTarget',
|
||||
|
||||
components: {
|
||||
LabeledInput, LabeledSelect, Tip, Password, MessageLink
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = { type: '', endpoint: '' };
|
||||
}
|
||||
|
||||
if (!parseDefaultValue.type) {
|
||||
parseDefaultValue.type = 's3';
|
||||
}
|
||||
|
||||
return {
|
||||
parseDefaultValue,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
typeOption() {
|
||||
return [{
|
||||
value: 'nfs',
|
||||
label: 'NFS'
|
||||
}, {
|
||||
value: 's3',
|
||||
label: 'S3'
|
||||
}];
|
||||
},
|
||||
|
||||
virtualHostedStyleType() {
|
||||
return [{
|
||||
value: true,
|
||||
label: 'True'
|
||||
}, {
|
||||
value: false,
|
||||
label: 'False'
|
||||
}];
|
||||
},
|
||||
|
||||
isS3() {
|
||||
return this.parseDefaultValue.type === 's3';
|
||||
},
|
||||
|
||||
endpointPlaceholder() {
|
||||
return this.isS3 ? '' : 'nfs://server:/path/';
|
||||
},
|
||||
|
||||
toCA() {
|
||||
return `${ HCI_SETTING.ADDITIONAL_CA }?mode=edit`;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
let parseDefaultValue;
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(neu.value);
|
||||
} catch (err) {
|
||||
parseDefaultValue = { type: '', endpoint: '' };
|
||||
}
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
this.update();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
if (!this.isS3) {
|
||||
delete this.parseDefaultValue.accessKeyId;
|
||||
delete this.parseDefaultValue.secretAccessKey;
|
||||
delete this.parseDefaultValue.bucketName;
|
||||
delete this.parseDefaultValue.bucketRegion;
|
||||
delete this.parseDefaultValue.virtualHostedStyle;
|
||||
delete this.parseDefaultValue.cert;
|
||||
}
|
||||
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
if (!this.parseDefaultValue.type) {
|
||||
this.$delete(this.value, 'value');
|
||||
} else {
|
||||
this.$set(this.value, 'value', value);
|
||||
}
|
||||
},
|
||||
|
||||
useDefault() {
|
||||
const parseDefaultValue = { type: '', endpoint: '' };
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" @input="update">
|
||||
<div class="col span-12">
|
||||
<LabeledSelect v-model="parseDefaultValue.type" class="mb-20" :label="t('harvester.fields.type')" :options="typeOption" @input="update" />
|
||||
|
||||
<LabeledInput v-model="parseDefaultValue.endpoint" class="mb-5" :placeholder="endpointPlaceholder" :mode="mode" label="Endpoint" />
|
||||
<Tip class="mb-20" icon="icon icon-info" :text="t('harvester.backup.backupTargetTip')" />
|
||||
|
||||
<template v-if="isS3">
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.bucketName"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="Bucket Name"
|
||||
required
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.bucketRegion"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="Bucket Region"
|
||||
required
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.accessKeyId"
|
||||
:placeholder="t('harvester.setting.placeholder.accessKeyId')"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="Access Key ID"
|
||||
required
|
||||
/>
|
||||
|
||||
<Password
|
||||
v-model="parseDefaultValue.secretAccessKey"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
:placeholder="t('harvester.setting.placeholder.secretAccessKey')"
|
||||
label="Secret Access Key"
|
||||
required
|
||||
/>
|
||||
|
||||
<LabeledSelect v-model="parseDefaultValue.virtualHostedStyle" class="mb-20" label="Virtual Hosted-Style" :options="virtualHostedStyleType" @input="update" />
|
||||
|
||||
<div class="mb-20">
|
||||
<Tip icon="icon icon-info">
|
||||
<MessageLink
|
||||
:to="toCA"
|
||||
target="_blank"
|
||||
prefix-label="harvester.setting.message.ca.prefix"
|
||||
middle-label="harvester.setting.message.ca.middle"
|
||||
suffix-label="harvester.setting.message.ca.suffix"
|
||||
/>
|
||||
</Tip>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-h-question {
|
||||
font-size: 24px;
|
||||
}
|
||||
.tip {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.goCA {
|
||||
margin: 0 3px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,377 @@
|
||||
<script>
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import KeyValue from '@shell/components/form/KeyValue';
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
import { clone } from '@shell/utils/object';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterContainerdRegistry',
|
||||
|
||||
components: {
|
||||
InfoBox,
|
||||
KeyValue,
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
|
||||
registerBeforeHook: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
const originMirror = {
|
||||
Endpoints: [],
|
||||
Rewrites: {}
|
||||
};
|
||||
|
||||
const originConfig = {
|
||||
Auth: {
|
||||
Username: '',
|
||||
Password: '',
|
||||
Auth: '',
|
||||
IdentityToken: ''
|
||||
},
|
||||
TLS: { InsecureSkipVerify: false }
|
||||
};
|
||||
|
||||
let originValue = {};
|
||||
const baseValue = {
|
||||
Mirrors: { '': clone(originMirror) },
|
||||
Configs: {}
|
||||
};
|
||||
|
||||
try {
|
||||
originValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
originValue = baseValue;
|
||||
}
|
||||
|
||||
if (!Object.keys(originValue).length) {
|
||||
originValue = baseValue;
|
||||
}
|
||||
|
||||
const _mirrors = originValue.Mirrors || {};
|
||||
const _configs = originValue.Configs || {};
|
||||
const mirrorsKeys = Object.keys(_mirrors);
|
||||
const configsKeys = Object.keys(_configs);
|
||||
const mirrors = mirrorsKeys.map((key) => {
|
||||
return {
|
||||
key,
|
||||
value: originValue.Mirrors[key],
|
||||
idx: randomStr(5).toLowerCase()
|
||||
};
|
||||
});
|
||||
|
||||
const configs = configsKeys.map((key) => {
|
||||
if (!originValue.Configs[key]?.Auth) {
|
||||
originValue.Configs[key].Auth = originConfig.Auth;
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
value: originValue.Configs[key],
|
||||
idx: randomStr(5).toLowerCase()
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
mirrors,
|
||||
configs,
|
||||
originMirror,
|
||||
originConfig,
|
||||
mirrorsKeys,
|
||||
configsKeys,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.willSave, 'willSave');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
insecureSkipVerifyOption() {
|
||||
return [{
|
||||
label: 'True',
|
||||
value: true
|
||||
}, {
|
||||
label: 'False',
|
||||
value: false
|
||||
}];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
willSave() {
|
||||
const errors = [];
|
||||
|
||||
if (this.value.value) {
|
||||
try {
|
||||
JSON.parse(this.value.value);
|
||||
|
||||
this.mirrors.forEach((mirror) => {
|
||||
if (!mirror.key) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.containerdRegistry.mirrors.registryName') }, true));
|
||||
}
|
||||
|
||||
if (mirror.value.Endpoints.length === 0) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.containerdRegistry.mirrors.endpoints') }, true));
|
||||
}
|
||||
});
|
||||
|
||||
this.configs.forEach((config) => {
|
||||
if (!config.key) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.containerdRegistry.configs.registryEDQNorIP') }, true));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
update() {
|
||||
const _mirrors = {};
|
||||
const _configs = {};
|
||||
|
||||
this.mirrors.forEach((mirror) => {
|
||||
_mirrors[mirror.key] = mirror.value;
|
||||
});
|
||||
|
||||
this.configs.forEach((config) => {
|
||||
_configs[config.key] = config.value;
|
||||
});
|
||||
|
||||
const out = {
|
||||
Mirrors: _mirrors,
|
||||
Configs: _configs
|
||||
};
|
||||
|
||||
if (!Object.keys(_mirrors).length) {
|
||||
delete out.Mirrors;
|
||||
}
|
||||
|
||||
if (!Object.keys(_configs).length) {
|
||||
delete out.Configs;
|
||||
}
|
||||
|
||||
const value = Object.keys(out).length ? JSON.stringify(out) : '';
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
},
|
||||
|
||||
addMirror() {
|
||||
this.mirrors.push({
|
||||
key: '', value: clone(this.originMirror), idx: randomStr(5).toLowerCase()
|
||||
});
|
||||
this.update();
|
||||
},
|
||||
|
||||
addConfig() {
|
||||
this.configs.push({
|
||||
key: '', value: clone(this.originConfig), idx: randomStr(5).toLowerCase()
|
||||
});
|
||||
this.update();
|
||||
},
|
||||
|
||||
remove(type, idx) {
|
||||
this[type].splice(idx, 1);
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(value) {
|
||||
if (!value.value) { // useDefaultVale
|
||||
this.$set(this, 'mirrors', []);
|
||||
this.$set(this, 'configs', []);
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ t('harvester.setting.containerdRegistry.mirrors.mirrors') }}</h3>
|
||||
<div>
|
||||
<InfoBox v-for="mirror, idx in mirrors" :key="mirror.idx" class="box">
|
||||
<button type="button" class="role-link btn btn-sm remove" @click="remove('mirrors', idx)">
|
||||
<i class="icon icon-2x icon-x" />
|
||||
</button>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<LabeledInput
|
||||
v-model="mirror.key"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.setting.containerdRegistry.mirrors.registryName"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-20">
|
||||
<LabeledSelect
|
||||
:key="mirror.idx"
|
||||
v-model="mirror.value.Endpoints"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.setting.containerdRegistry.mirrors.endpoints"
|
||||
:multiple="true"
|
||||
:taggable="true"
|
||||
:searchable="true"
|
||||
:options="[]"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<KeyValue
|
||||
v-model="mirror.value.Rewrites"
|
||||
:add-label="t('harvester.setting.containerdRegistry.mirrors.rewrite.addRewrite')"
|
||||
:mode="mode"
|
||||
:title="t('harvester.setting.containerdRegistry.mirrors.rewrite.rewrite')"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</infobox>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-sm role-primary" @click.self="addMirror">
|
||||
{{ t('harvester.setting.containerdRegistry.mirrors.addMirror') }}
|
||||
</button>
|
||||
|
||||
<hr class="divider mt-20 mb-20" />
|
||||
|
||||
<h3>{{ t('harvester.setting.containerdRegistry.configs.configs') }}</h3>
|
||||
<div>
|
||||
<InfoBox v-for="config, idx in configs" :key="config.idx" class="box">
|
||||
<button type="button" class="role-link btn btn-sm remove" @click="remove('configs', idx)">
|
||||
<i class="icon icon-2x icon-x" />
|
||||
</button>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<div class="col span-12">
|
||||
<LabeledInput
|
||||
v-model="config.key"
|
||||
:mode="mode"
|
||||
:placeholder="t('harvester.setting.containerdRegistry.configs.registryPlaceholder')"
|
||||
label-key="harvester.setting.containerdRegistry.configs.registryEDQNorIP"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model="config.value.Auth.Username"
|
||||
:mode="mode"
|
||||
label-key="harvester.setting.containerdRegistry.configs.username"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model="config.value.Auth.Password"
|
||||
:mode="mode"
|
||||
label-key="harvester.setting.containerdRegistry.configs.password"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model="config.value.Auth.Auth"
|
||||
:mode="mode"
|
||||
type="multiline"
|
||||
:min-height="150"
|
||||
label-key="harvester.setting.containerdRegistry.configs.auth"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model="config.value.Auth.IdentityToken"
|
||||
:mode="mode"
|
||||
type="multiline"
|
||||
:min-height="150"
|
||||
label-key="harvester.setting.containerdRegistry.configs.identityToken"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<LabeledSelect
|
||||
v-model="config.value.TLS.InsecureSkipVerify"
|
||||
:mode="mode"
|
||||
label-key="harvester.setting.containerdRegistry.configs.insecureSkipVerify"
|
||||
:options="insecureSkipVerifyOption"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</infobox>
|
||||
|
||||
<button class="btn btn-sm role-primary" @click="addConfig">
|
||||
{{ t('harvester.setting.containerdRegistry.configs.addConfig') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
position: relative;
|
||||
padding-top: 40px;
|
||||
}
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
<script>
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
import { CSI_DRIVER, VOLUME_SNAPSHOT_CLASS } from '../../types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
|
||||
const LONGHORN_DRIVER = 'driver.longhorn.io';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterCsiDriver',
|
||||
|
||||
components: {
|
||||
InfoBox,
|
||||
LabeledSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
|
||||
registerBeforeHook: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const hash = {
|
||||
csiDrivers: this.$store.dispatch(`${ inStore }/findAll`, { type: CSI_DRIVER }),
|
||||
volumeSnapshotClass: this.$store.dispatch(`${ inStore }/findAll`, { type: VOLUME_SNAPSHOT_CLASS })
|
||||
};
|
||||
|
||||
await allHash(hash);
|
||||
},
|
||||
|
||||
data() {
|
||||
const initValue = this.value.value || this.value.default;
|
||||
const configArr = this.parseValue(initValue);
|
||||
|
||||
return { configArr };
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.willSave, 'willSave');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
provisioners() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
||||
|
||||
return csiDrivers.filter((provisioner) => {
|
||||
return !this.configArr.map(config => config.key).includes(provisioner.name);
|
||||
}).map((provisioner) => {
|
||||
return provisioner.name;
|
||||
});
|
||||
},
|
||||
|
||||
disableAdd() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
||||
|
||||
return this.configArr.length >= csiDrivers.length;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getVolumeSnapshotOptions(provisioner) {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const volumeSnapshotClass = this.$store.getters[`${ inStore }/all`](VOLUME_SNAPSHOT_CLASS) || [];
|
||||
|
||||
return volumeSnapshotClass.filter((snapshot) => {
|
||||
return snapshot.driver === provisioner;
|
||||
}).map((snapshot) => {
|
||||
return {
|
||||
label: snapshot.name,
|
||||
value: snapshot.name
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
parseValue(value) {
|
||||
const out = [];
|
||||
let csiConfigJson = {};
|
||||
|
||||
try {
|
||||
csiConfigJson = JSON.parse(value);
|
||||
} catch (e) {
|
||||
new Error('json error');
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(csiConfigJson)) {
|
||||
out.push({
|
||||
key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
update() {
|
||||
const out = {};
|
||||
|
||||
this.configArr.map((config) => {
|
||||
out[config.key] = config.value;
|
||||
});
|
||||
|
||||
const value = this.configArr.length ? JSON.stringify(out) : '';
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
},
|
||||
|
||||
willSave() {
|
||||
this.update();
|
||||
const errors = [];
|
||||
|
||||
try {
|
||||
this.configArr.forEach((config) => {
|
||||
if (!config.key) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.provisioner') }, true));
|
||||
}
|
||||
|
||||
if (!config.value.volumeSnapshotClassName) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.volumeSnapshotClassName') }, true));
|
||||
}
|
||||
|
||||
if (!config.value.backupVolumeSnapshotClassName) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName') }, true));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
remove(idx) {
|
||||
this.configArr.splice(idx, 1);
|
||||
},
|
||||
|
||||
disableEdit(driver) {
|
||||
return driver === LONGHORN_DRIVER;
|
||||
},
|
||||
|
||||
add() {
|
||||
this.configArr.push({
|
||||
key: '',
|
||||
value: { volumeSnapshotClassName: '', backupVolumeSnapshotClassName: '' }
|
||||
});
|
||||
},
|
||||
|
||||
useDefault() {
|
||||
const configArr = this.parseValue(this.value.default);
|
||||
|
||||
this.$set(this, 'configArr', configArr);
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<InfoBox v-for="(driver, idx) in configArr" :key="driver.key" class="box">
|
||||
<button :disabled="disableEdit(driver.key)" type="button" class="role-link btn btn-sm remove" @click="remove(idx)">
|
||||
<i class="icon icon-x" />
|
||||
</button>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-4">
|
||||
<LabeledSelect
|
||||
v-model="driver.key"
|
||||
:mode="mode"
|
||||
required
|
||||
:disabled="disableEdit(driver.key)"
|
||||
label-key="harvester.setting.csiDriverConfig.provisioner"
|
||||
:searchable="true"
|
||||
:options="provisioners"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col span-4">
|
||||
<LabeledSelect
|
||||
v-model="driver.value.volumeSnapshotClassName"
|
||||
:mode="mode"
|
||||
required
|
||||
:disabled="disableEdit(driver.key)"
|
||||
:options="getVolumeSnapshotOptions(driver.key)"
|
||||
:label="t('harvester.setting.csiDriverConfig.volumeSnapshotClassName')"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col span-4">
|
||||
<LabeledSelect
|
||||
v-model="driver.value.backupVolumeSnapshotClassName"
|
||||
:mode="mode"
|
||||
required
|
||||
:disabled="disableEdit(driver.key)"
|
||||
:options="getVolumeSnapshotOptions(driver.key)"
|
||||
:label="t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName')"
|
||||
@keydown.native.enter.prevent="()=>{}"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</infobox>
|
||||
|
||||
<button class="btn btn-sm role-primary" :disabled="disableAdd" @click.self="add">
|
||||
{{ t('generic.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
position: relative;
|
||||
padding-top: 40px;
|
||||
}
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import UnitInput from '@shell/components/form/UnitInput';
|
||||
|
||||
export default {
|
||||
name: 'DefaultVMTerminationGracePeriodSeconds',
|
||||
|
||||
components: { UnitInput },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
return { terminationGracePeriodSeconds: this.value.value || this.value.default };
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.$set(this.value, 'value', String(this.terminationGracePeriodSeconds));
|
||||
},
|
||||
|
||||
useDefault() {
|
||||
this.$set(this, 'terminationGracePeriodSeconds', Number(this.value.default));
|
||||
this.update();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div class="col span-12">
|
||||
<UnitInput
|
||||
v-model="terminationGracePeriodSeconds"
|
||||
:suffix="terminationGracePeriodSeconds === 1 ? 'Second' : 'Seconds'"
|
||||
:label="t('harvester.virtualMachine.terminationGracePeriodSeconds.label')"
|
||||
:mode="mode"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
|
||||
import { Banner } from '@components/Banner';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterHttpProxy',
|
||||
|
||||
components: { Banner, LabeledInput },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return {
|
||||
parseDefaultValue,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Banner color="warning">
|
||||
<t k="harvester.setting.httpProxy.warning" :raw="true" />
|
||||
</Banner>
|
||||
|
||||
<div class="row" @input="update">
|
||||
<div class="col span-12">
|
||||
<template>
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.httpProxy"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="http-proxy"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.httpsProxy"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="https-proxy"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.noProxy"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label="no-proxy"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import ArrayList from '@shell/components/form/ArrayList.vue';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterNtpServersConfig',
|
||||
|
||||
components: { ArrayList },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = { ntpServers: [] };
|
||||
}
|
||||
|
||||
return { parseDefaultValue };
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
let parseDefaultValue;
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(neu.value);
|
||||
} catch (err) {
|
||||
parseDefaultValue = { ntpServers: [] };
|
||||
}
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
this.update();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
useDefault() {
|
||||
const parseDefaultValue = { ntpServers: [] };
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<template>
|
||||
<ArrayList
|
||||
v-model="parseDefaultValue.ntpServers"
|
||||
:title="t('harvester.host.ntp.label')"
|
||||
:protip="t('harvester.host.ntp.tips')"
|
||||
:value-placeholder="t('harvester.host.ntp.placeholder')"
|
||||
:mode="mode"
|
||||
@input="update"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,94 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import UnitInput from '@shell/components/form/UnitInput';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterOvercommitConfig',
|
||||
|
||||
components: { UnitInput },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return {
|
||||
parseDefaultValue,
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<template>
|
||||
<UnitInput
|
||||
v-model="parseDefaultValue.cpu"
|
||||
v-int-number
|
||||
label-key="harvester.generic.cpu"
|
||||
suffix="%"
|
||||
:delay="0"
|
||||
required
|
||||
:mode="mode"
|
||||
class="mb-20"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<UnitInput
|
||||
v-model="parseDefaultValue.memory"
|
||||
v-int-number
|
||||
label-key="harvester.generic.memory"
|
||||
suffix="%"
|
||||
:delay="0"
|
||||
required
|
||||
:mode="mode"
|
||||
class="mb-20"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<UnitInput
|
||||
v-model="parseDefaultValue.storage"
|
||||
v-int-number
|
||||
label-key="harvester.generic.storage"
|
||||
suffix="%"
|
||||
:delay="0"
|
||||
required
|
||||
:mode="mode"
|
||||
class="mb-20"
|
||||
@input="update"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,117 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import FileSelector from '@shell/components/form/FileSelector';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterSSLCertificates',
|
||||
|
||||
components: { FileSelector },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return {
|
||||
parseDefaultValue,
|
||||
caFileName: '',
|
||||
publicCertificateFileName: '',
|
||||
privateKeyFileName: ''
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onKeySelectedCa(type, file) {
|
||||
const { name, value } = file;
|
||||
|
||||
this.$set(this.parseDefaultValue, type, value);
|
||||
this.$set(this, `${ type }FileName`, name);
|
||||
const _value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', _value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<div class="mb-10">
|
||||
{{ t('harvester.setting.sslCertificates.publicCertificate') }}
|
||||
</div>
|
||||
|
||||
<div class="chooseFile">
|
||||
<FileSelector
|
||||
:include-file-name="true"
|
||||
class="btn btn-sm bg-primary mr-20"
|
||||
label="Choose File"
|
||||
@selected="onKeySelectedCa('publicCertificate', $event)"
|
||||
/>
|
||||
<span :class="{ 'text-muted': !publicCertificateFileName }">{{ publicCertificateFileName ? publicCertificateFileName : t('harvester.generic.noFileChosen') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<div class="mb-10">
|
||||
{{ t('harvester.setting.sslCertificates.privateKey') }}
|
||||
</div>
|
||||
|
||||
<div class="chooseFile">
|
||||
<FileSelector
|
||||
:include-file-name="true"
|
||||
class="btn btn-sm bg-primary mr-20"
|
||||
label="Choose File"
|
||||
@selected="onKeySelectedCa('privateKey', $event)"
|
||||
/>
|
||||
<span :class="{ 'text-muted': !privateKeyFileName }">{{ privateKeyFileName ? privateKeyFileName : t('harvester.generic.noFileChosen') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-12">
|
||||
<div class="mb-10">
|
||||
{{ t('harvester.setting.sslCertificates.ca') }}
|
||||
</div>
|
||||
|
||||
<div class="chooseFile">
|
||||
<FileSelector
|
||||
:include-file-name="true"
|
||||
class="btn btn-sm bg-primary mr-20"
|
||||
label="Choose File"
|
||||
@selected="onKeySelectedCa('ca', $event)"
|
||||
/>
|
||||
<span :class="{ 'text-muted': !caFileName }">{{ caFileName ? caFileName : t('harvester.generic.noFileChosen') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chooseFile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script>
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterSslParameters',
|
||||
|
||||
components: {
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
|
||||
registerBeforeHook: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
let parsedDefaultValue = {};
|
||||
|
||||
try {
|
||||
parsedDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parsedDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
const protocols = parsedDefaultValue.protocols && (parsedDefaultValue.protocols || '').split(' ');
|
||||
|
||||
return {
|
||||
parsedDefaultValue: {
|
||||
protocols,
|
||||
ciphers: parsedDefaultValue.ciphers,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.willSave, 'willSave');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
protocolOptions() {
|
||||
return [{
|
||||
label: 'TLSv1.3',
|
||||
value: 'TLSv1.3',
|
||||
}, {
|
||||
label: 'TLSv1.2',
|
||||
value: 'TLSv1.2',
|
||||
}, {
|
||||
label: `TLSv1.1 (${ this.t('generic.deprecated') })`,
|
||||
value: 'TLSv1.1',
|
||||
}, {
|
||||
label: `TLSv1 (${ this.t('generic.deprecated') })`,
|
||||
value: 'TLSv1',
|
||||
}, {
|
||||
label: `SSLv3 (${ this.t('generic.deprecated') })`,
|
||||
value: 'SSLv3',
|
||||
}, {
|
||||
label: `SSLv2 (${ this.t('generic.deprecated') })`,
|
||||
value: 'SSLv2',
|
||||
}];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const out = {
|
||||
protocols: (this.parsedDefaultValue.protocols || []).join(' '),
|
||||
ciphers: this.parsedDefaultValue.ciphers,
|
||||
};
|
||||
|
||||
const value = JSON.stringify(out);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
},
|
||||
|
||||
willSave() {
|
||||
const errors = [];
|
||||
|
||||
const ciphers = this.parsedDefaultValue.ciphers;
|
||||
const protocols = this.parsedDefaultValue.protocols || [];
|
||||
|
||||
if (ciphers && protocols.length === 0) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.sslParameters.protocols.label') }, true));
|
||||
}
|
||||
|
||||
if (!ciphers && protocols.length > 0) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.sslParameters.ciphers.label') }, true));
|
||||
}
|
||||
|
||||
const regex = /^(:?[A-Z0-9]+(?:-[A-Z0-9]+)+)+$/gm;
|
||||
|
||||
if (ciphers && (!ciphers.match(regex) || ciphers.startsWith(':'))) {
|
||||
errors.push(this.t('validation.invalid', { key: this.t('harvester.sslParameters.ciphers.label') }, true));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'value.value': {
|
||||
handler(value) {
|
||||
if (value === this.value.default) {
|
||||
this.parsedDefaultValue.protocols = [];
|
||||
this.parsedDefaultValue.ciphers = '';
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-12">
|
||||
<LabeledSelect
|
||||
v-model="parsedDefaultValue.protocols"
|
||||
:mode="mode"
|
||||
label-key="harvester.sslParameters.protocols.label"
|
||||
:multiple="true"
|
||||
:options="protocolOptions"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-20">
|
||||
<div class="col span-12">
|
||||
<LabeledInput
|
||||
v-model="parsedDefaultValue.ciphers"
|
||||
:mode="mode"
|
||||
label-key="harvester.sslParameters.ciphers.label"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,240 @@
|
||||
<script>
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import ArrayList from '@shell/components/form/ArrayList';
|
||||
import { isValidCIDR } from '@shell/utils/validators/cidr';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { Banner } from '@components/Banner';
|
||||
import Tip from '@shell/components/Tip';
|
||||
import { HCI } from '../../types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { NODE } from '@shell/config/types';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditStorageNetwork',
|
||||
|
||||
components: {
|
||||
ArrayList,
|
||||
Tip,
|
||||
Banner,
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
RadioGroup
|
||||
},
|
||||
|
||||
props: {
|
||||
registerBeforeHook: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
await allHash({
|
||||
clusterNetworks: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER_NETWORK }),
|
||||
vlanStatus: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VLAN_STATUS }),
|
||||
nodes: this.$store.dispatch(`${ inStore }/findAll`, { type: NODE }),
|
||||
});
|
||||
},
|
||||
|
||||
data() {
|
||||
let parsedDefaultValue = {};
|
||||
let openVlan = false;
|
||||
|
||||
try {
|
||||
parsedDefaultValue = JSON.parse(this.value.value);
|
||||
openVlan = true;
|
||||
} catch (error) {
|
||||
parsedDefaultValue = {
|
||||
vlan: '',
|
||||
clusterNetwork: '',
|
||||
range: '',
|
||||
exclude: []
|
||||
};
|
||||
}
|
||||
const exclude = parsedDefaultValue?.exclude?.toString().split(',') || [];
|
||||
|
||||
return {
|
||||
openVlan,
|
||||
errors: [],
|
||||
exclude,
|
||||
parsedDefaultValue,
|
||||
defaultAddValue: ''
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.willSave, 'willSave');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
clusterNetworkOptions() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
||||
|
||||
return clusterNetworks.map((n) => {
|
||||
const disabled = !n.isReadyForStorageNetwork;
|
||||
|
||||
return {
|
||||
label: disabled ? `${ n.id } (${ this.t('generic.notReady') })` : n.id,
|
||||
value: n.id,
|
||||
disabled,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const exclude = this.exclude.filter(ip => ip);
|
||||
|
||||
if (Array.isArray(exclude) && exclude.length > 0) {
|
||||
this.parsedDefaultValue.exclude = exclude;
|
||||
} else {
|
||||
delete this.parsedDefaultValue.exclude;
|
||||
}
|
||||
|
||||
const valueString = JSON.stringify(this.parsedDefaultValue);
|
||||
|
||||
if (this.openVlan) {
|
||||
this.$set(this.value, 'value', valueString);
|
||||
} else {
|
||||
this.$set(this.value, 'value', '');
|
||||
}
|
||||
},
|
||||
|
||||
willSave() {
|
||||
this.update();
|
||||
const errors = [];
|
||||
|
||||
if (this.openVlan) {
|
||||
const valid = !!/^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([1-9]|[1-2]\d|3[0-2])$/.test(this.parsedDefaultValue.range);
|
||||
|
||||
if (!valid) {
|
||||
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
|
||||
}
|
||||
|
||||
if (!this.parsedDefaultValue.vlan) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
|
||||
}
|
||||
|
||||
if (!this.parsedDefaultValue.clusterNetwork) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.clusterNetwork') }, true));
|
||||
}
|
||||
|
||||
if (this.exclude) {
|
||||
const hasInvalidCIDR = this.exclude.find((cidr) => {
|
||||
return !isValidCIDR(cidr);
|
||||
});
|
||||
|
||||
if (hasInvalidCIDR) {
|
||||
errors.push(this.t('harvester.setting.storageNetwork.exclude.invalid', null, true));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="mode"
|
||||
@input="update"
|
||||
>
|
||||
<Banner color="warning">
|
||||
<t k="harvester.setting.storageNetwork.warning" :raw="true" />
|
||||
</Banner>
|
||||
|
||||
<RadioGroup
|
||||
v-model="openVlan"
|
||||
class="mb-20"
|
||||
name="model"
|
||||
:options="[true,false]"
|
||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<div v-if="openVlan">
|
||||
<LabeledInput
|
||||
v-model.number="parsedDefaultValue.vlan"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.setting.storageNetwork.vlan"
|
||||
/>
|
||||
|
||||
<LabeledSelect
|
||||
v-model="parsedDefaultValue.clusterNetwork"
|
||||
label-key="harvester.setting.storageNetwork.clusterNetwork"
|
||||
class="mb-20"
|
||||
required
|
||||
:options="clusterNetworkOptions"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parsedDefaultValue.range"
|
||||
class="mb-5"
|
||||
:mode="mode"
|
||||
required
|
||||
:placeholder="t('harvester.setting.storageNetwork.range.placeholder')"
|
||||
label-key="harvester.setting.storageNetwork.range.label"
|
||||
/>
|
||||
<Tip class="mb-20" icon="icon icon-info" :text="t('harvester.setting.storageNetwork.tip')">
|
||||
<t k="harvester.setting.storageNetwork.tip" :raw="true" />
|
||||
</Tip>
|
||||
|
||||
<ArrayList
|
||||
v-model="exclude"
|
||||
:show-header="true"
|
||||
:default-add-value="defaultAddValue"
|
||||
:mode="mode"
|
||||
:add-label="t('harvester.setting.storageNetwork.exclude.addIp')"
|
||||
@input="update"
|
||||
>
|
||||
<template v-slot:column-headers>
|
||||
<div class="box">
|
||||
<div class="key">
|
||||
{{ t('harvester.setting.storageNetwork.exclude.label') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:columns="scope">
|
||||
<div class="key">
|
||||
<input
|
||||
v-model="scope.row.value"
|
||||
:placeholder="t('harvester.setting.storageNetwork.exclude.placeholder')"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ArrayList>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,134 @@
|
||||
<script>
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterSupportBundleImage',
|
||||
|
||||
components: { LabeledInput, LabeledSelect },
|
||||
|
||||
props: {
|
||||
registerBeforeHook: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return { parseDefaultValue };
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.willSave, 'willSave');
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
imagePolicyOptions() {
|
||||
return [{
|
||||
label: this.t('generic.imagePullPolicy.always'),
|
||||
value: 'Always',
|
||||
}, {
|
||||
label: this.t('generic.imagePullPolicy.ifNotPresent'),
|
||||
value: 'IfNotPresent',
|
||||
}, {
|
||||
label: this.t('generic.imagePullPolicy.never'),
|
||||
value: 'Never'
|
||||
}];
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
},
|
||||
|
||||
willSave() {
|
||||
const errors = [];
|
||||
|
||||
if (!this.parseDefaultValue.repository) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.supportBundleImage.repo') }, true));
|
||||
}
|
||||
|
||||
if (!this.parseDefaultValue.tag) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.supportBundleImage.tag') }, true));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" @input="update">
|
||||
<div class="col span-12">
|
||||
<template>
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.repository"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.setting.supportBundleImage.repo"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-model="parseDefaultValue.tag"
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.setting.supportBundleImage.tag"
|
||||
/>
|
||||
|
||||
<LabeledSelect
|
||||
v-model="parseDefaultValue.imagePullPolicy"
|
||||
class="mb-20"
|
||||
required
|
||||
label-key="harvester.setting.supportBundleImage.imagePullPolicy"
|
||||
:options="imagePolicyOptions"
|
||||
@input="update"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script>
|
||||
import { NAMESPACE } from '@shell/config/types';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterBundleNamespaces',
|
||||
|
||||
components: { LabeledSelect },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
async fetch() {
|
||||
await this.$store.dispatch('harvester/findAll', { type: NAMESPACE });
|
||||
},
|
||||
|
||||
data() {
|
||||
let namespaces = [];
|
||||
const namespacesStr = this.value?.value || this.value?.default || '';
|
||||
|
||||
if (namespacesStr) {
|
||||
namespaces = namespacesStr.split(',');
|
||||
}
|
||||
|
||||
return { namespaces };
|
||||
},
|
||||
|
||||
computed: {
|
||||
namespaceOptions() {
|
||||
return this.$store.getters['harvester/all'](NAMESPACE).map((N) => {
|
||||
return {
|
||||
label: N.id,
|
||||
value: N.id
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const namespaceStr = this.namespaces.join(',');
|
||||
|
||||
this.$set(this.value, 'value', namespaceStr);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'value.value': {
|
||||
handler(neu) {
|
||||
if (neu === this.value.default || !neu) {
|
||||
this.namespaces = [];
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<LabeledSelect
|
||||
v-model="namespaces"
|
||||
:multiple="true"
|
||||
label-key="nameNsDescription.namespace.label"
|
||||
:mode="mode"
|
||||
:options="namespaceOptions"
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,81 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterVMForceDeletePolicy',
|
||||
|
||||
components: { LabeledInput, RadioGroup },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
let parseDefaultValue = {};
|
||||
|
||||
try {
|
||||
parseDefaultValue = JSON.parse(this.value.value);
|
||||
} catch (error) {
|
||||
parseDefaultValue = JSON.parse(this.value.default);
|
||||
}
|
||||
|
||||
return { parseDefaultValue };
|
||||
},
|
||||
|
||||
created() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const value = JSON.stringify(this.parseDefaultValue);
|
||||
|
||||
this.$set(this.value, 'value', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const parseDefaultValue = JSON.parse(neu.value);
|
||||
|
||||
this.$set(this, 'parseDefaultValue', parseDefaultValue);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" @input="update">
|
||||
<div class="col span-12">
|
||||
<RadioGroup
|
||||
v-model="parseDefaultValue.enable"
|
||||
class="mb-20"
|
||||
name="model"
|
||||
:options="[true,false]"
|
||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||
@input="update"
|
||||
/>
|
||||
|
||||
<LabeledInput
|
||||
v-if="parseDefaultValue.enable"
|
||||
v-model.number="parseDefaultValue.period"
|
||||
v-int-number
|
||||
class="mb-20"
|
||||
:mode="mode"
|
||||
label-key="harvester.setting.vmForceDeletionPolicy.period"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .radio-group {
|
||||
display: flex;
|
||||
.radio-container {
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user