mirror of https://github.com/rancher/dashboard.git
631 lines
19 KiB
Vue
631 lines
19 KiB
Vue
<script>
|
|
import Loading from '@shell/components/Loading';
|
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
import FormValidation from '@shell/mixins/form-validation';
|
|
import { _CREATE } from '@shell/config/query-params';
|
|
import { stringify } from '@shell/utils/error';
|
|
import { Banner } from '@components/Banner';
|
|
import merge from 'lodash/merge';
|
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
import ArrayList from '@shell/components/form/ArrayList';
|
|
import GCEImage from '@shell/machine-config/components/GCEImage.vue';
|
|
import { convertStringToKV, convertKVToString } from '@shell/utils/object';
|
|
import KeyValue from '@shell/components/form/KeyValue';
|
|
import {
|
|
getGKEZones, getGKEDiskTypes, getGKENetworks, getGKEMachineTypes, getGKESubnetworks, getGKESharedSubnetworks
|
|
} from '@shell/components/google/util/gcp';
|
|
import { formatSharedNetworks, formatNetworkOptions, formatSubnetworkOptions } from '@shell/components/google/util/formatter';
|
|
import { mapGetters } from 'vuex';
|
|
import { sortBy, sortableNumericSuffix } from '@shell/utils/sort';
|
|
const GKE_NONE_OPTION = 'none';
|
|
|
|
const DEFAULT_MIN_DISK = 10;
|
|
|
|
const defaultConfig = Object.freeze({
|
|
zone: 'us-central1-a',
|
|
machineImage: '',
|
|
diskType: 'pd-standard',
|
|
network: '',
|
|
subnetwork: '',
|
|
scopes: 'https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write',
|
|
machineType: 'n1-standard-2',
|
|
diskSize: '50',
|
|
tags: '',
|
|
address: '',
|
|
openPort: [],
|
|
vmLabels: '',
|
|
username: 'docker-user',
|
|
setInternalFirewallRulePrefix: true,
|
|
setExternalFirewallRulePrefix: false,
|
|
preemptible: false
|
|
});
|
|
|
|
export default {
|
|
emits: ['expandAdvanced', 'error', 'validationChanged'],
|
|
|
|
components: {
|
|
ArrayList,
|
|
Banner,
|
|
Checkbox,
|
|
KeyValue,
|
|
LabeledInput,
|
|
LabeledSelect,
|
|
Loading,
|
|
GCEImage
|
|
},
|
|
|
|
mixins: [CreateEditView, FormValidation],
|
|
|
|
props: {
|
|
credentialId: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
projectId: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: _CREATE,
|
|
},
|
|
uuid: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
poolCreateMode: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
},
|
|
|
|
async fetch() {
|
|
if ( !this.credentialId ) {
|
|
return;
|
|
}
|
|
|
|
for (const key in this.defaultConfig) {
|
|
if (this.value[key] === undefined && !!this.defaultConfig[key]) {
|
|
this.value[key] = this.defaultConfig[key];
|
|
}
|
|
}
|
|
await Promise.all([this.getZones(), this.getOptions()]);
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
defaultConfig,
|
|
loadingZones: false,
|
|
loadingDiskTypes: false,
|
|
loadingNetworks: false,
|
|
loadingMachineTypes: false,
|
|
zones: [],
|
|
diskTypes: [],
|
|
networks: [],
|
|
subnetworks: [],
|
|
sharedSubnetworks: [],
|
|
machineTypes: [],
|
|
useIpAliases: false,
|
|
minDiskFromImage: DEFAULT_MIN_DISK,
|
|
originalOpenPort: this.value.openPort || [],
|
|
originalMachineImage: this.value.machineImage,
|
|
diskSizeRequirementsFromType: null,
|
|
fvFormRuleSets: [
|
|
{ path: 'machineImage', rules: ['required'] },
|
|
{ path: 'diskType', rules: ['required'] },
|
|
{ path: 'diskSize', rules: ['required', 'isPositive', 'minDiskFromImageSize'] },
|
|
{ path: 'machineType', rules: ['required'] },
|
|
{ path: 'network', rules: ['required'] },
|
|
]
|
|
};
|
|
},
|
|
created() {
|
|
if (this.poolCreateMode) {
|
|
this.$emit('validationChanged', false);
|
|
this.value.project = this.projectId;
|
|
for (const key in this.defaultConfig) {
|
|
if (this.value[key] === undefined && !!this.defaultConfig[key]) {
|
|
this.value[key] = this.defaultConfig[key];
|
|
}
|
|
}
|
|
merge(this.value, this.defaultConfig);
|
|
} else {
|
|
this.value.setInternalFirewallRulePrefix = !!this.value.internalFirewallRulePrefix;
|
|
this.value.setExternalFirewallRulePrefix = !!this.value.externalFirewallRulePrefix;
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
credentialId() {
|
|
this.$fetch();
|
|
},
|
|
fvFormIsValid(newVal) {
|
|
this.$emit('validationChanged', !!newVal);
|
|
},
|
|
|
|
'value.zone'() {
|
|
this.getOptions();
|
|
},
|
|
'value.availabilityZone'(neu) {
|
|
if (neu && (!this.value.managedDisks || !this.value.enablePublicIpStandardSku || !this.value.staticPublicIp)) {
|
|
this.$emit('expandAdvanced');
|
|
}
|
|
},
|
|
'value.setExternalFirewallRulePrefix'(neu) {
|
|
if (!neu) {
|
|
this.value.openPort = [];
|
|
} else if (this.poolCreateMode) {
|
|
this.value.openPort.push('6443');
|
|
} else {
|
|
this.value.openPort = this.originalOpenPort.length > 0 ? this.originalOpenPort : ['6443'];
|
|
}
|
|
},
|
|
|
|
networkOptions(neu) {
|
|
if (neu && neu.length && !this.value.network) {
|
|
const defaultNetwork = neu.find((network) => network?.name === 'default');
|
|
|
|
if (defaultNetwork) {
|
|
this.value.network = defaultNetwork.name;
|
|
} else {
|
|
const firstnetwork = neu.find((network) => network.kind !== 'group');
|
|
|
|
this.value.network = firstnetwork.name;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
...mapGetters({ t: 'i18n/t' }),
|
|
fvExtraRules() {
|
|
return {
|
|
minDiskFromImageSize: (val) => {
|
|
let minTotal = Number(this.minDiskFromImage);
|
|
let maxTotal = null;
|
|
const valAsNumber = Number(val || 0);
|
|
|
|
if ( !!this.diskSizeRequirementsFromType) {
|
|
const vals = this.diskSizeRequirementsFromType.split('-');
|
|
const minFromType = vals[0]?.substring(0, vals[0]?.length - 2);
|
|
const maxFromType = vals[1]?.substring(0, vals[1]?.length - 2);
|
|
|
|
minTotal = minFromType > minTotal ? Number(minFromType) : minTotal;
|
|
maxTotal = Number(maxFromType);
|
|
}
|
|
const valLessThanMin = valAsNumber < minTotal;
|
|
const valMoreThanMax = !!maxTotal && valAsNumber > maxTotal;
|
|
|
|
if (!maxTotal) {
|
|
return val && valLessThanMin ? this.t('cluster.machineConfig.gce.error.diskSizeWithoutMax', { diskSizeMin: minTotal }) : undefined;
|
|
} else {
|
|
return val && (valLessThanMin || valMoreThanMax) ? this.t('cluster.machineConfig.gce.error.diskSizeWithMax', { diskSizeMin: minTotal, diskSizeMax: maxTotal }) : undefined;
|
|
}
|
|
}
|
|
|
|
};
|
|
},
|
|
location() {
|
|
return { zone: this.value.zone };
|
|
},
|
|
project() {
|
|
return this.value.project;
|
|
},
|
|
|
|
sharedNetworks() {
|
|
return formatSharedNetworks(this.sharedSubnetworks);
|
|
},
|
|
networkOptions() {
|
|
const out = formatNetworkOptions(this.t, this.networks, this.subnetworks, this.sharedNetworks );
|
|
|
|
return out;
|
|
},
|
|
|
|
subnetworkOptions() {
|
|
return formatSubnetworkOptions(this.t, this.value.network, this.subnetworks, this.sharedNetworks, this.useIpAliases);
|
|
},
|
|
|
|
selectedNetwork: {
|
|
get() {
|
|
const { network } = this.value;
|
|
|
|
if (this.isView) {
|
|
return network;
|
|
}
|
|
if (!network) {
|
|
return undefined;
|
|
}
|
|
|
|
return this.networkOptions.find((n) => n.name === network);
|
|
},
|
|
set(neu) {
|
|
this.value.network = neu.name;
|
|
}
|
|
},
|
|
|
|
selectedSubnetwork: {
|
|
get() {
|
|
const { subnetwork } = this.value;
|
|
|
|
if (this.isView) {
|
|
return subnetwork;
|
|
}
|
|
if (!subnetwork || subnetwork === '') {
|
|
return { label: this.t('gke.subnetwork.auto'), name: GKE_NONE_OPTION };
|
|
}
|
|
|
|
return this.subnetworkOptions.find((n) => n.name === subnetwork);
|
|
},
|
|
set(neu) {
|
|
if (neu.name === GKE_NONE_OPTION) {
|
|
this.value.subnetwork = '';
|
|
} else {
|
|
this.value.subnetwork = neu.name;
|
|
}
|
|
}
|
|
},
|
|
diskType: {
|
|
get() {
|
|
return this.value?.diskType || '';
|
|
},
|
|
set(neu) {
|
|
this.value.diskType = neu.name;
|
|
this.diskSizeRequirementsFromType = neu.validDiskSize;
|
|
}
|
|
},
|
|
tags: {
|
|
get() {
|
|
return this.value?.tags ? this.value.tags.split(',') : [];
|
|
},
|
|
set(neu) {
|
|
this.value.tags = neu.toString();
|
|
}
|
|
},
|
|
scopes: {
|
|
get() {
|
|
return this.value?.scopes ? this.value.scopes.split(',') : [];
|
|
},
|
|
set(neu) {
|
|
this.value.scopes = neu.toString();
|
|
}
|
|
},
|
|
labels: {
|
|
get() {
|
|
const labels = this.value.vmLabels || '';
|
|
|
|
return convertStringToKV(labels);
|
|
},
|
|
set(neu) {
|
|
this.value.vmLabels = convertKVToString(neu);
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
stringify,
|
|
async getZones() {
|
|
try {
|
|
const res = await getGKEZones(this.$store, this.credentialId, this.project, {});
|
|
|
|
this.zones = sortBy((res.items || []).map((z) => {
|
|
z.disabled = z?.status?.toLowerCase() !== 'up';
|
|
z.sortName = sortableNumericSuffix(z.name);
|
|
|
|
return z.name;
|
|
}), 'sortName', false);
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
|
|
return '';
|
|
}
|
|
},
|
|
|
|
async getDiskTypes() {
|
|
this.loadingDiskTypes = true;
|
|
try {
|
|
const res = await getGKEDiskTypes(this.$store, this.credentialId, this.project, this.location);
|
|
|
|
this.diskTypes = res.items.map((type) => {
|
|
return { name: type.name, validDiskSize: type.validDiskSize };
|
|
});
|
|
const cur = this.diskTypes.find((el) => el.name === this.value.diskType);
|
|
|
|
if (!cur ) {
|
|
// If default is not actually available, reset
|
|
if (this.poolCreateMode) {
|
|
this.value.diskType = '';
|
|
}
|
|
} else {
|
|
this.diskSizeRequirementsFromType = cur.validDiskSize;
|
|
}
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
}
|
|
|
|
this.loadingDiskTypes = false;
|
|
},
|
|
async getNetworks() {
|
|
this.loadingNetworks = true;
|
|
try {
|
|
const res = await getGKENetworks(this.$store, this.credentialId, this.project, this.location);
|
|
|
|
this.networks = res?.items;
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
}
|
|
this.loadingNetworks = false;
|
|
},
|
|
async getSharedSubnetworks() {
|
|
try {
|
|
const res = await getGKESharedSubnetworks(this.$store, this.credentialId, this.project, this.location);
|
|
|
|
this.sharedSubnetworks = res?.subnetworks || [];
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
}
|
|
},
|
|
async getSubnetworks() {
|
|
const region = `${ this.value.zone.split('-')[0] }-${ this.value.zone.split('-')[1] }`;
|
|
|
|
try {
|
|
const res = await getGKESubnetworks(this.$store, this.credentialId, this.project, { region });
|
|
|
|
this.subnetworks = res?.items || [];
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
}
|
|
},
|
|
async getMachineTypes() {
|
|
this.loadingMachineTypes = true;
|
|
try {
|
|
const res = await getGKEMachineTypes(this.$store, this.credentialId, this.project, this.location);
|
|
|
|
this.machineTypes = res?.items.map((type) => {
|
|
return type.name;
|
|
});
|
|
} catch (e) {
|
|
this.errors.push(e.data);
|
|
}
|
|
this.loadingMachineTypes = false;
|
|
},
|
|
|
|
async getOptions() {
|
|
await this.getDiskTypes();
|
|
await this.getMachineTypes();
|
|
await this.getNetworks();
|
|
// These can finish loading later
|
|
this.getSubnetworks();
|
|
this.getSharedSubnetworks();
|
|
},
|
|
closeError(index) {
|
|
this.errors = this.errors.filter((_, i) => i !== index);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Loading
|
|
v-if="$fetchState.pending"
|
|
:delayed="true"
|
|
/>
|
|
|
|
<div v-else>
|
|
<div v-if="errors.length">
|
|
<div
|
|
v-for="(err, idx) in errors"
|
|
:key="idx"
|
|
>
|
|
<Banner
|
|
color="error"
|
|
:label="stringify(err)"
|
|
:closable="true"
|
|
:data-testid="`gce-error-banner-${idx}`"
|
|
@close="closeError(idx)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="col span-6">
|
|
<LabeledSelect
|
|
v-model:value="value.zone"
|
|
label-key="cluster.machineConfig.gce.location.zone.label"
|
|
:mode="mode"
|
|
:options="zones"
|
|
|
|
:loading="loadingZones"
|
|
data-testid="gce-zone-select"
|
|
class="span-3 mr-10"
|
|
required
|
|
/>
|
|
</div>
|
|
<GCEImage
|
|
v-model:value="value.machineImage"
|
|
:credentialId="credentialId"
|
|
:projectId="value.project"
|
|
:originalMachineImage="originalMachineImage"
|
|
:pool-create-mode="poolCreateMode"
|
|
:mode="mode"
|
|
:location="location"
|
|
:rules="{machineImage: fvGetAndReportPathRules('machineImage')}"
|
|
@min-disk-changed="(val)=>minDiskFromImage=val"
|
|
@error="(val)=>errors.push(val)"
|
|
/>
|
|
|
|
<div class="row mt-20">
|
|
<LabeledSelect
|
|
v-model:value="diskType"
|
|
label-key="cluster.machineConfig.gce.diskType.label"
|
|
:mode="mode"
|
|
:options="diskTypes"
|
|
:loading="loadingDiskTypes"
|
|
option-key="name"
|
|
option-label="name"
|
|
data-testid="gce-disk-type-select"
|
|
required
|
|
class="span-3 mr-10"
|
|
:rules="fvGetAndReportPathRules('diskType')"
|
|
/>
|
|
<LabeledInput
|
|
v-model:value="value.diskSize"
|
|
:mode="mode"
|
|
label-key="cluster.machineConfig.gce.diskSize.label"
|
|
:placeholder="50"
|
|
data-testid="gce-disk-size-input"
|
|
class="span-3 mr-10"
|
|
required
|
|
:rules="fvGetAndReportPathRules('diskSize')"
|
|
/>
|
|
<LabeledSelect
|
|
v-model:value="value.machineType"
|
|
label-key="cluster.machineConfig.gce.machineType.label"
|
|
:mode="mode"
|
|
:options="machineTypes"
|
|
:loading="loadingMachineTypes"
|
|
data-testid="gce-machine-type-select"
|
|
required
|
|
:rules="fvGetAndReportPathRules('machineType')"
|
|
/>
|
|
</div>
|
|
<div class="row mt-20">
|
|
<LabeledSelect
|
|
v-model:value="selectedNetwork"
|
|
label-key="cluster.machineConfig.gce.network.label"
|
|
:mode="mode"
|
|
:options="networkOptions"
|
|
:disabled="!poolCreateMode"
|
|
option-key="name"
|
|
option-label="label"
|
|
:loading="loadingNetworks"
|
|
data-testid="gce-network-select"
|
|
class="span-3 mr-10"
|
|
required
|
|
:rules="fvGetAndReportPathRules('network')"
|
|
/>
|
|
<LabeledSelect
|
|
v-model:value="selectedSubnetwork"
|
|
label-key="cluster.machineConfig.gce.subnetwork.label"
|
|
:mode="mode"
|
|
:options="subnetworkOptions"
|
|
:disabled="!poolCreateMode"
|
|
option-key="name"
|
|
option-label="name"
|
|
:loading="loadingNetworks"
|
|
data-testid="gce-subnetwork-select"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<portal :to="'advanced-' + uuid">
|
|
<div class="row mt-20">
|
|
<LabeledInput
|
|
v-model:value="value.username"
|
|
:mode="mode"
|
|
label-key="cluster.machineConfig.gce.username.label"
|
|
:placeholder="t('cluster.machineConfig.gce.username.placeholder')"
|
|
:tooltip="t('cluster.machineConfig.gce.username.tooltip')"
|
|
data-testid="gce-username-input"
|
|
class="span-3 mr-10"
|
|
/>
|
|
|
|
<LabeledInput
|
|
v-model:value="value.address"
|
|
:mode="mode"
|
|
label-key="cluster.machineConfig.gce.address.label"
|
|
:placeholder="t('cluster.machineConfig.gce.address.placeholder')"
|
|
:tooltip="t('cluster.machineConfig.gce.address.tooltip')"
|
|
data-testid="gce-address-input"
|
|
class="span-3"
|
|
/>
|
|
</div>
|
|
<Checkbox
|
|
v-model:value="value.preemptible"
|
|
:mode="mode"
|
|
:label="t('cluster.machineConfig.gce.preemptible.label')"
|
|
:tooltip="t('cluster.machineConfig.gce.preemptible.tooltip')"
|
|
class="mt-20"
|
|
/>
|
|
|
|
<ArrayList
|
|
v-model:value="scopes"
|
|
table-class="fixed"
|
|
:mode="mode"
|
|
:title="t('cluster.machineConfig.gce.scopes.label')"
|
|
:add-label="t('cluster.machineConfig.gce.scopes.add')"
|
|
:disabled="disabled"
|
|
class="col mt-20 span-10"
|
|
data-testid="gce-scopes-array"
|
|
/>
|
|
<h3 class="mt-20">
|
|
{{ t('cluster.machineConfig.gce.firewall.header') }}
|
|
</h3>
|
|
<div class="row mt-20 span-12">
|
|
<div class="col span-6">
|
|
<Checkbox
|
|
v-model:value="value.setInternalFirewallRulePrefix"
|
|
:mode="mode"
|
|
:label="t('cluster.machineConfig.gce.internalFirewall.label')"
|
|
:tooltip="t('cluster.machineConfig.gce.internalFirewall.tooltip')"
|
|
data-testid="gce-internal-firewall-prefix-checkbox"
|
|
/>
|
|
<Banner
|
|
color="info"
|
|
label-key="cluster.machineConfig.gce.internalFirewall.banner"
|
|
data-testid="gce-internal-firewall-banner"
|
|
/>
|
|
<ArrayList
|
|
v-model:value="tags"
|
|
:mode="mode"
|
|
:title="t('gke.tags.label')"
|
|
:add-label="t('gke.tags.add')"
|
|
class="col mr-10"
|
|
data-testid="gce-tags-array"
|
|
/>
|
|
</div>
|
|
<div class="col span-6">
|
|
<Checkbox
|
|
v-model:value="value.setExternalFirewallRulePrefix"
|
|
:mode="mode"
|
|
:label="t('cluster.machineConfig.gce.externalFirewall.label')"
|
|
:tooltip="t('cluster.machineConfig.gce.externalFirewall.tooltip')"
|
|
data-testid="gce-external-firewall-prefix-checkbox"
|
|
/>
|
|
<div v-if="!!value.setExternalFirewallRulePrefix">
|
|
<Banner
|
|
color="info"
|
|
label-key="cluster.machineConfig.gce.externalFirewall.banner"
|
|
data-testid="gce-external-firewall-banner"
|
|
/>
|
|
<ArrayList
|
|
v-model:value="value.openPort"
|
|
:mode="mode"
|
|
:title="t('cluster.machineConfig.gce.openPort.label')"
|
|
:add-label="t('cluster.machineConfig.gce.openPort.add')"
|
|
class="col"
|
|
data-testid="gce-ports-array"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-20">
|
|
<h3>
|
|
<t k="labels.labels.title" />
|
|
</h3>
|
|
<KeyValue
|
|
v-model:value="labels"
|
|
:mode="mode"
|
|
:value-can-be-empty="true"
|
|
:add-label="t('aks.nodePools.labels.add')"
|
|
:read-allowed="false"
|
|
data-testid="gce-labels-kv"
|
|
/>
|
|
</div>
|
|
</portal>
|
|
</div>
|
|
</template>
|