mirror of https://github.com/rancher/dashboard.git
Adding the vSphere maching-config
Also fixed a bug with the vsphere credentials. rancher/dashboard#2969 rancher/dashboard#3326
This commit is contained in:
parent
7aff3b8358
commit
52244cf2dd
|
|
@ -140,6 +140,7 @@ product:
|
||||||
suffix:
|
suffix:
|
||||||
percent: "%"
|
percent: "%"
|
||||||
milliCpus: mili CPUs
|
milliCpus: mili CPUs
|
||||||
|
cores: Cores
|
||||||
cpus: CPUs
|
cpus: CPUs
|
||||||
ib: iB
|
ib: iB
|
||||||
mib: MiB
|
mib: MiB
|
||||||
|
|
@ -1020,6 +1021,83 @@ cluster:
|
||||||
=1 {# vCPU}
|
=1 {# vCPU}
|
||||||
other {# vCPUs}
|
other {# vCPUs}
|
||||||
}, {disk} GB Disk ({value})
|
}, {disk} GB Disk ({value})
|
||||||
|
vsphere:
|
||||||
|
hostOptions:
|
||||||
|
any: Any
|
||||||
|
vAppOptions:
|
||||||
|
label: vApp Options
|
||||||
|
description: Choose OVF environment properties
|
||||||
|
disable: Do not use vApp
|
||||||
|
auto: Use vApp to configure networks with network protocol profiles
|
||||||
|
manual: Provide a custom vApp config
|
||||||
|
restoreType: Restore Type
|
||||||
|
transport:
|
||||||
|
label: OVF environment transport
|
||||||
|
tooltip: com.vmware.guestInfo or iso
|
||||||
|
placeholder: e.g. com.vmware.guestInfo
|
||||||
|
protocol:
|
||||||
|
label: vApp IP protocol
|
||||||
|
tooltip: IPv4 or IPv6
|
||||||
|
placeholder: e.g. IPv4
|
||||||
|
allocation:
|
||||||
|
label: vApp IP allocation policy
|
||||||
|
tooltip: dhcp, fixed, transient or fixedAllocated
|
||||||
|
placeholder: e.g. fixedAllocated
|
||||||
|
properties:
|
||||||
|
label: vApp properties
|
||||||
|
add: Add Property
|
||||||
|
keyPlaceholder: e.g. guestinfo.interface.0.ip.0.address
|
||||||
|
valuePlaceholder: e.g. ip:VM Network, expression or string
|
||||||
|
networks:
|
||||||
|
label: Networks
|
||||||
|
add: Add Network
|
||||||
|
guestinfo:
|
||||||
|
label: Configuration Parameters used for guestinfo
|
||||||
|
add: Add Parameter
|
||||||
|
keyPlaceholder: e.g. guestinfo.hostname
|
||||||
|
valuePlaceholder: e.g. myrancherhost
|
||||||
|
creationMethods:
|
||||||
|
template: 'Deploy from template: Data Center'
|
||||||
|
library: 'Deploy from template: Content Library'
|
||||||
|
vm: 'Clone an existing virtual machine'
|
||||||
|
legacy: 'Install from boot2docker ISO (Legacy)'
|
||||||
|
scheduling:
|
||||||
|
label: Scheduling
|
||||||
|
description: Choose what hypervisor the virtual machine will be scheduled to
|
||||||
|
dataCenter: Data Center
|
||||||
|
resourcePool: Resource Pool
|
||||||
|
dataStore: Data Store
|
||||||
|
folder: Folder
|
||||||
|
host:
|
||||||
|
label: Host
|
||||||
|
note: Specific host to create VM on (leave blank for standalone ESXi or for cluster with DRS)
|
||||||
|
instanceOptions:
|
||||||
|
label: Instance Options
|
||||||
|
description: Choose the size and OS of the virtual machine
|
||||||
|
cpus: CPUs
|
||||||
|
memory: Memory
|
||||||
|
disk: Disk
|
||||||
|
creationMethod: Creation method
|
||||||
|
template: Template
|
||||||
|
contentLibrary: Content library
|
||||||
|
libraryTemplate: Library template
|
||||||
|
virtualMachine: Virtual machine
|
||||||
|
osIsoUrl:
|
||||||
|
label: OS ISO URL
|
||||||
|
placeholder: 'Default: Latest rancheros-vmware image'
|
||||||
|
cloudInit:
|
||||||
|
label: Cloud Init
|
||||||
|
placeholder: e.g. http://my_host/cloud-config.yml
|
||||||
|
note: Cloud-init file or url to set in the guestinfo
|
||||||
|
cloudConfigYaml: Cloud Config YAML
|
||||||
|
tags:
|
||||||
|
label: Tags
|
||||||
|
description: Tags allow you to attach metadata to objects in the vSphere inventory to make it easier to sort and search for these objects.
|
||||||
|
addTag: Add Tag
|
||||||
|
customAttributes:
|
||||||
|
label: Custom attributes (legacy)
|
||||||
|
description: Custom attributes allow you to attach metadata to objects in the vSphere inventory to make it easier to sort and search for these objects.
|
||||||
|
add: Add custom attribute
|
||||||
|
|
||||||
machinePool:
|
machinePool:
|
||||||
name:
|
name:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,15 @@ export default {
|
||||||
components: { LabeledInput },
|
components: { LabeledInput },
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
deep: true,
|
||||||
|
handler(neu) {
|
||||||
|
this.$emit('validationChanged', !!neu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
test() {
|
test() {
|
||||||
// Vsphere doesn't have a test function. The credential has to be created before we can make calls.
|
// Vsphere doesn't have a test function. The credential has to be created before we can make calls.
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,11 @@ export default {
|
||||||
type: [String, Number, Object, Array],
|
type: [String, Number, Object, Array],
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -280,8 +285,8 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showAdd && !isView" class="footer">
|
<div v-if="showAdd && !isView" class="footer">
|
||||||
<slot v-if="showAdd" name="add">
|
<slot v-if="showAdd" name="add">
|
||||||
<button type="button" class="btn role-tertiary add" @click="add()">
|
<button type="button" class="btn role-tertiary add" :disabled="loading" @click="add()">
|
||||||
{{ addLabel }}
|
<i v-if="loading" class="mr-5 icon icon-spinner icon-spin icon-lg" /> {{ addLabel }}
|
||||||
</button>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script>
|
||||||
|
import ArrayList from '@/components/form/ArrayList';
|
||||||
|
import Select from '@/components/form/Select';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { ArrayList, Select },
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
default: null,
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
selectProps: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
arrayListProps: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredOptions() {
|
||||||
|
return this.options
|
||||||
|
.filter(option => !this.value.includes(option.value));
|
||||||
|
},
|
||||||
|
|
||||||
|
addAllowed() {
|
||||||
|
return this.filteredOptions.length > 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateRow(index, value) {
|
||||||
|
this.value.splice(index, 1, value);
|
||||||
|
this.$emit(value);
|
||||||
|
},
|
||||||
|
calculateOptions(value) {
|
||||||
|
const valueOption = this.options.find(o => o.value === value);
|
||||||
|
|
||||||
|
if (valueOption) {
|
||||||
|
return [valueOption, ...this.filteredOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.filteredOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ArrayList
|
||||||
|
v-bind="arrayListProps"
|
||||||
|
:value="value"
|
||||||
|
class="array-list-select"
|
||||||
|
:add-allowed="addAllowed || loading"
|
||||||
|
:loading="loading"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<template v-slot:columns="scope">
|
||||||
|
<Select :value="scope.row.value" v-bind="selectProps" :options="calculateOptions(scope.row.value)" @input="updateRow(scope.i, $event)" />
|
||||||
|
</template>
|
||||||
|
</ArrayList>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
::v-deep .unlabeled-select {
|
||||||
|
height: 61px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -73,6 +73,11 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
keyOptionUnique: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
keyPlaceholder: {
|
keyPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default() {
|
default() {
|
||||||
|
|
@ -211,6 +216,10 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [': ', '='],
|
default: () => [': ', '='],
|
||||||
},
|
},
|
||||||
|
loading: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -279,6 +288,19 @@ export default {
|
||||||
containerStyle() {
|
containerStyle() {
|
||||||
return `grid-template-columns: repeat(${ 2 + this.extraColumns.length }, 1fr)${ this.removeAllowed ? ' 50px' : '' };`;
|
return `grid-template-columns: repeat(${ 2 + this.extraColumns.length }, 1fr)${ this.removeAllowed ? ' 50px' : '' };`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
usedKeyOptions() {
|
||||||
|
return this.rows.map(row => row[this.keyName]);
|
||||||
|
},
|
||||||
|
|
||||||
|
filteredKeyOptions() {
|
||||||
|
if (this.keyOptionUnique) {
|
||||||
|
return this.keyOptions
|
||||||
|
.filter(option => !this.usedKeyOptions.includes(option.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.keyOptions;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
|
@ -418,6 +440,16 @@ export default {
|
||||||
this.queueUpdate();
|
this.queueUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
calculateOptions(value) {
|
||||||
|
const valueOption = this.keyOptions.find(o => o.value === value);
|
||||||
|
|
||||||
|
if (valueOption) {
|
||||||
|
return [valueOption, ...this.filteredKeyOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.filteredKeyOptions;
|
||||||
|
},
|
||||||
|
|
||||||
get,
|
get,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -476,7 +508,8 @@ export default {
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
:taggable="keyTaggable"
|
:taggable="keyTaggable"
|
||||||
:options="keyOptions"
|
:options="calculateOptions(row[keyName])"
|
||||||
|
@input="queueUpdate"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
|
|
@ -545,8 +578,8 @@ export default {
|
||||||
|
|
||||||
<div v-if="(addAllowed || readAllowed) && !isView" class="footer">
|
<div v-if="(addAllowed || readAllowed) && !isView" class="footer">
|
||||||
<slot name="add" :add="add">
|
<slot name="add" :add="add">
|
||||||
<button v-if="addAllowed" type="button" class="btn role-tertiary add" @click="add()">
|
<button v-if="addAllowed" type="button" class="btn role-tertiary add" :disabled="loading || (keyOptions && filteredKeyOptions.length === 0)" @click="add()">
|
||||||
{{ addLabel }}
|
<i v-if="loading" class="mr-5 icon icon-spinner icon-spin icon-lg" /> {{ addLabel }}
|
||||||
</button>
|
</button>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
v-if="readAllowed"
|
v-if="readAllowed"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
|
loading: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
localizedLabel: {
|
localizedLabel: {
|
||||||
default: false,
|
default: false,
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
|
@ -62,7 +66,7 @@ export default {
|
||||||
selectable: {
|
selectable: {
|
||||||
default: (opt) => {
|
default: (opt) => {
|
||||||
if ( opt ) {
|
if ( opt ) {
|
||||||
if ( opt.disabled || opt.kind === 'group' || opt.kind === 'divider' ) {
|
if ( opt.disabled || opt.kind === 'group' || opt.kind === 'divider' || opt.loading ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +244,7 @@ export default {
|
||||||
:append-to-body="appendToBody"
|
:append-to-body="appendToBody"
|
||||||
:calculate-position="withPopper"
|
:calculate-position="withPopper"
|
||||||
:class="{ 'no-label': !(label || '').length }"
|
:class="{ 'no-label': !(label || '').length }"
|
||||||
:disabled="isView || disabled"
|
:disabled="isView || disabled || loading"
|
||||||
:get-option-key="
|
:get-option-key="
|
||||||
(opt) => (optionKey ? get(opt, optionKey) : getOptionLabel(opt))
|
(opt) => (optionKey ? get(opt, optionKey) : getOptionLabel(opt))
|
||||||
"
|
"
|
||||||
|
|
@ -252,7 +256,7 @@ export default {
|
||||||
:reduce="(x) => reduce(x)"
|
:reduce="(x) => reduce(x)"
|
||||||
:searchable="isSearchable"
|
:searchable="isSearchable"
|
||||||
:selectable="selectable"
|
:selectable="selectable"
|
||||||
:value="value != null ? value : ''"
|
:value="value != null && !loading ? value : ''"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
@search:blur="onBlur"
|
@search:blur="onBlur"
|
||||||
@search:focus="onFocus"
|
@search:focus="onFocus"
|
||||||
|
|
@ -274,6 +278,7 @@ export default {
|
||||||
<slot :name="slot" v-bind="scope" />
|
<slot :name="slot" v-bind="scope" />
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</v-select>
|
||||||
|
<i v-if="loading" class="icon icon-spinner icon-spin icon-lg" />
|
||||||
<LabeledTooltip
|
<LabeledTooltip
|
||||||
v-if="tooltip && !focused"
|
v-if="tooltip && !focused"
|
||||||
:hover="hoverTooltip"
|
:hover="hoverTooltip"
|
||||||
|
|
@ -285,6 +290,14 @@ export default {
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
.labeled-select {
|
.labeled-select {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.icon-spinner {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - .5em);
|
||||||
|
top: calc(50% - .5em);
|
||||||
|
}
|
||||||
|
|
||||||
.labeled-container {
|
.labeled-container {
|
||||||
padding: $input-padding-sm 0 1px $input-padding-sm;
|
padding: $input-padding-sm 0 1px $input-padding-sm;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,928 @@
|
||||||
|
<script>
|
||||||
|
import Loading from '@/components/Loading';
|
||||||
|
import CreateEditView from '@/mixins/create-edit-view';
|
||||||
|
import LabeledSelect from '@/components/form/LabeledSelect';
|
||||||
|
// import Checkbox from '@/components/form/Checkbox';
|
||||||
|
import { SECRET } from '@/config/types';
|
||||||
|
import { stringify } from '@/utils/error';
|
||||||
|
import Banner from '@/components/Banner';
|
||||||
|
import UnitInput from '@/components/form/UnitInput';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import RadioGroup from '@/components/form/RadioGroup';
|
||||||
|
import KeyValue from '@/components/form/KeyValue';
|
||||||
|
import LabeledInput from '@/components/form/LabeledInput';
|
||||||
|
import ArrayListSelect from '@/components/form/ArrayListSelect';
|
||||||
|
import YamlEditor from '@/components/YamlEditor';
|
||||||
|
import { get, set } from '@/utils/object';
|
||||||
|
import { integerString, keyValueStrings } from '@/utils/computed';
|
||||||
|
import { _CREATE } from '@/config/query-params';
|
||||||
|
|
||||||
|
const SENTINEL = '__SENTINEL__';
|
||||||
|
const VAPP_MODE = {
|
||||||
|
DISABLED: 'disabled',
|
||||||
|
AUTO: 'auto',
|
||||||
|
MANUAL: 'manual'
|
||||||
|
};
|
||||||
|
const CREATION_METHOD = {
|
||||||
|
TEMPLATE: 'template',
|
||||||
|
LIBRARY: 'library',
|
||||||
|
VM: 'vm',
|
||||||
|
LEGACY: 'legacy'
|
||||||
|
};
|
||||||
|
const BOOT_2_DOCKER_URL = 'https://releases.rancher.com/os/latest/rancheros-vmware.iso';
|
||||||
|
const INITIAL_VAPP_OPTIONS = {
|
||||||
|
vappIpprotocol: '',
|
||||||
|
vappIpallocationpolicy: '',
|
||||||
|
vappTransport: '',
|
||||||
|
vappProperty: []
|
||||||
|
};
|
||||||
|
const DEFAULT_CFGPARAM = ['disk.enableUUID=TRUE'];
|
||||||
|
|
||||||
|
const getDefaultVappOptions = (networks) => {
|
||||||
|
return {
|
||||||
|
vappIpprotocol: 'IPv4',
|
||||||
|
vappIpallocationpolicy: 'fixedAllocated',
|
||||||
|
vappTransport: 'com.vmware.guestInfo',
|
||||||
|
vappProperty: networksToVappProperties(networks)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const stringsToParams = (params, str) => {
|
||||||
|
const index = str.indexOf('=');
|
||||||
|
|
||||||
|
if ( index > -1 ) {
|
||||||
|
params.push({
|
||||||
|
key: str.slice(0, index),
|
||||||
|
value: str.slice(index + 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
const networksToVappProperties = (networks) => {
|
||||||
|
if (networks.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks.reduce(networkToVappProperties, [
|
||||||
|
`guestinfo.dns.servers=\${dns:${ networks[0] }}`,
|
||||||
|
`guestinfo.dns.domains=\${searchPath:${ networks[0] }}`
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const networkToVappProperties = (props, network, i) => {
|
||||||
|
const n = i.toString();
|
||||||
|
|
||||||
|
props.push(
|
||||||
|
`guestinfo.interface.${ n }.ip.0.address=ip:${ network }`,
|
||||||
|
`guestinfo.interface.${ n }.ip.0.netmask=\${netmask:${ network }}`,
|
||||||
|
`guestinfo.interface.${ n }.route.0.gateway=\${gateway:${ network }}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialVappMode = (c) => {
|
||||||
|
const vappProperty = c.vappProperty || [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!c.vappIpprotocol &&
|
||||||
|
!c.vappIpallocationpolicy &&
|
||||||
|
!c.vappTransport &&
|
||||||
|
vappProperty.length === 0
|
||||||
|
) {
|
||||||
|
return VAPP_MODE.DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = getDefaultVappOptions(c.network);
|
||||||
|
|
||||||
|
if (
|
||||||
|
c.vappIpprotocol === d.vappIpprotocol &&
|
||||||
|
c.vappIpallocationpolicy === d.vappIpallocationpolicy &&
|
||||||
|
c.vappTransport === d.vappTransport &&
|
||||||
|
vappProperty.length === d.vappProperty.length &&
|
||||||
|
vappProperty.join() === d.vappProperty.join()
|
||||||
|
) {
|
||||||
|
return VAPP_MODE.AUTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VAPP_MODE.MANUAL;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* passing 'datacenter' yields
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* datacenter() {
|
||||||
|
* return this.datacenterResults || [];
|
||||||
|
* },
|
||||||
|
* datacenterLoading() {
|
||||||
|
* return this.datacenterLoading === null;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function createOptionHelpers(name) {
|
||||||
|
return {
|
||||||
|
[name]() {
|
||||||
|
return this[`${ name }Results`] || [];
|
||||||
|
},
|
||||||
|
[`${ name }Loading`]() {
|
||||||
|
return this[`${ name }Results`] === null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ArrayListSelect, Card, KeyValue, Loading, LabeledInput, LabeledSelect, Banner, UnitInput, RadioGroup, YamlEditor
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [CreateEditView],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
credentialId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
this.errors = [];
|
||||||
|
|
||||||
|
this.credential = await this.$store.dispatch('management/find', { type: SECRET, id: this.cloudCredentialId });
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const vAppOptions = [
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.vAppOptions.disable'),
|
||||||
|
value: VAPP_MODE.DISABLED
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.vAppOptions.auto'),
|
||||||
|
value: VAPP_MODE.AUTO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.vAppOptions.manual'),
|
||||||
|
value: VAPP_MODE.MANUAL
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const creationMethods = [
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.creationMethods.template'),
|
||||||
|
value: CREATION_METHOD.TEMPLATE
|
||||||
|
},
|
||||||
|
// This is currently broken in the backend. Once fixed we can add this back
|
||||||
|
// {
|
||||||
|
// label: this.t('cluster.machineConfig.vsphere.creationMethods.library'),
|
||||||
|
// value: CREATION_METHOD.LIBRARY
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.creationMethods.vm'),
|
||||||
|
value: CREATION_METHOD.VM
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.creationMethods.legacy'),
|
||||||
|
value: CREATION_METHOD.LEGACY
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.mode === _CREATE) {
|
||||||
|
this.$set(this.value, 'creationType', creationMethods[0].value);
|
||||||
|
this.$set(this.value, 'cpuCount', '2');
|
||||||
|
this.$set(this.value, 'diskSize', '20000');
|
||||||
|
this.$set(this.value, 'memorySize', '2048');
|
||||||
|
this.$set(this.value, 'hostsystem', '');
|
||||||
|
this.$set(this.value, 'cloudConfig', '#cloud-config\n\n');
|
||||||
|
this.$set(this.value, 'cfgparam', DEFAULT_CFGPARAM);
|
||||||
|
this.$set(this.value, 'vappProperty', this.value.vappProperty);
|
||||||
|
Object.entries(INITIAL_VAPP_OPTIONS).forEach(([key, value]) => {
|
||||||
|
this.$set(this.value, key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
credential: null,
|
||||||
|
creationMethods,
|
||||||
|
dataCentersResults: null,
|
||||||
|
resourcePoolsResults: null,
|
||||||
|
dataStoresResults: null,
|
||||||
|
dataStoreClustersResults: null,
|
||||||
|
foldersResults: null,
|
||||||
|
hostsResults: null,
|
||||||
|
templatesResults: null,
|
||||||
|
contentLibrariesResults: null,
|
||||||
|
libraryTemplatesResults: null,
|
||||||
|
virtualMachinesResults: null,
|
||||||
|
showTemplatesResults: null,
|
||||||
|
tagsResults: null,
|
||||||
|
attributeKeysResults: null,
|
||||||
|
networksResults: null,
|
||||||
|
vAppOptions,
|
||||||
|
vappMode: getInitialVappMode(this.value)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...createOptionHelpers('dataCenters'),
|
||||||
|
...createOptionHelpers('resourcePools'),
|
||||||
|
...createOptionHelpers('dataStores'),
|
||||||
|
...createOptionHelpers('dataStoreClusters'),
|
||||||
|
...createOptionHelpers('folders'),
|
||||||
|
...createOptionHelpers('hosts'),
|
||||||
|
...createOptionHelpers('templates'),
|
||||||
|
...createOptionHelpers('contentLibraries'),
|
||||||
|
...createOptionHelpers('libraryTemplates'),
|
||||||
|
...createOptionHelpers('virtualMachines'),
|
||||||
|
...createOptionHelpers('showTemplates'),
|
||||||
|
...createOptionHelpers('tags'),
|
||||||
|
...createOptionHelpers('attributeKeys'),
|
||||||
|
...createOptionHelpers('networks'),
|
||||||
|
|
||||||
|
showTemplate() {
|
||||||
|
return this.value.creationType === 'template';
|
||||||
|
},
|
||||||
|
|
||||||
|
showContentLibrary() {
|
||||||
|
return this.value.creationType === 'library';
|
||||||
|
},
|
||||||
|
|
||||||
|
showVirtualMachines() {
|
||||||
|
return this.value.creationType === 'vm';
|
||||||
|
},
|
||||||
|
|
||||||
|
showIso() {
|
||||||
|
return this.value.creationType === 'legacy';
|
||||||
|
},
|
||||||
|
|
||||||
|
showManual() {
|
||||||
|
return this.vappMode === 'manual';
|
||||||
|
},
|
||||||
|
|
||||||
|
cloudCredentialId() {
|
||||||
|
return this.credentialId.split('/')[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
host: {
|
||||||
|
get() {
|
||||||
|
return this.value.hostsystem === '' ? SENTINEL : this.value.hostsystem;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const newValue = value === SENTINEL ? '' : value;
|
||||||
|
|
||||||
|
this.$set(this.value, 'hostsystem', newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
customAttribute: keyValueStrings('value.customAttribute'),
|
||||||
|
vappProperty: keyValueStrings('value.vappProperty'),
|
||||||
|
cfgparam: keyValueStrings('value.cfgparam'),
|
||||||
|
|
||||||
|
cpuCount: integerString('value.cpuCount'),
|
||||||
|
memorySize: integerString('value.memorySize'),
|
||||||
|
diskSize: integerString('value.diskSize'),
|
||||||
|
|
||||||
|
showCloudConfigYaml() {
|
||||||
|
return this.value.creationType !== 'legacy';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
cloudCredentialId() {
|
||||||
|
this.$fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
credential() {
|
||||||
|
this.loadDataCenters();
|
||||||
|
},
|
||||||
|
|
||||||
|
dataCentersResults() {
|
||||||
|
this.loadResourcePools();
|
||||||
|
this.loadDataStores();
|
||||||
|
this.loadFolders();
|
||||||
|
this.loadHosts();
|
||||||
|
this.loadTemplates();
|
||||||
|
this.loadTags();
|
||||||
|
this.loadCustomAttributes();
|
||||||
|
// This is currently broken in the backend. Once fixed we can add this back
|
||||||
|
// this.loadContentLibraries();
|
||||||
|
this.loadLibraryTemplates();
|
||||||
|
this.loadVirtualMachines();
|
||||||
|
this.loadNetworks();
|
||||||
|
},
|
||||||
|
|
||||||
|
'value.contentLibrary'() {
|
||||||
|
this.loadLibraryTemplates();
|
||||||
|
},
|
||||||
|
'value.creationType'(value) {
|
||||||
|
this.$set(this.value, 'cloneFrom', '');
|
||||||
|
const boot2dockerUrl = value === CREATION_METHOD.LEGACY ? BOOT_2_DOCKER_URL : '';
|
||||||
|
|
||||||
|
this.$set(this.value, 'boot2dockerUrl', boot2dockerUrl);
|
||||||
|
},
|
||||||
|
vappMode(value) {
|
||||||
|
if (value === VAPP_MODE.AUTO) {
|
||||||
|
const defaultVappOptions = getDefaultVappOptions(this.value.network);
|
||||||
|
|
||||||
|
return this.updateVappOptions(defaultVappOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateVappOptions(INITIAL_VAPP_OPTIONS);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
stringify,
|
||||||
|
async requestOptions(resource, secretId, dataCenter, library) {
|
||||||
|
const datacenterLessResources = ['tag-categories', 'tags', 'data-centers', 'custom-attributes'];
|
||||||
|
|
||||||
|
if (!secretId || (!datacenterLessResources.includes(resource) && !dataCenter)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = Object.entries({
|
||||||
|
secretId,
|
||||||
|
dataCenter,
|
||||||
|
library
|
||||||
|
})
|
||||||
|
.filter(entry => entry[1])
|
||||||
|
.map(entry => `${ entry[0] }=${ entry[1] }`)
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
const url = `/meta/vsphere/${ resource }?${ queryParams }`;
|
||||||
|
|
||||||
|
const result = await this.$store.dispatch('management/request', {
|
||||||
|
url,
|
||||||
|
redirectUnauthorized: false,
|
||||||
|
}, { root: true });
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadDataCenters() {
|
||||||
|
const options = await this.requestOptions('data-centers', this.cloudCredentialId);
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
const valueInContent = content.find(c => c.value === this.value.datacenter );
|
||||||
|
|
||||||
|
if (!valueInContent) {
|
||||||
|
this.$set(this.value, 'datacenter', options[0]);
|
||||||
|
this.$set(this.value, 'cloneFrom', undefined);
|
||||||
|
this.$set(this.value, 'useDataStoreCluster', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$set(this, 'dataCentersResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadTags() {
|
||||||
|
const categoriesPromise = this.requestOptions('tag-categories', this.cloudCredentialId);
|
||||||
|
const optionsPromise = this.requestOptions('tags', this.cloudCredentialId);
|
||||||
|
const [categories, options] = await Promise.all([categoriesPromise, optionsPromise]);
|
||||||
|
const content = this.mapTagsToContent(options).map(option => ({
|
||||||
|
...option,
|
||||||
|
category: categories.find(c => c.name === option.category)
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('tag', content, options, true);
|
||||||
|
|
||||||
|
this.$set(this, 'tagsResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadCustomAttributes() {
|
||||||
|
const options = await this.requestOptions('custom-attributes', this.cloudCredentialId);
|
||||||
|
|
||||||
|
this.$set(this, 'attributeKeysResults', this.mapCustomAttributesToContent(options));
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadHosts() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'hosts',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapHostOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('hostsystem', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'hostsResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadResourcePools() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'resource-pools',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = this.mapPoolOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('pool', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'resourcePoolsResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadDataStores() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'data-stores',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('datastore', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'dataStoresResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadDataStoreClusters() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'data-store-clusters',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('datastoreCluster', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'dataStoreClustersResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadFolders() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'folders',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapFolderOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('folder', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'foldersResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadNetworks() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'networks',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('network', content, options, true);
|
||||||
|
|
||||||
|
this.$set(this, 'networksResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadContentLibraries() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'content-libraries',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('contentLibrary', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'contentLibrariesResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadLibraryTemplates() {
|
||||||
|
const contentLibrary = this.value.contentLibrary;
|
||||||
|
|
||||||
|
if (!contentLibrary) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'library-templates',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
undefined,
|
||||||
|
contentLibrary
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('cloneFrom', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'libraryTemplatesResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadVirtualMachines() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'virtual-machines',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('cloneFrom', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'virtualMachinesResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadTemplates() {
|
||||||
|
const options = await this.requestOptions(
|
||||||
|
'templates',
|
||||||
|
this.cloudCredentialId,
|
||||||
|
this.value.datacenter
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = this.mapPathOptionsToContent(options);
|
||||||
|
|
||||||
|
this.resetValueIfNecessary('cloneFrom', content, options);
|
||||||
|
|
||||||
|
this.$set(this, 'templatesResults', content);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetValueIfNecessary(key, content, options, isArray = false) {
|
||||||
|
const isValueInContent = () => {
|
||||||
|
if (isArray) {
|
||||||
|
return this.value[key].every(value => content.find(c => c.value === value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return content.find(c => c.value === this.value[key] );
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isValueInContent()) {
|
||||||
|
const value = isArray ? [] : content[0]?.value;
|
||||||
|
|
||||||
|
this.$set(this.value, key, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mapPathOptionsToContent(pathOptions) {
|
||||||
|
return pathOptions.map((pathOption) => {
|
||||||
|
const split = pathOption.split('/');
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: split[split.length - 1],
|
||||||
|
value: pathOption
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
mapHostOptionsToContent(hostOptions) {
|
||||||
|
return this.mapPathOptionsToContent(hostOptions)
|
||||||
|
.map(c => c.value === '' ? {
|
||||||
|
label: this.t('cluster.machineConfig.vsphere.hostOptions.any'),
|
||||||
|
value: SENTINEL
|
||||||
|
} : c);
|
||||||
|
},
|
||||||
|
|
||||||
|
mapFolderOptionsToContent(folderOptions) {
|
||||||
|
return folderOptions.map(option => ({
|
||||||
|
label: option || '\u00A0',
|
||||||
|
value: option || ''
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
mapPoolOptionsToContent(pathOptions) {
|
||||||
|
return pathOptions.map((pathOption) => {
|
||||||
|
const splitOptions = pathOption.split('/');
|
||||||
|
const label = splitOptions.slice(2).join('/');
|
||||||
|
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
value: pathOption
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
mapCustomAttributesToContent(customAttributes) {
|
||||||
|
return customAttributes.map(customAttribute => ({
|
||||||
|
label: customAttribute.name,
|
||||||
|
value: customAttribute.key.toString()
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
mapTagsToContent(tags) {
|
||||||
|
return tags.map(tag => ({
|
||||||
|
...tag,
|
||||||
|
label: `${ tag.category } / ${ tag.name }`,
|
||||||
|
value: tag.id
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
initKeyValueParams(pairsKey, paramsKey) {
|
||||||
|
set(this, paramsKey, (get(this, pairsKey) || []).reduce(stringsToParams, []));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVappOptions(opts) {
|
||||||
|
this.$set(this.value, 'vappIpprotocol', opts.vappIpprotocol);
|
||||||
|
this.$set(this.value, 'vappIpallocationpolicy', opts.vappIpallocationpolicy);
|
||||||
|
this.$set(this.value, 'vappTransport', opts.vappTransport);
|
||||||
|
this.$set(this.value, 'vappProperty', opts.vappProperty);
|
||||||
|
this.initKeyValueParams('value.vappProperty', 'initVappArray');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="$fetchState.pending" :delayed="true" />
|
||||||
|
<div v-else-if="errors.length">
|
||||||
|
<div
|
||||||
|
v-for="(err, idx) in errors"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<Banner
|
||||||
|
color="error"
|
||||||
|
:label="stringify(err)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<Card class="m-0 mt-20" :show-highlight-border="false" :show-actions="false">
|
||||||
|
<h4 slot="title" class="text-default-text mb-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.scheduling.label') }}
|
||||||
|
<p class="text-muted text-small">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.scheduling.description') }}
|
||||||
|
</p>
|
||||||
|
</h4>
|
||||||
|
<div slot="body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.datacenter"
|
||||||
|
:loading="dataCentersLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="dataCenters"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.scheduling.dataCenter')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.pool"
|
||||||
|
:loading="resourcePoolsLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="resourcePools"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.scheduling.resourcePool')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.datastore"
|
||||||
|
:loading="dataStoresLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="dataStores"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.scheduling.dataStore')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.folder"
|
||||||
|
:loading="foldersLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="folders"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.scheduling.folder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-12">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="host"
|
||||||
|
:loading="hostsLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="hosts"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.scheduling.host.label')"
|
||||||
|
/>
|
||||||
|
<p class="text-muted mt-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.scheduling.host.note') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card class="m-0 mt-20" :show-highlight-border="false" :show-actions="false">
|
||||||
|
<h4 slot="title" class="text-default-text mb-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.instanceOptions.label') }}
|
||||||
|
<p class="text-muted text-small">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.instanceOptions.description') }}
|
||||||
|
</p>
|
||||||
|
</h4>
|
||||||
|
<div slot="body">
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-6">
|
||||||
|
<UnitInput
|
||||||
|
v-model="cpuCount"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.cpus')"
|
||||||
|
:suffix="t('suffix.cores')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<UnitInput
|
||||||
|
v-model="memorySize"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.memory')"
|
||||||
|
:suffix="t('suffix.mib')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-6">
|
||||||
|
<UnitInput
|
||||||
|
v-model="diskSize"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.disk')"
|
||||||
|
:suffix="t('suffix.mib')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col" :class="showContentLibrary ? 'span-4' : 'span-6'">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.creationType"
|
||||||
|
:mode="mode"
|
||||||
|
:options="creationMethods"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.creationMethod')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showTemplate" class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.cloneFrom"
|
||||||
|
:loading="templatesLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="templates"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.template')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showContentLibrary" class="col span-4">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.contentLibrary"
|
||||||
|
:loading="contentLibrariesLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="contentLibraries"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.contentLibrary')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showContentLibrary" class="col span-4">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.cloneFrom"
|
||||||
|
:loading="libraryTemplatesLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="libraryTemplates"
|
||||||
|
:searchable="true"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.libraryTemplate')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showVirtualMachines" class="col span-6">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model="value.cloneFrom"
|
||||||
|
:loading="virtualMachinesLoading"
|
||||||
|
:mode="mode"
|
||||||
|
:options="virtualMachines"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.virtualMachine')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="showIso" class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model="value.boot2dockerUrl"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.osIsoUrl.label')"
|
||||||
|
:placeholder="t('cluster.machineConfig.vsphere.instanceOptions.osIsoUrl.placeholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showIso" class="row mt-10">
|
||||||
|
<div class="col span-12">
|
||||||
|
<LabeledInput
|
||||||
|
v-model="value.cloudinit"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.instanceOptions.cloudInit.label')"
|
||||||
|
:placeholder="t('cluster.machineConfig.vsphere.instanceOptions.cloudInit.placeholder')"
|
||||||
|
/>
|
||||||
|
<p class="text-muted mt-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.instanceOptions.cloudInit.note') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showCloudConfigYaml" class="row mt-10">
|
||||||
|
<div class="col span-12">
|
||||||
|
<label class="text-label mt-0">{{ t('cluster.machineConfig.vsphere.instanceOptions.cloudConfigYaml') }}</label>
|
||||||
|
<YamlEditor
|
||||||
|
ref="yaml-additional"
|
||||||
|
v-model="value.cloudConfig"
|
||||||
|
:editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
|
||||||
|
initial-yaml-values="# Additional Manifest YAML"
|
||||||
|
class="yaml-editor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-12">
|
||||||
|
<label class="text-label mt-0">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.networks.label') }}
|
||||||
|
</label>
|
||||||
|
<ArrayListSelect v-model="value.network" :options="networks" :array-list-props="{ addLabel: t('cluster.machineConfig.vsphere.networks.add') }" :loading="networksLoading" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col span-12">
|
||||||
|
<label class="text-label mt-0">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.guestinfo.label') }}
|
||||||
|
</label>
|
||||||
|
<KeyValue v-model="cfgparam" :add-label="t('cluster.machineConfig.vsphere.guestinfo.add')" :key-placeholder="t('cluster.machineConfig.vsphere.guestinfo.keyPlaceholder')" :value-placeholder="t('cluster.machineConfig.vsphere.guestinfo.valuePlaceholder')" :read-allowed="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card class="m-0 mt-20" :show-highlight-border="false" :show-actions="false">
|
||||||
|
<h4 slot="title" class="text-default-text mb-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.tags.label') }}
|
||||||
|
<p class="text-muted text-small">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.tags.description') }}
|
||||||
|
</p>
|
||||||
|
</h4>
|
||||||
|
<div slot="body">
|
||||||
|
<ArrayListSelect v-model="value.tag" :options="tags" :array-list-props="{ addLabel: t('cluster.machineConfig.vsphere.tags.addTag') }" :loading="tagsLoading" />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card class="m-0 mt-20" :show-highlight-border="false" :show-actions="false">
|
||||||
|
<h4 slot="title" class="text-default-text mb-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.customAttributes.label') }}
|
||||||
|
<p class="text-muted text-small">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.customAttributes.description') }}
|
||||||
|
</p>
|
||||||
|
</h4>
|
||||||
|
<div slot="body">
|
||||||
|
<KeyValue
|
||||||
|
v-model="customAttribute"
|
||||||
|
:key-options="attributeKeys"
|
||||||
|
:options="tags"
|
||||||
|
:add-label="t('cluster.machineConfig.vsphere.customAttributes.add')"
|
||||||
|
:read-allowed="false"
|
||||||
|
:loading="attributeKeysLoading"
|
||||||
|
:key-taggable="false"
|
||||||
|
:key-option-unique="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card class="m-0 mt-20" :show-highlight-border="false" :show-actions="false">
|
||||||
|
<h4 slot="title" class="text-default-text mb-5">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.vAppOptions.label') }}
|
||||||
|
<p class="text-muted text-small">
|
||||||
|
{{ t('cluster.machineConfig.vsphere.vAppOptions.description') }}
|
||||||
|
</p>
|
||||||
|
</h4>
|
||||||
|
<div slot="body">
|
||||||
|
<div class="row mb-10">
|
||||||
|
<div class="col span-6">
|
||||||
|
<RadioGroup
|
||||||
|
v-model="vappMode"
|
||||||
|
name="restoreMode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.vAppOptions.restoreType')"
|
||||||
|
:options="vAppOptions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showManual" class="row mb-10">
|
||||||
|
<div class="col span-4">
|
||||||
|
<LabeledInput
|
||||||
|
v-model="value.vappTransport"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.vAppOptions.transport.label')"
|
||||||
|
:tooltip="t('cluster.machineConfig.vsphere.vAppOptions.transport.tooltip')"
|
||||||
|
:placeholder="t('cluster.machineConfig.vsphere.vAppOptions.transport.placeholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-4">
|
||||||
|
<LabeledInput
|
||||||
|
v-model="value.vappIpprotocol"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.vAppOptions.protocol.label')"
|
||||||
|
:tooltip="t('cluster.machineConfig.vsphere.vAppOptions.protocol.tooltip')"
|
||||||
|
:placeholder="t('cluster.machineConfig.vsphere.vAppOptions.protocol.placeholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-4">
|
||||||
|
<LabeledInput
|
||||||
|
v-model="value.vappIpallocationpolicy"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('cluster.machineConfig.vsphere.vAppOptions.allocation.label')"
|
||||||
|
:tooltip="t('cluster.machineConfig.vsphere.vAppOptions.allocation.tooltip')"
|
||||||
|
:placeholder="t('cluster.machineConfig.vsphere.vAppOptions.allocation.placeholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showManual" class="row">
|
||||||
|
<div class="col span-12">
|
||||||
|
<KeyValue
|
||||||
|
v-model="vappProperty"
|
||||||
|
:title="t('cluster.machineConfig.vsphere.vAppOptions.properties.label')"
|
||||||
|
:key-placeholder="t('cluster.machineConfig.vsphere.vAppOptions.properties.keyPlaceholder')"
|
||||||
|
:value-placeholder="t('cluster.machineConfig.vsphere.vAppOptions.properties.valuePlaceholder')"
|
||||||
|
:add-label="t('cluster.machineConfig.vsphere.vAppOptions.properties.add')"
|
||||||
|
:read-allowed="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
const { set, get } = require('@/utils/object');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a computed property that handles converting strings to numbers and numbers to strings. Particularly when dealing with UnitInput.
|
||||||
|
* @param {*} path The path of the real value
|
||||||
|
* @returns the computed property
|
||||||
|
*/
|
||||||
|
export function integerString(path) {
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
return Number.parseFloat(get(this, path));
|
||||||
|
},
|
||||||
|
|
||||||
|
set(value) {
|
||||||
|
set(this, path, value.toString(10));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a computed property that handles converting strings a list of strings that look like ['key=value'] into { key: value } and back
|
||||||
|
* @param {*} path The path of the real value
|
||||||
|
* @param {*} delimeter the character/s used between the key/value. Default value '='.
|
||||||
|
* @returns the computed property
|
||||||
|
*/
|
||||||
|
export function keyValueStrings(path, delimeter = '=') {
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
get(this, path).forEach((entry) => {
|
||||||
|
const [key, value] = entry.split(delimeter);
|
||||||
|
|
||||||
|
result[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const newValue = Object.entries(value).map(([key, value]) => `${ key }${ delimeter }${ value }`);
|
||||||
|
|
||||||
|
set(this, path, newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue