mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-07-01 14:22:24 +00:00
feat: add Host Networks tab list and edit pages (#920)
* feat: add Host Network tab Signed-off-by: Andy Lee <andy.lee@suse.com> * feat: add Host Network edit page Signed-off-by: Andy Lee <andy.lee@suse.com> * feat: add node selector tab Signed-off-by: Andy Lee <andy.lee@suse.com> * refactor: copilot review Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: lint Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: copilot review Signed-off-by: Andy Lee <andy.lee@suse.com> * refactor: add warning message if addon is not enabled or already hasone Signed-off-by: Andy Lee <andy.lee@suse.com> * refactor: some wordings in en-us.yaml Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
661ab995f6
commit
5985913f5e
186
pkg/harvester/components/HarvesterNodeSelector.vue
Normal file
186
pkg/harvester/components/HarvesterNodeSelector.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<script>
|
||||
import { Banner } from '@components/Banner';
|
||||
import MatchExpressions from '@shell/components/form/MatchExpressions';
|
||||
import ResourceTable from '@shell/components/ResourceTable';
|
||||
import { _EDIT } from '@shell/config/query-params';
|
||||
import { convert, simplify, matching as selectorMatching } from '@shell/utils/selector';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { NODE } from '@shell/config/types';
|
||||
import { NAME, AGE } from '@shell/config/table-headers';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterNodeSelector',
|
||||
|
||||
components: {
|
||||
Banner,
|
||||
MatchExpressions,
|
||||
ResourceTable,
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
this.updateMatchingResources();
|
||||
},
|
||||
|
||||
data() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return {
|
||||
matchingResources: {
|
||||
matched: 0,
|
||||
matches: [],
|
||||
none: true,
|
||||
sample: null,
|
||||
total: 0,
|
||||
},
|
||||
tableHeaders: [
|
||||
NAME,
|
||||
{
|
||||
name: 'host-ip',
|
||||
labelKey: 'tableHeaders.hostIp',
|
||||
search: ['internalIp'],
|
||||
value: 'internalIp',
|
||||
sort: ['internalIp'],
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'cpuManager',
|
||||
labelKey: 'harvester.tableHeaders.cpuManager',
|
||||
value: 'id',
|
||||
formatter: 'HarvesterCPUPinning',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
name: 'diskState',
|
||||
labelKey: 'tableHeaders.diskState',
|
||||
value: 'diskState',
|
||||
formatter: 'HarvesterDiskState',
|
||||
width: 130,
|
||||
},
|
||||
AGE,
|
||||
],
|
||||
inStore,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler: 'updateMatchingResources',
|
||||
deep: true,
|
||||
},
|
||||
allResourcesInScope() {
|
||||
this.updateMatchingResources();
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
schema() {
|
||||
return this.$store.getters[`${ this.inStore }/schemaFor`](NODE);
|
||||
},
|
||||
|
||||
selectorExpressions: {
|
||||
get() {
|
||||
return convert(
|
||||
this.value.matchLabels || {},
|
||||
this.value.matchExpressions || []
|
||||
);
|
||||
},
|
||||
set(selectorExpressions) {
|
||||
const { matchLabels, matchExpressions } = simplify(selectorExpressions);
|
||||
|
||||
this.value['matchLabels'] = matchLabels;
|
||||
this.value['matchExpressions'] = matchExpressions;
|
||||
this.updateMatchingResources();
|
||||
},
|
||||
},
|
||||
|
||||
allNodes() {
|
||||
return this.$store.getters[`${ this.inStore }/all`](NODE) || [];
|
||||
},
|
||||
|
||||
allResourcesInScope() {
|
||||
return this.allNodes.length;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateMatchingResources: throttle(function() {
|
||||
const expressions = this.selectorExpressions;
|
||||
const allNodes = this.allNodes;
|
||||
|
||||
// Empty expressions with no key = no match
|
||||
const hasValidExpression = expressions.length > 0 && expressions.every((e) => !!e.key);
|
||||
|
||||
if (!hasValidExpression) {
|
||||
this.matchingResources = {
|
||||
matched: 0,
|
||||
matches: [],
|
||||
none: true,
|
||||
sample: null,
|
||||
total: allNodes.length,
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = selectorMatching(allNodes, expressions, 'metadata.labels');
|
||||
|
||||
this.matchingResources = {
|
||||
matched: matches.length,
|
||||
matches,
|
||||
none: matches.length === 0,
|
||||
sample: matches[0]?.nameDisplay || null,
|
||||
total: allNodes.length,
|
||||
};
|
||||
}, 100, { trailing: true })
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<MatchExpressions
|
||||
v-model:value="selectorExpressions"
|
||||
:mode="mode"
|
||||
:show-remove="false"
|
||||
:type="'node'"
|
||||
:target-resources="allResourcesInScope"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<Banner :color="(matchingResources.none ? 'warning' : 'success')">
|
||||
<span v-clean-html="t('generic.selectors.matchingResources.matchesSome', matchingResources)" />
|
||||
</Banner>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<ResourceTable
|
||||
:rows="matchingResources.matches"
|
||||
:headers="tableHeaders"
|
||||
key-field="id"
|
||||
:table-actions="false"
|
||||
:row-actions="false"
|
||||
:schema="schema"
|
||||
:groupable="false"
|
||||
:search="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -581,6 +581,7 @@ export function init($plugin, store) {
|
||||
[
|
||||
HCI.CLUSTER_NETWORK,
|
||||
HCI.NETWORK_ATTACHMENT,
|
||||
HCI.HOST_NETWORK_CONFIG,
|
||||
HCI.VPC,
|
||||
NETWORK_POLICY,
|
||||
HCI.LB,
|
||||
@ -1141,4 +1142,24 @@ export function init($plugin, store) {
|
||||
ifHaveType: HCI.IP_POOL,
|
||||
});
|
||||
headers(HCI.IP_POOL, IP_POOL_HEADERS);
|
||||
|
||||
configureType(HCI.HOST_NETWORK_CONFIG, {
|
||||
location: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.HOST_NETWORK_CONFIG }
|
||||
},
|
||||
canYaml: false,
|
||||
});
|
||||
virtualType({
|
||||
labelKey: 'harvester.hostNetworkConfig.label',
|
||||
name: HCI.HOST_NETWORK_CONFIG,
|
||||
namespaced: false,
|
||||
weight: 183,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.HOST_NETWORK_CONFIG }
|
||||
},
|
||||
exact: false,
|
||||
ifHaveType: HCI.HOST_NETWORK_CONFIG,
|
||||
});
|
||||
}
|
||||
|
||||
419
pkg/harvester/edit/network.harvesterhci.io.hostnetworkconfig.vue
Normal file
419
pkg/harvester/edit/network.harvesterhci.io.hostnetworkconfig.vue
Normal file
@ -0,0 +1,419 @@
|
||||
<script>
|
||||
import CruResource from '@shell/components/CruResource';
|
||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||
import ResourceTabs from '@shell/components/form/ResourceTabs';
|
||||
import Tab from '@shell/components/Tabbed/Tab';
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
import MessageLink from '@shell/components/MessageLink';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import HarvesterNodeSelector from '../components/HarvesterNodeSelector';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { set } from '@shell/utils/object';
|
||||
import { NODE } from '@shell/config/types';
|
||||
import { HCI } from '../types';
|
||||
import { ADD_ONS } from '../config/harvester-map';
|
||||
|
||||
const MODE_DHCP = 'dhcp';
|
||||
const MODE_STATIC = 'static';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterHostNetworkConfigEditPage',
|
||||
|
||||
emits: ['update:value'],
|
||||
|
||||
components: {
|
||||
CruResource,
|
||||
NameNsDescription,
|
||||
ResourceTabs,
|
||||
Tab,
|
||||
InfoBox,
|
||||
MessageLink,
|
||||
LabeledSelect,
|
||||
LabeledInput,
|
||||
RadioGroup,
|
||||
Checkbox,
|
||||
HarvesterNodeSelector,
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
await allHash({
|
||||
clusterNetworks: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER_NETWORK }),
|
||||
nodes: this.$store.dispatch(`${ inStore }/findAll`, { type: NODE }),
|
||||
hostNetworkConfigs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.HOST_NETWORK_CONFIG }),
|
||||
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
|
||||
});
|
||||
},
|
||||
|
||||
data() {
|
||||
const networkMode = this.value?.spec?.mode || MODE_DHCP;
|
||||
const ips = { ...(this.value?.spec?.ips || {}) };
|
||||
|
||||
if (!this.value.spec) {
|
||||
set(this.value, 'spec', {});
|
||||
}
|
||||
|
||||
return {
|
||||
networkMode,
|
||||
ips,
|
||||
hasNodeSelector: !!this.value?.spec?.nodeSelector,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
modeOptions() {
|
||||
return [
|
||||
{ label: 'DHCP', value: MODE_DHCP },
|
||||
{ label: 'Static', value: MODE_STATIC },
|
||||
];
|
||||
},
|
||||
|
||||
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.isReady;
|
||||
|
||||
return {
|
||||
label: disabled ? `${ n.id } (${ this.t('generic.notReady') })` : n.id,
|
||||
value: n.id,
|
||||
disabled,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
nodes() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return this.$store.getters[`${ inStore }/all`](NODE) || [];
|
||||
},
|
||||
|
||||
isStaticMode() {
|
||||
return this.networkMode === MODE_STATIC;
|
||||
},
|
||||
|
||||
underlay: {
|
||||
get() {
|
||||
return !!this.value?.spec?.underlay;
|
||||
},
|
||||
set(val) {
|
||||
set(this.value, 'spec.underlay', val);
|
||||
},
|
||||
},
|
||||
|
||||
vlanID: {
|
||||
get() {
|
||||
return this.value?.spec?.vlanID;
|
||||
},
|
||||
set(val) {
|
||||
set(this.value, 'spec.vlanID', val);
|
||||
},
|
||||
},
|
||||
|
||||
clusterNetwork: {
|
||||
get() {
|
||||
return this.value?.spec?.clusterNetwork;
|
||||
},
|
||||
set(val) {
|
||||
set(this.value, 'spec.clusterNetwork', val);
|
||||
},
|
||||
},
|
||||
|
||||
underlayConflict() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const all = this.$store.getters[`${ inStore }/all`](HCI.HOST_NETWORK_CONFIG) || [];
|
||||
const currentId = this.value?.id;
|
||||
|
||||
return all.find((c) => c.id !== currentId && c.spec?.underlay === true) || null;
|
||||
},
|
||||
|
||||
kubeovnEnabled() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const addons = this.$store.getters[`${ inStore }/all`](HCI.ADD_ONS) || [];
|
||||
|
||||
return addons.find((a) => a.name === ADD_ONS.KUBEOVN_OPERATOR)?.spec?.enabled === true;
|
||||
},
|
||||
|
||||
underlayDisabled() {
|
||||
return !this.kubeovnEnabled || !!this.underlayConflict;
|
||||
},
|
||||
|
||||
kubeovnAddonTo() {
|
||||
return {
|
||||
name: 'c-cluster-product-resource-namespace-id',
|
||||
params: {
|
||||
cluster: this.$route.params.cluster,
|
||||
product: this.$store.getters['productId'],
|
||||
resource: HCI.ADD_ONS,
|
||||
namespace: 'kube-system',
|
||||
id: ADD_ONS.KUBEOVN_OPERATOR,
|
||||
},
|
||||
query: { mode: 'edit' },
|
||||
hash: '#basic',
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
networkMode(neu) {
|
||||
set(this.value, 'spec.mode', neu);
|
||||
|
||||
if (neu !== MODE_STATIC) {
|
||||
if (this.value?.spec?.ips !== undefined) {
|
||||
delete this.value.spec.ips;
|
||||
}
|
||||
this.ips = {};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.registerBeforeHook) {
|
||||
this.registerBeforeHook(this.updateBeforeSave);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateBeforeSave() {
|
||||
set(this.value, 'spec.mode', this.networkMode);
|
||||
|
||||
if (this.isStaticMode) {
|
||||
set(this.value, 'spec.ips', { ...this.ips });
|
||||
}
|
||||
},
|
||||
|
||||
updateIp(nodeName, val) {
|
||||
this.ips = { ...this.ips, [nodeName]: val };
|
||||
},
|
||||
|
||||
addNodeSelector() {
|
||||
set(this.value.spec, 'nodeSelector', {
|
||||
matchExpressions: [{
|
||||
key: '', operator: 'In', values: []
|
||||
}]
|
||||
});
|
||||
this.hasNodeSelector = true;
|
||||
},
|
||||
|
||||
removeNodeSelector() {
|
||||
delete this.value.spec.nodeSelector;
|
||||
this.hasNodeSelector = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CruResource
|
||||
:done-route="doneRoute"
|
||||
:mode="mode"
|
||||
:resource="value"
|
||||
:errors="errors"
|
||||
:apply-hooks="applyHooks"
|
||||
@finish="save"
|
||||
@cancel="done"
|
||||
@error="e => errors = e"
|
||||
>
|
||||
<NameNsDescription
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:namespaced="false"
|
||||
description-key="spec.description"
|
||||
@update:value="$emit('update:value', $event)"
|
||||
/>
|
||||
|
||||
<ResourceTabs
|
||||
class="mt-15"
|
||||
:need-conditions="false"
|
||||
:need-related="false"
|
||||
:need-events="false"
|
||||
:side-tabs="true"
|
||||
:mode="mode"
|
||||
>
|
||||
<Tab
|
||||
name="basic"
|
||||
:label="t('harvester.hostNetworkConfig.tabs.mode')"
|
||||
:weight="99"
|
||||
class="bordered-table"
|
||||
>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<RadioGroup
|
||||
v-model:value="networkMode"
|
||||
name="hostNetworkConfigMode"
|
||||
:options="modeOptions"
|
||||
:mode="mode"
|
||||
:row="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="clusterNetwork"
|
||||
:label="t('harvester.network.clusterNetwork.label')"
|
||||
:options="clusterNetworkOptions"
|
||||
:mode="mode"
|
||||
required
|
||||
:placeholder="t('harvester.network.clusterNetwork.selectPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
v-model:value.number="vlanID"
|
||||
type="number"
|
||||
required
|
||||
:min="2"
|
||||
:max="4094"
|
||||
placeholder="e.g. 2 ~ 4094"
|
||||
:label="t('harvester.hostNetworkConfig.vlanID.label')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<Checkbox
|
||||
v-model:value="underlay"
|
||||
:label="t('harvester.hostNetworkConfig.underlay.label')"
|
||||
:tooltip="t('harvester.hostNetworkConfig.underlay.tooltip')"
|
||||
:mode="mode"
|
||||
:disabled="underlayDisabled"
|
||||
/>
|
||||
<span
|
||||
v-if="!kubeovnEnabled"
|
||||
class="underlay-conflict-warning"
|
||||
>
|
||||
<i class="icon icon-warning" />
|
||||
<MessageLink
|
||||
:to="kubeovnAddonTo"
|
||||
prefix-label="harvester.hostNetworkConfig.underlay.noKubeovn.prefix"
|
||||
middle-label="harvester.hostNetworkConfig.underlay.noKubeovn.middle"
|
||||
suffix-label="harvester.hostNetworkConfig.underlay.noKubeovn.suffix"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="underlayConflict"
|
||||
class="underlay-conflict-warning"
|
||||
>
|
||||
<i class="icon icon-warning" />
|
||||
{{ t('harvester.hostNetworkConfig.underlay.conflict', { name: underlayConflict.nameDisplay || underlayConflict.id }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="isStaticMode">
|
||||
<hr class="section-divider" />
|
||||
<div
|
||||
v-for="node in nodes"
|
||||
:key="node.id"
|
||||
class="row mb-10 ips-row"
|
||||
>
|
||||
<div class="col span-3">
|
||||
<LabeledInput
|
||||
:value="node.nameDisplay || node.id"
|
||||
:label="t('harvester.hostNetworkConfig.ips.nodeLabel')"
|
||||
mode="view"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-5">
|
||||
<LabeledInput
|
||||
:value="ips[node.id]"
|
||||
:label="t('harvester.hostNetworkConfig.ips.label')"
|
||||
:placeholder="t('harvester.hostNetworkConfig.ips.placeholder')"
|
||||
:mode="mode"
|
||||
required
|
||||
@update:value="updateIp(node.id, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Tab>
|
||||
<Tab
|
||||
name="nodeSelector"
|
||||
:label="t('harvester.hostNetworkConfig.tabs.nodeSelector')"
|
||||
:weight="98"
|
||||
>
|
||||
<template v-if="hasNodeSelector">
|
||||
<InfoBox class="node-selector-box">
|
||||
<button
|
||||
v-if="!isView"
|
||||
type="button"
|
||||
class="role-link btn btn-sm remove"
|
||||
:aria-label="t('generic.remove')"
|
||||
@click="removeNodeSelector"
|
||||
>
|
||||
<i class="icon icon-x" />
|
||||
</button>
|
||||
<HarvesterNodeSelector
|
||||
class="mt-20"
|
||||
:value="value.spec.nodeSelector"
|
||||
:mode="mode"
|
||||
/>
|
||||
</InfoBox>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
type="button"
|
||||
class="btn role-secondary"
|
||||
:disabled="isView"
|
||||
@click="addNodeSelector"
|
||||
>
|
||||
{{ t('harvester.hostNetworkConfig.nodeSelector.addButton') }}
|
||||
</button>
|
||||
</template>
|
||||
</Tab>
|
||||
</ResourceTabs>
|
||||
</CruResource>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section-divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 10px 0 20px;
|
||||
}
|
||||
|
||||
.node-selector-box {
|
||||
position: relative;
|
||||
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.underlay-conflict-warning {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
color: var(--warning);
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
23
pkg/harvester/formatters/HarvesterBoolean.vue
Normal file
23
pkg/harvester/formatters/HarvesterBoolean.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'HarvesterBooleanFormatter',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="value">
|
||||
<i class="icon icon-checkmark" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</template>
|
||||
29
pkg/harvester/formatters/HarvesterHostNetworkConfigMode.vue
Normal file
29
pkg/harvester/formatters/HarvesterHostNetworkConfigMode.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'HarvesterHostNetworkConfigModeFormatter',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayMode() {
|
||||
if (this.value?.toLowerCase() === 'dhcp') {
|
||||
return 'DHCP';
|
||||
}
|
||||
|
||||
if (this.value?.toLowerCase() === 'static') {
|
||||
return 'Static';
|
||||
}
|
||||
|
||||
return this.value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{ displayMode }}</span>
|
||||
</template>
|
||||
@ -295,6 +295,12 @@ harvester:
|
||||
harvesterIpAddress:
|
||||
customIpTooltip: "Custom IP (set via annotation)"
|
||||
tableHeaders:
|
||||
hostNetworkConfig:
|
||||
underlay: Underlay
|
||||
underlayTooltip: Allow this interface to act as the underlay for VM overlay networks.
|
||||
vlanID: VLAN ID
|
||||
mode: Mode
|
||||
clusterNetwork: Cluster Network
|
||||
imageEncryption: Encryption
|
||||
size: Size
|
||||
virtualSize: Virtual Size
|
||||
@ -1871,6 +1877,32 @@ harvester:
|
||||
addLabel: Add CIDR
|
||||
range:
|
||||
addLabel: Add Range
|
||||
hostNetworkConfig:
|
||||
label: Host Networks
|
||||
mode:
|
||||
label: Mode
|
||||
tabs:
|
||||
mode: Mode
|
||||
nodeSelector: Node Selector
|
||||
nodeSelector:
|
||||
addButton: Add Node Selector
|
||||
underlay:
|
||||
label: Underlay
|
||||
tooltip: Allow this interface to act as the underlay for VM overlay networks.
|
||||
conflict: '`{name}` host network config already has underlay enabled. Only one underlay is allowed in the cluster.'
|
||||
noKubeovn:
|
||||
prefix: The kubeovn-operator add-on is not enabled. Click
|
||||
middle: here
|
||||
suffix: to enable the add-on for overlay networking.
|
||||
vlanID:
|
||||
label: VLAN ID
|
||||
ipRange:
|
||||
label: IP Range ({node})
|
||||
placeholder: e.g. 192.168.1.10/24
|
||||
ips:
|
||||
nodeLabel: Node
|
||||
label: IP
|
||||
placeholder: 'e.g. 192.168.1.10/24'
|
||||
|
||||
service:
|
||||
healthCheckPort:
|
||||
@ -2194,6 +2226,11 @@ typeLabel:
|
||||
one { Cluster Network }
|
||||
other { Cluster Networks }
|
||||
}
|
||||
network.harvesterhci.io.hostnetworkconfig: |-
|
||||
{count, plural,
|
||||
one { Host Network }
|
||||
other { Host Networks }
|
||||
}
|
||||
harvesterhci.io.addon: |-
|
||||
{count, plural,
|
||||
one { Add-on }
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
<script>
|
||||
import ResourceTable from '@shell/components/ResourceTable';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import { STATE, NAME as NAME_COL, AGE } from '@shell/config/table-headers';
|
||||
import { HCI } from '../types';
|
||||
|
||||
const UNDERLAY = {
|
||||
name: 'underlay',
|
||||
labelKey: 'harvester.tableHeaders.hostNetworkConfig.underlay',
|
||||
tooltip: 'harvester.tableHeaders.hostNetworkConfig.underlayTooltip',
|
||||
value: 'spec.underlay',
|
||||
sort: 'spec.underlay',
|
||||
formatter: 'HarvesterBoolean',
|
||||
};
|
||||
|
||||
const VLAN_ID = {
|
||||
name: 'vlanID',
|
||||
labelKey: 'harvester.tableHeaders.hostNetworkConfig.vlanID',
|
||||
value: 'spec.vlanID',
|
||||
sort: 'spec.vlanID',
|
||||
};
|
||||
|
||||
const MODE = {
|
||||
name: 'mode',
|
||||
labelKey: 'harvester.tableHeaders.hostNetworkConfig.mode',
|
||||
value: 'spec.mode',
|
||||
sort: 'spec.mode',
|
||||
formatter: 'HarvesterHostNetworkConfigMode',
|
||||
};
|
||||
|
||||
const CLUSTER_NETWORK = {
|
||||
name: 'clusterNetwork',
|
||||
labelKey: 'harvester.tableHeaders.hostNetworkConfig.clusterNetwork',
|
||||
value: 'spec.clusterNetwork',
|
||||
sort: 'spec.clusterNetwork',
|
||||
align: 'center',
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'HarvesterListHostNetworkConfig',
|
||||
components: { ResourceTable, Loading },
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.HOST_NETWORK_CONFIG });
|
||||
},
|
||||
|
||||
data() {
|
||||
return { rows: [] };
|
||||
},
|
||||
|
||||
computed: {
|
||||
schema() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return this.$store.getters[`${ inStore }/schemaFor`](HCI.HOST_NETWORK_CONFIG);
|
||||
},
|
||||
headers() {
|
||||
return [
|
||||
STATE,
|
||||
NAME_COL,
|
||||
UNDERLAY,
|
||||
VLAN_ID,
|
||||
MODE,
|
||||
CLUSTER_NETWORK,
|
||||
AGE,
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Loading v-if="$fetchState.pending" />
|
||||
<ResourceTable
|
||||
v-else
|
||||
v-bind="$attrs"
|
||||
:headers="headers"
|
||||
:rows="rows"
|
||||
:schema="schema"
|
||||
key-field="_key"
|
||||
/>
|
||||
</template>
|
||||
@ -16,6 +16,7 @@ export const HCI = {
|
||||
RESTORE: 'harvesterhci.io.virtualmachinerestore',
|
||||
NODE_NETWORK: 'network.harvesterhci.io.nodenetwork',
|
||||
CLUSTER_NETWORK: 'network.harvesterhci.io.clusternetwork',
|
||||
HOST_NETWORK_CONFIG: 'network.harvesterhci.io.hostnetworkconfig',
|
||||
SUBNET: 'kubeovn.io.subnet',
|
||||
VPC: 'kubeovn.io.vpc',
|
||||
IP: 'kubeovn.io.ip',
|
||||
@ -62,6 +63,7 @@ export const HCI = {
|
||||
VMIMPORT_SOURCE_OVA: 'migration.harvesterhci.io.ovasource',
|
||||
VMIMPORT: 'migration.harvesterhci.io.virtualmachineimport',
|
||||
MIGRATION: 'migration.harvesterhci.io',
|
||||
|
||||
};
|
||||
|
||||
export const VOLUME_SNAPSHOT = 'snapshot.storage.k8s.io.volumesnapshot';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user