dashboard/edit/service.vue

324 lines
10 KiB
Vue

<script>
import { isEmpty, find, isNaN } from 'lodash';
import ArrayList from '@/components/form/ArrayList';
import CreateEditView from '@/mixins/create-edit-view';
import Footer from '@/components/form/Footer';
import KeyValue from '@/components/form/KeyValue';
import LabeledInput from '@/components/form/LabeledInput';
import LabeledSelect from '@/components/form/LabeledSelect';
import NameNsDescription from '@/components/form/NameNsDescription';
import RadioGroup from '@/components/form/RadioGroup';
import ResourceTabs from '@/components/form/ResourceTabs';
import ServicePorts from '@/components/form/ServicePorts';
import Tab from '@/components/Tabbed/Tab';
import UnitInput from '@/components/form/UnitInput';
import { DEFAULT_SERVICE_TYPES, HEADLESS, CLUSTERIP } from '@/models/service';
import { ucFirst } from '@/utils/string';
import Banner from '@/components/Banner';
const SESSION_AFFINITY_ACTION_VALUES = {
NONE: 'None',
CLUSTERIP: 'ClusterIP'
};
const SESSION_AFFINITY_ACTION_LABELS = {
NONE: 'servicesPage.affinity.actionLabels.none',
CLUSTERIP: 'servicesPage.affinity.actionLabels.clusterIp'
};
const SESSION_STICKY_TIME_DEFAULT = 10800;
export default {
// Props are found in CreateEditView
// props: {},
components: {
ArrayList,
Banner,
Footer,
KeyValue,
LabeledInput,
LabeledSelect,
NameNsDescription,
RadioGroup,
ResourceTabs,
ServicePorts,
Tab,
UnitInput,
},
mixins: [CreateEditView],
data() {
if (!this?.value?.spec?.type) {
if (!this.value?.spec) {
const defaultService = find(DEFAULT_SERVICE_TYPES, ['id', CLUSTERIP]);
this.$set(this.value, 'spec', {
ports: [],
sessionAffinity: 'None',
});
this.serviceType = defaultService.id;
}
}
return {
defaultServiceTypes: DEFAULT_SERVICE_TYPES,
saving: false,
sessionAffinityActionLabels: Object.values(SESSION_AFFINITY_ACTION_LABELS).map(v => this.$store.getters['i18n/t'](v)).map(ucFirst),
sessionAffinityActionOptions: Object.values(SESSION_AFFINITY_ACTION_VALUES),
};
},
computed: {
extraColumns() {
return ['type-col'];
},
serviceType: {
get() {
const serviceType = this.value?.spec?.type;
const clusterIp = this.value?.spec?.clusterIP;
const defaultService = find(DEFAULT_SERVICE_TYPES, ['id', CLUSTERIP]);
if (serviceType) {
if (serviceType === CLUSTERIP && clusterIp === 'None') {
return HEADLESS;
} else {
return serviceType;
}
}
return defaultService;
},
set(serviceType) {
if (serviceType === HEADLESS) {
this.$set(this.value.spec, 'type', CLUSTERIP);
this.$set(this.value.spec, 'clusterIP', 'None');
} else {
if (serviceType !== HEADLESS && this.value?.spec?.clusterIP === 'None') {
this.$set(this.value.spec, 'clusterIP', null);
} else if (serviceType === 'ExternalName') {
this.$set(this.value.spec, 'ports', null);
}
this.$set(this.value.spec, 'type', serviceType);
}
},
},
},
watch: {
'value.spec.sessionAffinity'(val) {
if (val === CLUSTERIP) {
this.value.spec.sessionAffinityConfig = { clientIP: { timeoutSeconds: null } };
// set it null and then set it with vue to make reactive.
this.$set(this.value.spec.sessionAffinityConfig.clientIP, 'timeoutSeconds', SESSION_STICKY_TIME_DEFAULT);
} else if (this.value?.spec?.sessionAffinityConfig?.clientIP?.timeoutSeconds) {
delete this.value.spec.sessionAffinityConfig.clientIP.timeoutSeconds;
}
}
},
methods: {
checkTypeIs(typeIn) {
const { serviceType } = this;
if (serviceType === typeIn) {
return true;
}
return false;
},
updateServicePorts(servicePorts) {
servicePorts.forEach((sp) => {
if ( !isEmpty(sp?.targetPort) ) {
const tpCoerced = parseInt(sp.targetPort, 10);
if (!isNaN(tpCoerced)) {
sp.targetPort = tpCoerced;
}
}
});
this.$set(this.value.spec, 'ports', servicePorts);
}
},
};
</script>
<template>
<div>
<form>
<NameNsDescription
v-if="!isView"
:value="value"
:mode="mode"
:extra-columns="extraColumns"
>
<template #type-col>
<LabeledSelect
option-key="id"
option-label="translationLabel"
:label="t('servicesPage.typeOpts.label')"
:localized-label="true"
:mode="mode"
:options="defaultServiceTypes"
:value="serviceType"
@input="e=>serviceType = e.id"
/>
</template>
</NameNsDescription>
<div class="spacer"></div>
<div v-if="checkTypeIs('ExternalName')">
<div class="clearfix">
<h4>
<t k="servicesPage.externalName.label" />
</h4>
<Banner color="info" :label="t('servicesPage.externalName.helpText')" />
</div>
<div class="row mt-10">
<div class="col span-6">
<span v-if="isView">{{ value.spec.externalName }}</span>
<input
v-else
ref="external-name"
v-model.number="value.spec.externalName"
type="text"
:placeholder="t('servicesPage.externalName.placeholder')"
/>
</div>
</div>
</div>
<ServicePorts
v-else
v-model="value.spec.ports"
class="col span-12"
:mode="mode"
:spec-type="serviceType"
@input="updateServicePorts"
/>
<div class="spacer"></div>
<ResourceTabs v-model="value" :mode="mode">
<template #before>
<Tab
v-if="!checkTypeIs('ExternalName')"
name="selectors"
:weight="1"
:label="t('servicesPage.selectors.label')"
>
<div class="row">
<div class="col span-12">
<Banner color="info" :label="t('servicesPage.selectors.helpText')" />
</div>
</div>
<div class="row">
<div class="col span-12">
<KeyValue
key="selectors"
v-model="value.spec.selector"
:mode="mode"
:initial-empty-row="true"
:pad-left="false"
:read-allowed="false"
:protip="false"
@input="e=>$set(value.spec, 'selector', e)"
/>
</div>
</div>
</Tab>
<Tab
name="ips"
:label="t('servicesPage.ips.label')"
:weight="2"
>
<div class="row">
<div class="col span-12">
<Banner color="warning" :label="t('servicesPage.ips.helpText')" />
<Banner
v-if="checkTypeIs('ClusterIP') || checkTypeIs('LoadBalancer') || checkTypeIs('NodePort')"
color="info"
:label="t('servicesPage.ips.clusterIpHelpText')"
/>
</div>
</div>
<div v-if="checkTypeIs('ClusterIP') || checkTypeIs('LoadBalancer') || checkTypeIs('NodePort')" class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model="value.spec.clusterIP"
:mode="mode"
:label="t('servicesPage.ips.input.label')"
:placeholder="t('servicesPage.ips.input.placeholder')"
@input="e=>$set(value.spec, 'clusterIP', e)"
/>
</div>
</div>
<div class="row">
<div class="col span-7">
<ArrayList
key="clusterExternalIpAddresses"
v-model="value.spec.externalIPs"
:title="t('servicesPage.ips.external.label')"
:value-placeholder="t('servicesPage.ips.external.placeholder')"
:value-multiline="false"
:mode="mode"
:pad-left="false"
:protip="t('servicesPage.ips.external.protip')"
@input="e=>$set(value.spec, 'externalIPs', e)"
/>
</div>
</div>
</Tab>
<Tab
v-if="!checkTypeIs('NodePort') && !checkTypeIs('ExternalName') && !checkTypeIs('Headless')"
name="session-affinity"
:label="t('servicesPage.affinity.label')"
:weight="3"
>
<div class="col span-12">
<Banner color="info" :label="t('servicesPage.affinity.helpText')" />
</div>
<div class="row session-affinity">
<div class="col span-6">
<RadioGroup
v-model="value.spec.sessionAffinity"
class="enforcement-action"
:options="sessionAffinityActionOptions"
:labels="sessionAffinityActionLabels"
:mode="mode"
@input="e=>value.spec.sessionAffinity = e"
/>
</div>
<div v-if="value.spec.sessionAffinity === 'ClusterIP'" class="col span-6">
<UnitInput
v-model="value.spec.sessionAffinityConfig.clientIP.timeoutSeconds"
:suffix="t('suffix.seconds')"
:label="t('servicesPage.affinity.timeout.label')"
:placeholder="t('servicesPage.affinity.timeout.placeholder')"
@input="e=>$set(value.spec.sessionAffinityConfig.clientIP, 'timeoutSeconds', e)"
/>
</div>
</div>
</Tab>
</template>
</ResourceTabs>
<Footer
v-if="!isView"
:mode="mode"
:errors="errors"
@save="save"
@done="done"
/>
</form>
</div>
</template>