dashboard/shell/edit/provisioning.cattle.io.cluster/MachinePool.vue

356 lines
8.8 KiB
Vue

<script>
import { LabeledInput } from '@components/Form/LabeledInput';
import { Checkbox } from '@components/Form/Checkbox';
import { _EDIT } from '@shell/config/query-params';
import Taints from '@shell/components/form/Taints.vue';
import KeyValue from '@shell/components/form/KeyValue.vue';
import AdvancedSection from '@shell/components/AdvancedSection.vue';
import { Banner } from '@components/Banner';
import UnitInput from '@shell/components/form/UnitInput.vue';
import { randomStr } from '@shell/utils/string';
export default {
name: 'MachinePool',
components: {
LabeledInput,
Checkbox,
Taints,
KeyValue,
AdvancedSection,
Banner,
UnitInput
},
props: {
value: {
type: Object,
required: true,
},
cluster: {
type: Object,
default: () => ({})
},
// no credentials are required for elemental machine pools
credentialId: {
type: String,
default: null
},
mode: {
type: String,
default: _EDIT,
},
provider: {
type: String,
required: true,
},
idx: {
type: Number,
required: true,
},
machinePools: {
type: Array,
default: () => []
},
// Is the UI busy (e.g. during save)
busy: {
type: Boolean,
default: false,
}
},
data() {
const parseDuration = (duration) => {
// The back end stores the timeout in Duration format, for example, "42d31h10m30s".
// Here we convert that string to an integer and return the duration as seconds.
const splitStr = duration.split(/([a-z])/);
const durationsAsSeconds = splitStr.reduce((old, neu, idx) => {
const parsed = parseInt(neu);
if ( isNaN(parsed) ) {
return old;
}
const interval = splitStr[(idx + 1)];
switch (interval) {
case 'd':
old.push(parsed * 24 * 60 * 60);
break;
case 'h':
old.push(parsed * 60 * 60);
break;
case 'm':
old.push(parsed * 60);
break;
case 's':
old.push(parsed);
break;
default:
break;
}
return old;
}, []);
return durationsAsSeconds.reduce((old, neu) => old + neu);
};
return {
uuid: randomStr(),
unhealthyNodeTimeoutInteger: this.value.pool.unhealthyNodeTimeout ? parseDuration(this.value.pool.unhealthyNodeTimeout) : 0
};
},
computed: {
configComponent() {
if (this.$store.getters['type-map/hasCustomMachineConfigComponent'](this.provider)) {
return this.$store.getters['type-map/importMachineConfig'](this.provider);
}
return this.$store.getters['type-map/importMachineConfig']('generic');
},
isWindows() {
return this.value?.config?.os === 'windows';
},
},
watch: {
isWindows(neu) {
if (neu) {
this.value.pool.etcdRole = false;
this.value.pool.controlPlaneRole = false;
this.value.pool.machineOS = 'windows';
} else {
this.value.pool.machineOS = 'linux';
}
}
},
/**
* On creation, ensure that the pool is marked valid - custom machine pools can emit further validation events
*/
created() {
this.$emit('validationChanged', true);
},
beforeDestroy() {
// Ensure we emit validation event so parent can forget any validation for this Machine Pool when it is removed
this.$emit('validationChanged', undefined);
},
methods: {
async test() {
if ( typeof this.$refs.configComponent?.test === 'function' ) {
let errors = [];
try {
const res = await this.$refs.configComponent.test();
if ( !res || res?.errors) {
if (res?.errors) {
errors = res.errors;
}
}
} catch (e) {
errors = [e];
}
return errors;
}
},
// handle emitted matched machine inventories on selector so that machine count
// on machine pool can be kept up to date
// (only used on Elemental because it comes from "machineinventoryselectortemplate" machine-config)
updateMachineCount(val) {
this.value.pool.quantity = val || 1;
},
expandAdvanced() {
const advancedComponent = this.$refs.advanced;
if (advancedComponent && !advancedComponent.show) {
advancedComponent.toggle();
}
},
// Propagate up validation status for this Machine Pool
validationChanged(val) {
this.$emit('validationChanged', val);
}
}
};
</script>
<template>
<div>
<Banner
v-if="value.pool.hostnameLengthLimit"
color="info"
>
<div class="text">
{{ t('cluster.machinePool.truncationPool', { limit: value.pool.hostnameLengthLimit }) }}
</div>
</Banner>
<div class="row">
<div class="col span-4">
<LabeledInput
v-model="value.pool.name"
:mode="mode"
:label="t('cluster.machinePool.name.label')"
:required="true"
:disabled="!value.config || !!value.config.id || busy"
/>
</div>
<div class="col span-4">
<LabeledInput
v-model.number="value.pool.quantity"
:mode="mode"
:label="t('cluster.machinePool.quantity.label')"
:disabled="busy"
type="number"
min="0"
:required="true"
/>
</div>
<div class="col span-4 pt-5">
<h3>
{{ t('cluster.machinePool.role.label') }}
</h3>
<Checkbox
v-model="value.pool.etcdRole"
:mode="mode"
:label="t('cluster.machinePool.role.etcd')"
:disabled="isWindows || busy"
/>
<Checkbox
v-model="value.pool.controlPlaneRole"
:mode="mode"
:label="t('cluster.machinePool.role.controlPlane')"
:disabled="isWindows || busy"
/>
<Checkbox
v-model="value.pool.workerRole"
:mode="mode"
:label="t('cluster.machinePool.role.worker')"
:disabled="busy"
/>
</div>
</div>
<hr class="mt-10">
<component
:is="configComponent"
v-if="value.config && configComponent"
ref="configComponent"
:cluster="cluster"
:uuid="uuid"
:mode="mode"
:value="value.config"
:provider="provider"
:credential-id="credentialId"
:pool-index="idx"
:machine-pools="machinePools"
:busy="busy"
@error="e=>errors = e"
@updateMachineCount="updateMachineCount"
@expandAdvanced="expandAdvanced"
@validationChanged="validationChanged"
/>
<Banner
v-else-if="value.configMissing"
color="error"
label-key="cluster.machinePool.configNotFound"
/>
<Banner
v-else
color="info"
label-key="cluster.machinePool.noAccessBanner"
/>
<AdvancedSection
ref="advanced"
:mode="mode"
class="advanced"
>
<portal-target
:name="'advanced-' + uuid"
multiple
/>
<div class="spacer" />
<div class="row">
<div class="col span-4">
<h3>
{{ t('cluster.machinePool.autoReplace.label') }}
<i
v-clean-tooltip="t('cluster.machinePool.autoReplace.toolTip')"
class="icon icon-info icon-lg"
/>
</h3>
<UnitInput
v-model.number="unhealthyNodeTimeoutInteger"
:hide-arrows="true"
:placeholder="t('containerResourceLimit.cpuPlaceholder')"
:mode="mode"
:output-modifier="true"
:base-unit="t('cluster.machinePool.autoReplace.unit')"
:disabled="busy"
@input="value.pool.unhealthyNodeTimeout = `${unhealthyNodeTimeoutInteger}s`"
/>
</div>
<div class="col span-4">
<h3>
{{ t('cluster.machinePool.drain.header') }}
</h3>
<Checkbox
v-model="value.pool.drainBeforeDelete"
:mode="mode"
:label="t('cluster.machinePool.drain.label')"
:disabled="busy"
/>
</div>
</div>
<div class="spacer" />
<KeyValue
v-model="value.pool.labels"
:add-label="t('labels.addLabel')"
:disabled="busy"
:title="t('cluster.machinePool.labels.label')"
:read-allowed="false"
:value-can-be-empty="true"
/>
<div class="spacer" />
<Taints
v-model="value.pool.taints"
:mode="mode"
:disabled="busy"
/>
<portal-target
:name="'advanced-footer-' + uuid"
multiple
/>
</AdvancedSection>
</div>
</template>
<style lang="scss" scoped>
.advanced ::v-deep >.vue-portal-target:empty,
.advanced ::v-deep >.vue-portal-target:empty + .spacer {
display: none;
}
</style>