mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-03 04:36:04 +08:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2aaa0a55a2 | ||
|
|
5c2a23924d | ||
|
|
7af8a82838 | ||
|
|
3b343bcaca | ||
|
|
6ddd35d661 | ||
|
|
bd28ba6f71 | ||
|
|
4bb67153ce | ||
|
|
7dbd442519 | ||
|
|
5a0d7f283d | ||
|
|
9fdbe9c58f | ||
|
|
f652ed9d4b | ||
|
|
21a1cd4e89 | ||
|
|
facc74ca51 |
@@ -50,6 +50,7 @@ jobs:
|
||||
|
||||
echo "FROM=$FROM"
|
||||
echo "TO=$TO"
|
||||
|
||||
npx commitlint --from "$FROM" --to "$TO" --verbose
|
||||
env:
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
|
||||
@@ -8,4 +8,5 @@ export const DOC = {
|
||||
STORAGE_NETWORK_EXAMPLE: `/advanced/storagenetwork#configuration-example`,
|
||||
SUPPORT_BUNDLE_NAMESPACES: `/advanced/index/#support-bundle-namespaces`,
|
||||
VPC_CONFIGURATION_EXAMPLES: `/networking/kubeovn-vpc#vpc-peering-configuration-examples`,
|
||||
NETWORK_POLICY: `/networking/kubeovn-vm-isolation/#network-policies`,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,6 @@ const FEATURE_FLAGS = {
|
||||
],
|
||||
'v1.5.1': [],
|
||||
'v1.6.0': [
|
||||
'vmMachineTypes',
|
||||
'customSupportBundle',
|
||||
'csiOnlineExpandValidation',
|
||||
'vmNetworkMigration',
|
||||
@@ -50,7 +49,9 @@ const FEATURE_FLAGS = {
|
||||
'vmCloneRunStrategy',
|
||||
],
|
||||
'v1.6.1': [],
|
||||
'v1.7.0': []
|
||||
'v1.7.0': [
|
||||
'vmMachineTypeAuto'
|
||||
]
|
||||
};
|
||||
|
||||
const generateFeatureFlags = () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
LOGGING,
|
||||
STORAGE_CLASS,
|
||||
SECRET,
|
||||
NETWORK_POLICY
|
||||
} from '@shell/config/types';
|
||||
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
||||
import {
|
||||
@@ -425,6 +426,7 @@ export function init($plugin, store) {
|
||||
HCI.CLUSTER_NETWORK,
|
||||
HCI.NETWORK_ATTACHMENT,
|
||||
HCI.VPC,
|
||||
NETWORK_POLICY,
|
||||
HCI.LB,
|
||||
HCI.IP_POOL,
|
||||
],
|
||||
@@ -566,6 +568,21 @@ export function init($plugin, store) {
|
||||
ifHaveType: HCI.VPC,
|
||||
});
|
||||
|
||||
configureType(NETWORK_POLICY, { hiddenNamespaceGroupButton: true, canYaml: false });
|
||||
|
||||
virtualType({
|
||||
labelKey: 'harvester.networkPolicy.label',
|
||||
name: NETWORK_POLICY,
|
||||
namespaced: true,
|
||||
weight: 186,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: NETWORK_POLICY }
|
||||
},
|
||||
exact: false,
|
||||
ifHaveType: NETWORK_POLICY,
|
||||
});
|
||||
|
||||
configureType(HCI.SNAPSHOT, {
|
||||
isCreatable: false,
|
||||
location: {
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Checkbox } from '@components/Form/Checkbox';
|
||||
import { HCI } from '../../types';
|
||||
import { DOC } from '../../config/doc-links';
|
||||
import { docLink } from '../../utils/feature-flags';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { clone } from '@shell/utils/object';
|
||||
|
||||
export const ksmtunedMode = [{
|
||||
value: 'standard',
|
||||
@@ -64,11 +66,13 @@ export default {
|
||||
|
||||
this.enableMergeAcrossNodes = !!this.ksmtuned.spec?.mergeAcrossNodes;
|
||||
this.spec = this.ksmtuned.spec;
|
||||
this.originSpec = clone(this.ksmtuned.spec);
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
ksmtuned: {},
|
||||
originSpec: {},
|
||||
spec: {},
|
||||
thresCoef: 30,
|
||||
ksmtunedMode,
|
||||
@@ -99,6 +103,11 @@ export default {
|
||||
|
||||
methods: {
|
||||
async saveKsmtuned() {
|
||||
// no ksmtuned spec change, skip save
|
||||
if (isEqual(this.spec, this.originSpec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.spec.mergeAcrossNodes = this.enableMergeAcrossNodes ? 1 : 0;
|
||||
this.ksmtuned['spec'] = this.spec;
|
||||
|
||||
|
||||
@@ -527,7 +527,6 @@ export default {
|
||||
id: this.longhornNode.id,
|
||||
opt: { force: true },
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, '5000'));
|
||||
await retrySave();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
||||
import { removeAt } from '@shell/utils/array';
|
||||
import { Banner } from '@components/Banner';
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
|
||||
export default {
|
||||
name: 'AccessControlList',
|
||||
|
||||
emits: ['update:value'],
|
||||
|
||||
components: {
|
||||
Banner, InfoBox, LabeledSelect, LabeledInput
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: _EDIT,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
const rows = (this.value || []).map((row) => {
|
||||
return {
|
||||
action: row.action || '',
|
||||
direction: row.direction || '',
|
||||
priority: row.priority || 0,
|
||||
match: row.match || '',
|
||||
};
|
||||
});
|
||||
|
||||
return { rows };
|
||||
},
|
||||
|
||||
computed: {
|
||||
isView() {
|
||||
return this.mode === _VIEW;
|
||||
},
|
||||
|
||||
actionOptions() {
|
||||
return [
|
||||
{ label: 'Allow', value: 'allow' },
|
||||
{ label: 'Drop', value: 'drop' },
|
||||
{ label: 'Pass', value: 'pass' },
|
||||
{ label: 'Reject', value: 'reject' },
|
||||
{ label: 'Allow-related', value: 'allow-related' },
|
||||
{ label: 'Allow-stateless', value: 'allow-stateless' },
|
||||
];
|
||||
},
|
||||
directionOptions() {
|
||||
return [
|
||||
{ label: 'To-lport', value: 'to-lport' },
|
||||
{ label: 'From-lport', value: 'from-lport' },
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.queueUpdate = debounce(this.update, 100);
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.rows.push({
|
||||
action: '',
|
||||
direction: '',
|
||||
priority: this.rows.length,
|
||||
match: '',
|
||||
});
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
removeRule(idx) {
|
||||
removeAt(this.rows, idx);
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
update() {
|
||||
if (this.isView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('update:value', this.rows);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Banner
|
||||
v-if="rows.length > 0"
|
||||
color="info"
|
||||
>
|
||||
<t
|
||||
k="harvester.subnet.acl.banner"
|
||||
:raw="true"
|
||||
/>
|
||||
</Banner>
|
||||
<div
|
||||
v-for="(row, idx) in rows"
|
||||
:key="idx"
|
||||
>
|
||||
<InfoBox class="box">
|
||||
<button
|
||||
type="button"
|
||||
class="role-link btn btn-sm removeBtn"
|
||||
:disabled="isView"
|
||||
@click="removeRule(idx)"
|
||||
>
|
||||
<i class="icon icon-x" />
|
||||
</button>
|
||||
<div class="row">
|
||||
<div class="col span-4">
|
||||
<LabeledSelect
|
||||
v-model:value="row.action"
|
||||
:mode="mode"
|
||||
class="mt-5 ml-5"
|
||||
:options="actionOptions"
|
||||
:required="true"
|
||||
:label="t('harvester.subnet.acl.action.label')"
|
||||
:placeholder="t('harvester.subnet.acl.action.placeholder')"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-4">
|
||||
<LabeledSelect
|
||||
v-model:value="row.direction"
|
||||
:mode="mode"
|
||||
:options="directionOptions"
|
||||
class="mt-5"
|
||||
:required="true"
|
||||
:label="t('harvester.subnet.acl.direction.label')"
|
||||
:placeholder="t('harvester.subnet.acl.direction.placeholder')"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-3">
|
||||
<LabeledInput
|
||||
v-model:value.number="row.priority"
|
||||
type="number"
|
||||
class="mb-20 mt-5"
|
||||
:max="32767"
|
||||
:min="0"
|
||||
:mode="mode"
|
||||
required
|
||||
label-key="harvester.subnet.acl.priority.label"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col span-11">
|
||||
<LabeledInput
|
||||
v-model:value="row.match"
|
||||
class="mb-5 ml-5"
|
||||
:mode="mode"
|
||||
required
|
||||
:placeholder="t('harvester.subnet.acl.match.placeholder')"
|
||||
label-key="harvester.subnet.acl.match.label"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</InfoBox>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn role-tertiary add"
|
||||
:disabled="isView"
|
||||
@click="add()"
|
||||
>
|
||||
<t k="harvester.subnet.acl.addRule" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
position: relative;
|
||||
}
|
||||
.removeBtn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -15,6 +15,7 @@ import { allHash } from '@shell/utils/promise';
|
||||
import { HCI } from '../../types';
|
||||
import ResourceTabs from '@shell/components/form/ResourceTabs/index';
|
||||
import { Banner } from '@components/Banner';
|
||||
import AccessControlList from './AccessControlList';
|
||||
|
||||
export default {
|
||||
name: 'EditSubnet',
|
||||
@@ -32,6 +33,7 @@ export default {
|
||||
ArrayList,
|
||||
ResourceTabs,
|
||||
Loading,
|
||||
AccessControlList
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
@@ -51,7 +53,8 @@ export default {
|
||||
gatewayIP: '',
|
||||
excludeIps: [],
|
||||
private: false,
|
||||
enableDHCP
|
||||
enableDHCP,
|
||||
acls: []
|
||||
});
|
||||
},
|
||||
|
||||
@@ -143,6 +146,7 @@ export default {
|
||||
async saveSubnet(buttonCb) {
|
||||
const errors = [];
|
||||
const name = this.value?.metadata?.name;
|
||||
const hasEmptyAcls = this.value?.spec?.acls?.some((acl) => !acl.match || !acl.action || acl.priority === undefined || acl.priority === null);
|
||||
|
||||
try {
|
||||
if (!name) {
|
||||
@@ -153,6 +157,8 @@ export default {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.subnet.provider.label') }, true));
|
||||
} else if (this.value.spec.excludeIps.includes('')) {
|
||||
errors.push(this.t('harvester.validation.subnet.excludeIps'));
|
||||
} else if (hasEmptyAcls) {
|
||||
errors.push(this.t('harvester.validation.subnet.aclEmptyError'));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
@@ -371,6 +377,17 @@ export default {
|
||||
</template>
|
||||
</ArrayList>
|
||||
</Tab>
|
||||
<Tab
|
||||
name="ACL"
|
||||
:label="t('harvester.subnet.acl.label')"
|
||||
:weight="-2"
|
||||
class="bordered-table"
|
||||
>
|
||||
<AccessControlList
|
||||
v-model:value="value.spec.acls"
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
</ResourceTabs>
|
||||
</CruResource>
|
||||
</template>
|
||||
|
||||
@@ -338,6 +338,15 @@ export default {
|
||||
saveVM(buttonCb) {
|
||||
clear(this.errors);
|
||||
|
||||
this.validateCPUMemory();
|
||||
|
||||
// block create VM flow if has validation errors
|
||||
if (this.errors.length) {
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSingle) {
|
||||
this.saveSingle(buttonCb);
|
||||
} else {
|
||||
@@ -345,6 +354,18 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
validateCPUMemory() {
|
||||
const { cpu, memory } = this;
|
||||
|
||||
if ((!cpu)) {
|
||||
this.errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.cpu') }, true));
|
||||
}
|
||||
|
||||
if ((!memory)) {
|
||||
this.errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.memory') }, true));
|
||||
}
|
||||
},
|
||||
|
||||
async saveSingle(buttonCb) {
|
||||
this.parseVM();
|
||||
this.value.spec.template.spec.hostname = this.hostname ? this.hostname : this.value.metadata.name;
|
||||
@@ -466,9 +487,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const cpuMemoryErrors = this.getCPUMemoryValidation();
|
||||
const accessCredentialsErrors = this.getAccessCredentialsValidation();
|
||||
const errors = [...cpuMemoryErrors, ...accessCredentialsErrors];
|
||||
const errors = this.getAccessCredentialsValidation();
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
@@ -873,7 +892,10 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col span-6">
|
||||
<div
|
||||
v-if="!value.vmMachineTypeAutoFeatureEnabled"
|
||||
class="col span-6"
|
||||
>
|
||||
<LabeledSelect
|
||||
v-model:value="machineType"
|
||||
label-key="harvester.virtualMachine.input.MachineType"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<script>
|
||||
import NetworkPolicy from '@shell/edit/networking.k8s.io.networkpolicy';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { DOC } from '../config/doc-links';
|
||||
import { docLink } from '../utils/feature-flags';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NetworkPolicy,
|
||||
Banner
|
||||
},
|
||||
|
||||
computed: {
|
||||
networkPolicyDocLink() {
|
||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
||||
|
||||
return docLink(DOC.NETWORK_POLICY, version);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="network-policy-edit">
|
||||
<Banner color="info">
|
||||
<t
|
||||
k="harvester.networkPolicy.banner"
|
||||
:url="networkPolicyDocLink"
|
||||
:raw="true"
|
||||
/>
|
||||
</Banner>
|
||||
<NetworkPolicy v-bind="$attrs" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.network-policy-edit {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -404,7 +404,7 @@ harvester:
|
||||
sha512: 'Invalid SHA512 checksum.'
|
||||
subnet:
|
||||
excludeIps: 'Exclude IPs cannot be empty. Please remove or fill in the exclude IPs.'
|
||||
|
||||
aclEmptyError: The fields in subnet access control list rule can not be empty.
|
||||
dashboard:
|
||||
label: Dashboard
|
||||
header: "Harvester Cluster: {cluster}"
|
||||
@@ -1062,7 +1062,24 @@ harvester:
|
||||
placeholder: e.g. 172.16.0.0/16
|
||||
excludeIPs:
|
||||
tooltip: The IP address list to reserve from automatic assignment. The gateway IP address is always excluded and will be automatically added to the list.
|
||||
|
||||
acl:
|
||||
label: Access Control List
|
||||
tooltip: The ACL to apply to this Subnet. Must be one of the ACLs in the same namespace.
|
||||
action:
|
||||
label: Action
|
||||
placeholder: Please select an action
|
||||
direction:
|
||||
label: Direction
|
||||
placeholder: Please select a direction
|
||||
addRule: Add Rule
|
||||
priority:
|
||||
label: Priority
|
||||
placeholder: Please select a priority
|
||||
match:
|
||||
label: Match
|
||||
placeholder: e.g. ip4.dst == 10.10.0.2
|
||||
banner: The supported field in ACL match can refer to <a href="https://kubeovn.github.io/docs/v1.14.x/en/guide/subnet/#subnet-acl" target="_blank">KubeOvn Subnet ACL document</a>
|
||||
|
||||
vpc:
|
||||
noAddonEnabled:
|
||||
prefix: The kubeovn-operator add-on is not enabled, click
|
||||
@@ -1089,6 +1106,9 @@ harvester:
|
||||
remoteVpc:
|
||||
label: Remote VPC
|
||||
infoBanner: The static route destination CIDR must cover all subnets CIDR from remote VPC Peer. Read <a href="{url}" target="_blank">VPC Peering Configuration Examples</a> for more information.
|
||||
networkPolicy:
|
||||
label: Network Policies
|
||||
banner: The network policies must be used for VMs attached to overlay networks. Please read the <a href="{url}" target="_blank">harvester document</a> how the network policy works.
|
||||
network:
|
||||
label: Virtual Machine Networks
|
||||
tabs:
|
||||
@@ -1815,6 +1835,11 @@ typeLabel:
|
||||
one { Virtual Private Cloud }
|
||||
other { Virtual Private Clouds }
|
||||
}
|
||||
networking.k8s.io.networkpolicy: |-
|
||||
{count, plural,
|
||||
one { Network Policy }
|
||||
other { Network Policies }
|
||||
}
|
||||
harvesterhci.io.cloudtemplate: |-
|
||||
{count, plural,
|
||||
one { Cloud Configuration Template }
|
||||
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const _hash = {
|
||||
vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM }),
|
||||
vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM, opt: { force: true } }),
|
||||
pod: this.$store.dispatch(`${ inStore }/findAll`, { type: POD }),
|
||||
pvcs: this.$store.dispatch(`${ inStore }/findAll`, { type: PVC }),
|
||||
pvs: this.$store.dispatch(`${ inStore }/findAll`, { type: PV }),
|
||||
|
||||
@@ -273,7 +273,7 @@ export default {
|
||||
|
||||
needNewSecret() {
|
||||
// When creating a template it is always necessary to create a new secret.
|
||||
return this.showYaml ? false : this.resourceType === HCI.VM_VERSION || this.isCreate;
|
||||
return this.isCreate ? true : this.showYaml ? false : this.resourceType === HCI.VM_VERSION;
|
||||
},
|
||||
|
||||
defaultTerminationSetting() {
|
||||
@@ -299,9 +299,11 @@ export default {
|
||||
async created() {
|
||||
await this.$store.dispatch(`${ this.inStore }/findAll`, { type: SECRET });
|
||||
|
||||
if (this.value.vmMachineTypesFeatureEnabled) {
|
||||
if (!this.value.vmMachineTypeAutoFeatureEnabled) {
|
||||
try {
|
||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/clusters/local?link=machineTypes');
|
||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl'](
|
||||
'v1/harvester/clusters/local?link=machineTypes'
|
||||
);
|
||||
const machineTypes = await this.$store.dispatch('harvester/request', { url });
|
||||
|
||||
this.machineTypes = machineTypes;
|
||||
@@ -687,14 +689,12 @@ export default {
|
||||
set(this.spec.template.spec, 'domain.memory.maxGuest', this.maxMemory);
|
||||
set(this.spec.template.spec, 'domain.resources.limits.memory', this.maxMemory);
|
||||
} else {
|
||||
this.spec.template.spec.domain.cpu.maxSockets = 1;
|
||||
this.spec.template.spec.domain.cpu.sockets = 1;
|
||||
this.spec.template.spec.domain.cpu.cores = this.cpu;
|
||||
this.spec.template.spec.domain.resources.limits.cpu = this.cpu?.toString();
|
||||
this.spec.template.spec.domain.resources.limits.memory = this.memory;
|
||||
// clean below fields as we don't need them if not enable CPU and memory hotplug
|
||||
if (this.spec?.template?.spec?.domain?.cpu?.maxSockets) {
|
||||
delete this.spec.template.spec.domain.cpu.maxSockets;
|
||||
}
|
||||
if (this.spec?.template?.spec?.domain?.memory?.maxGuest) {
|
||||
delete this.spec.template.spec.domain.memory.maxGuest;
|
||||
}
|
||||
@@ -892,7 +892,7 @@ export default {
|
||||
});
|
||||
|
||||
const specInterfaces = this.spec?.template?.spec?.domain?.devices?.interfaces;
|
||||
const mergedInterfaces = this.mergeDeviceList(specInterfaces, interfaces);
|
||||
const mergedInterfaces = this.mergeInterfaceList(specInterfaces, interfaces);
|
||||
|
||||
const spec = {
|
||||
...this.spec.template.spec,
|
||||
@@ -1369,21 +1369,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getCPUMemoryValidation() {
|
||||
const errors = [];
|
||||
const { cpu, memory } = this;
|
||||
|
||||
if ((!cpu)) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.cpu') }, true));
|
||||
}
|
||||
|
||||
if ((!memory)) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.memory') }, true));
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
|
||||
getAccessCredentialsValidation() {
|
||||
const errors = [];
|
||||
|
||||
@@ -1551,6 +1536,32 @@ export default {
|
||||
this.refreshYamlEditor();
|
||||
},
|
||||
|
||||
mergeInterfaceList(specInterfaceList, interfaceList) {
|
||||
if (!specInterfaceList || specInterfaceList.length === 0) {
|
||||
return interfaceList;
|
||||
}
|
||||
const specInterfaceMap = new Map(specInterfaceList.map((iface) => [iface.name, iface]));
|
||||
|
||||
return interfaceList.map((iface) => {
|
||||
const specInterface = specInterfaceMap.get(iface.name);
|
||||
|
||||
if (specInterface) {
|
||||
const merged = { ...specInterface, ...iface };
|
||||
|
||||
// currently we only have bridge and masquerade network type, they are mutually exclusive
|
||||
if (iface['bridge']) {
|
||||
delete merged['masquerade'];
|
||||
} else {
|
||||
delete merged['bridge'];
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
return iface;
|
||||
});
|
||||
},
|
||||
|
||||
mergeDeviceList(specDeviceList, deviceList) {
|
||||
if (!specDeviceList || specDeviceList.length === 0) {
|
||||
return deviceList;
|
||||
|
||||
@@ -1236,8 +1236,8 @@ export default class VirtVm extends HarvesterResource {
|
||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||
}
|
||||
|
||||
get vmMachineTypesFeatureEnabled() {
|
||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypes');
|
||||
get vmMachineTypeAutoFeatureEnabled() {
|
||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypeAuto');
|
||||
}
|
||||
|
||||
get isBackupTargetUnavailable() {
|
||||
|
||||
@@ -16,7 +16,10 @@ export default class HciVlanConfig extends HarvesterResource {
|
||||
uplink: {
|
||||
nics: [],
|
||||
linkAttributes: {},
|
||||
bondOptions: { mode: 'active-backup' },
|
||||
bondOptions: {
|
||||
mode: 'active-backup',
|
||||
miimon: -1
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,17 +3,19 @@ import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations
|
||||
export function getVmCPUMemoryValues(vm) {
|
||||
if (!vm) {
|
||||
return {
|
||||
cpu: 0,
|
||||
cpu: null,
|
||||
memory: null,
|
||||
isHotplugEnabled: false
|
||||
};
|
||||
}
|
||||
|
||||
const isHotplugEnabled = isCPUMemoryHotPlugEnabled(vm);
|
||||
const { sockets = 1, threads = 1, cores = null } = vm.spec.template.spec.domain.cpu || {};
|
||||
const cpu = cores === null ? null : sockets * threads * cores;
|
||||
|
||||
if (isHotplugEnabled) {
|
||||
return {
|
||||
cpu: vm.spec.template.spec.domain.cpu.sockets,
|
||||
cpu,
|
||||
memory: vm.spec.template.spec.domain?.memory?.guest || null,
|
||||
maxCpu: vm.spec.template.spec.domain.cpu?.maxSockets || 0,
|
||||
maxMemory: vm.spec.template.spec.domain?.memory?.maxGuest || null,
|
||||
@@ -21,7 +23,7 @@ export function getVmCPUMemoryValues(vm) {
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
cpu: vm.spec.template.spec.domain.cpu.cores,
|
||||
cpu,
|
||||
memory: vm.spec.template.spec.domain.resources?.limits?.memory || null,
|
||||
isHotplugEnabled
|
||||
};
|
||||
@@ -29,8 +31,5 @@ export function getVmCPUMemoryValues(vm) {
|
||||
}
|
||||
|
||||
export function isCPUMemoryHotPlugEnabled(vm) {
|
||||
return vm?.metadata?.annotations[HCI_ANNOTATIONS.VM_CPU_MEMORY_HOTPLUG] === 'true' ||
|
||||
!!vm?.spec?.template?.spec?.domain.cpu?.maxSockets ||
|
||||
!!vm?.spec?.template?.spec?.domain?.memory?.maxGuest ||
|
||||
false;
|
||||
return vm?.metadata?.annotations[HCI_ANNOTATIONS.VM_CPU_MEMORY_HOTPLUG] === 'true' || !!vm?.spec?.template?.spec?.domain?.memory?.maxGuest || false;
|
||||
}
|
||||
|
||||
@@ -3114,9 +3114,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@20.10.8", "@types/node@^14.14.31", "@types/node@~20.19.0":
|
||||
version "20.19.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.13.tgz#b79004a05068e28fb2de281b3a44c5c993650e59"
|
||||
integrity sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==
|
||||
version "20.19.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.19.tgz#18c8982becd5728f92e5f1939f2f3dc85604abcd"
|
||||
integrity sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user