Compare commits

...
Author SHA1 Message Date
Andy Lee 5d996c42dc ci: allow any string as commit scope (#571)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-23 14:18:02 +08:00
Andy Lee 88cd302ce4 fix: trunk input field is missing when creating L2VlanTrunkNetwork (#570)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-23 09:47:47 +08:00
Yiya Chen e6dd8d6771 feat: online Volume Resizing from the VM Page (#568)
* feat: add conditions tab
* feat: ignore restart for resizing
* refactor: remove unused code
---------
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-10-22 17:33:28 +08:00
Andy Lee 1a3822881e fix: view and edit mode for l2vlan trunk (#563)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-20 14:52:32 +08:00
renovate[bot] ee8bb21a10 deps: update dependency @types/node to v20.19.22 (#564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:44:54 +08:00
Yiya Chen e3d30a0eec feat: make size editable (#562)
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-10-17 15:36:36 +08:00
Andy Lee 532b6c4d50 feat: add access / trunk mode in create VM network page (#510)
* feat: add l2VlanTrunkMode feature

Signed-off-by: Andy Lee <andy.lee@suse.com>

* refactor: remove unneeded code

Signed-off-by: Andy Lee <andy.lee@suse.com>

* refactor: fix edit l2vlan trunk mode edit page

Signed-off-by: Andy Lee <andy.lee@suse.com>

* fix: hide Route tab when trunk mode

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-17 15:28:21 +08:00
Yiya Chen f22644e058 fix: reset filesystem (#559)
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-10-17 12:15:58 +08:00
Andy Lee cd128d0444 chore: limit v1.7.0 to install on Rancher 2.13.0+ (#558)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-15 17:38:09 +08:00
Yiya Chen 0278f51260 fix: WebVNC console shortcut keys not working correctly (#557)
* style: disable overflow

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

* style: fix class name

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-10-13 16:03:16 +08:00
Andy Lee 98efd63110 fix: abort upload when switch to URL option (#552)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-10-13 14:48:37 +08:00
renovate[bot] 78e78ac8dd deps: update patch digest dependencies (#553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-12 22:27:42 +08:00
15 changed files with 302 additions and 84 deletions
+4
View File
@@ -22,6 +22,10 @@ module.exports = {
'security', // Security fixes 'security', // Security fixes
] ]
], ],
// Allows the scope to be empty. [0] means the rule is disabled.
'scope-empty': [0, 'always'],
// Allows any string for the scope.
'scope-case': [2, 'always', 'kebab-case'],
'type-case': [2, 'always', 'lower-case'], 'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'], 'type-empty': [2, 'never'],
'subject-case': [0, 'never'], 'subject-case': [0, 'never'],
+1 -1
View File
@@ -30,7 +30,7 @@
"nth-check": "2.1.1", "nth-check": "2.1.1",
"qs": "6.14.0", "qs": "6.14.0",
"roarr": "7.21.1", "roarr": "7.21.1",
"semver": "7.7.2", "semver": "7.7.3",
"@vue/cli-service/html-webpack-plugin": "^5.0.0" "@vue/cli-service/html-webpack-plugin": "^5.0.0"
}, },
"scripts": { "scripts": {
@@ -391,4 +391,7 @@ export default {
.vm-console .v-popper__popper{ .vm-console .v-popper__popper{
margin-top: 8px; margin-top: 8px;
} }
.vm-console .v-popper__inner{
overflow-y: visible;
}
</style> </style>
+3 -1
View File
@@ -50,7 +50,9 @@ const FEATURE_FLAGS = {
], ],
'v1.6.1': [], 'v1.6.1': [],
'v1.7.0': [ 'v1.7.0': [
'vmMachineTypeAuto' 'vmMachineTypeAuto',
'lhV2VolExpansion',
'l2VlanTrunkMode'
] ]
}; };
+9 -3
View File
@@ -4,9 +4,10 @@ export const BACKUP_TYPE = {
}; };
export const NETWORK_TYPE = { export const NETWORK_TYPE = {
L2VLAN: 'L2VlanNetwork', L2VLAN: 'L2VlanNetwork',
UNTAGGED: 'UntaggedNetwork', UNTAGGED: 'UntaggedNetwork',
OVERLAY: 'OverlayNetwork', OVERLAY: 'OverlayNetwork',
L2TRUNK_VLAN: 'L2VlanTrunkNetwork',
}; };
export const VOLUME_MODE = { export const VOLUME_MODE = {
@@ -23,3 +24,8 @@ export const INTERNAL_STORAGE_CLASS = {
VMSTATE_PERSISTENCE: 'vmstate-persistence', VMSTATE_PERSISTENCE: 'vmstate-persistence',
LONGHORN_STATIC: 'longhorn-static', LONGHORN_STATIC: 'longhorn-static',
}; };
export const L2VLAN_MODE = {
ACCESS: 'access',
TRUNK: 'trunk',
};
@@ -8,7 +8,7 @@ import LabelValue from '@shell/components/LabelValue';
import { BadgeState } from '@components/BadgeState'; import { BadgeState } from '@components/BadgeState';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import LabeledSelect from '@shell/components/form/LabeledSelect'; import LabeledSelect from '@shell/components/form/LabeledSelect';
import { RadioGroup, RadioButton } from '@components/Form/Radio'; import { RadioGroup } from '@components/Form/Radio';
import HarvesterDisk from '../../mixins/harvester-disk'; import HarvesterDisk from '../../mixins/harvester-disk';
import Tags from '../../components/DiskTags'; import Tags from '../../components/DiskTags';
import { HCI } from '../../types'; import { HCI } from '../../types';
@@ -30,7 +30,6 @@ export default {
BadgeState, BadgeState,
Banner, Banner,
RadioGroup, RadioGroup,
RadioButton,
ModalWithCard, ModalWithCard,
Tags, Tags,
}, },
@@ -184,12 +183,11 @@ export default {
}, },
forceFormattedDisabled() { forceFormattedDisabled() {
const lastFormattedAt = this.blockDevice?.status?.deviceStatus?.fileSystem?.LastFormattedAt;
const fileSystem = this.blockDevice?.status?.deviceStatus?.fileSystem.type; const fileSystem = this.blockDevice?.status?.deviceStatus?.fileSystem.type;
const systems = ['ext4', 'XFS']; const systems = ['ext4', 'XFS'];
if (lastFormattedAt || this.blockDevice?.childParts?.length > 0) { if (this.blockDevice?.childParts?.length > 0) {
return true; return true;
} else if (systems.includes(fileSystem)) { } else if (systems.includes(fileSystem)) {
return false; return false;
@@ -446,7 +444,7 @@ export default {
/> />
</div> </div>
<div <div
v-if="(value.isNew && isLonghornV1 && !isFormatted) || isCorrupted" v-if="(value.isNew && isLonghornV1) || isCorrupted"
class="col span-6" class="col span-6"
> >
<RadioGroup <RadioGroup
@@ -459,15 +457,6 @@ export default {
:disabled="forceFormattedDisabled" :disabled="forceFormattedDisabled"
tooltip-key="harvester.host.disk.forceFormatted.toolTip" tooltip-key="harvester.host.disk.forceFormatted.toolTip"
> >
<template #1="{option, listeners}">
<RadioButton
:label="option.label"
:val="option.value"
:value="value.forceFormatted"
:disabled="forceFormattedDisabled && !value.forceFormatted"
v-on="listeners"
/>
</template>
</RadioGroup> </RadioGroup>
</div> </div>
<div <div
@@ -10,9 +10,13 @@ import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-anno
import CreateEditView from '@shell/mixins/create-edit-view'; import CreateEditView from '@shell/mixins/create-edit-view';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import { HCI } from '../types'; import { HCI } from '../types';
import { NETWORK_TYPE } from '../config/types'; import { NETWORK_TYPE, L2VLAN_MODE } from '../config/types';
import { removeObject } from '@shell/utils/array';
const { L2VLAN, UNTAGGED, OVERLAY } = NETWORK_TYPE; const {
L2VLAN, UNTAGGED, OVERLAY, L2TRUNK_VLAN
} = NETWORK_TYPE;
const { ACCESS, TRUNK } = L2VLAN_MODE;
const AUTO = 'auto'; const AUTO = 'auto';
const MANUAL = 'manual'; const MANUAL = 'manual';
@@ -52,6 +56,8 @@ export default {
return { return {
config, config,
type, type,
l2VlanMode: this.value.vlanType === L2TRUNK_VLAN ? TRUNK : ACCESS,
vlanTrunk: this.parseVlanTrunk(config),
layer3Network: { layer3Network: {
mode: layer3Network.mode || AUTO, mode: layer3Network.mode || AUTO,
serverIPAddr: layer3Network.serverIPAddr || '', serverIPAddr: layer3Network.serverIPAddr || '',
@@ -108,6 +114,10 @@ export default {
}]; }];
}, },
l2VlanTrunkModeFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('l2VlanTrunkMode');
},
kubeovnVpcSubnetSupport() { kubeovnVpcSubnetSupport() {
return this.$store.getters['harvester-common/getFeatureEnabled']('kubeovnVpcSubnet'); return this.$store.getters['harvester-common/getFeatureEnabled']('kubeovnVpcSubnet');
}, },
@@ -131,6 +141,16 @@ export default {
}); });
}, },
l2VlanModeOptions() {
return [{
label: this.t('harvester.vlanStatus.vlanConfig.l2VlanMode.access'),
value: ACCESS,
}, {
label: this.t('harvester.vlanStatus.vlanConfig.l2VlanMode.trunk'),
value: TRUNK,
}];
},
networkTypes() { networkTypes() {
const types = [L2VLAN, UNTAGGED]; const types = [L2VLAN, UNTAGGED];
@@ -149,6 +169,22 @@ export default {
return this.type === L2VLAN; return this.type === L2VLAN;
}, },
isL2VlanTrunkMode() {
if (this.isView) {
return this.value.vlanType === L2TRUNK_VLAN && this.l2VlanMode === TRUNK;
}
return this.type === L2TRUNK_VLAN || (this.l2VlanMode === TRUNK && this.type === L2VLAN);
},
isL2VlanAccessMode() {
if (this.isView) {
return this.value.vlanType === L2VLAN && this.l2VlanMode === ACCESS;
}
return this.type === L2VLAN && this.l2VlanMode === ACCESS;
},
isOverlayNetwork() { isOverlayNetwork() {
if (this.isView) { if (this.isView) {
return this.value.vlanType === OVERLAY; return this.value.vlanType === OVERLAY;
@@ -168,11 +204,11 @@ export default {
watch: { watch: {
type(newType) { type(newType) {
if (newType === OVERLAY) { if (newType === OVERLAY) { // overlay network configuration
this.config.type = 'kube-ovn'; this.config.type = 'kube-ovn';
this.config.provider = `${ this.value.metadata.name }.${ this.value.metadata.namespace }.ovn`; this.config.provider = `${ this.value.metadata.name }.${ this.value.metadata.namespace }.ovn`;
this.config.server_socket = '/run/openvswitch/kube-ovn-daemon.sock'; this.config.server_socket = '/run/openvswitch/kube-ovn-daemon.sock';
} else { } else { // l2vlan or untagged network configuration
this.config.type = 'bridge'; this.config.type = 'bridge';
this.config.promiscMode = true; this.config.promiscMode = true;
this.config.ipam = {}; this.config.ipam = {};
@@ -180,7 +216,20 @@ export default {
delete this.config.provider; delete this.config.provider;
delete this.config.server_socket; delete this.config.server_socket;
} }
} },
l2VlanMode(newAccessMode) {
if (this.type !== L2VLAN) {
return;
}
if (newAccessMode === TRUNK) { // trunk mode
this.config.vlan = 0;
this.config.vlanTrunk = this.vlanTrunk;
} else { // access mode
delete this.config.vlanTrunk;
this.config.vlan = '';
}
},
}, },
methods: { methods: {
@@ -188,7 +237,15 @@ export default {
const errors = []; const errors = [];
if (this.isL2VlanNetwork || this.isUntaggedNetwork) { if (this.isL2VlanNetwork || this.isUntaggedNetwork) {
if (!this.config.vlan && !this.isUntaggedNetwork) { if (this.isL2VlanTrunkMode && this.vlanTrunk.some((trunk) => trunk.minID === '')) {
errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.vlanStatus.vlanConfig.vlanTrunk.minId') }));
}
if (this.isL2VlanTrunkMode && this.vlanTrunk.some((trunk) => trunk.maxID === '')) {
errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.vlanStatus.vlanConfig.vlanTrunk.maxId') }));
}
if (this.isL2VlanAccessMode && !this.config.vlan && !this.isUntaggedNetwork) {
errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('tableHeaders.networkVlan') })); errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('tableHeaders.networkVlan') }));
} }
@@ -217,6 +274,30 @@ export default {
await this.save(buttonCb); await this.save(buttonCb);
}, },
parseVlanTrunk(config) {
if (config?.vlanTrunk && config?.vlanTrunk?.length > 0) {
return config.vlanTrunk;
}
return [{ minID: '', maxID: '' }];
},
removeVlanTrunk(trunk) {
removeObject(this.vlanTrunk, trunk);
},
addVlanTrunk() {
if (!this.config.vlanTrunk) {
this.config.vlanTrunk = [];
}
this.vlanTrunk.push({ minID: this.minId, maxID: this.maxId });
this.config.vlanTrunk = this.vlanTrunk;
},
vlanTrunkChange() {
this.config.vlanTrunk = this.vlanTrunk;
},
input(neu) { input(neu) {
if (neu === '') { if (neu === '') {
this.config.vlan = ''; this.config.vlan = '';
@@ -292,8 +373,76 @@ export default {
required required
/> />
<LabeledSelect
v-if="isL2VlanNetwork && l2VlanTrunkModeFeatureEnabled"
v-model:value="l2VlanMode"
class="mb-20"
:options="l2VlanModeOptions"
:mode="mode"
:disabled="isEdit"
:label="t('harvester.vlanStatus.vlanConfig.l2VlanMode.label')"
required
/>
<div v-if="isL2VlanTrunkMode && l2VlanTrunkModeFeatureEnabled">
<div
v-for="(trunk, i) in vlanTrunk"
:key="i"
>
<div class="row mt-10">
<div class="col trunk-span">
<LabeledInput
v-model:value.number="trunk.minID"
class="mb-20"
required
placeholder="e.g. 1-4094"
:min="1"
:max="4094"
type="number"
:label="t('harvester.vlanStatus.vlanConfig.vlanTrunk.minId')"
:mode="mode"
@update:value="vlanTrunkChange"
/>
</div>
<div class="col trunk-span">
<LabeledInput
v-model:value.number="trunk.maxID"
class="mb-20"
:max="4094"
:min="1"
placeholder="e.g. 1-4094"
required
type="number"
:label="t('harvester.vlanStatus.vlanConfig.vlanTrunk.maxId')"
:mode="mode"
@update:value="vlanTrunkChange"
/>
</div>
<div class="col remove-btn mb-20">
<button
type="button"
:disabled="isView || vlanTrunk.length <= 1"
:aria-label="t('generic.ariaLabel.remove', {index: i+1})"
role="button"
class="btn role-link"
@click="removeVlanTrunk(trunk)"
>
{{ t('harvester.fields.remove') }}
</button>
</div>
</div>
</div>
<button
v-if="isL2VlanTrunkMode"
type="button"
class="btn btn-sm bg-primary mb-20"
:disabled="isView"
@click="addVlanTrunk"
>
{{ t('harvester.vlanStatus.vlanConfig.vlanTrunk.add') }}
</button>
</div>
<LabeledInput <LabeledInput
v-if="isL2VlanNetwork" v-if="isL2VlanAccessMode"
v-model:value.number="config.vlan" v-model:value.number="config.vlan"
class="mb-20" class="mb-20"
required required
@@ -316,7 +465,7 @@ export default {
/> />
</Tab> </Tab>
<Tab <Tab
v-if="isL2VlanNetwork" v-if="isL2VlanAccessMode"
name="layer3Network" name="layer3Network"
:label="t('harvester.network.tabs.layer3Network')" :label="t('harvester.network.tabs.layer3Network')"
:weight="98" :weight="98"
@@ -375,3 +524,12 @@ export default {
</Tabbed> </Tabbed>
</CruResource> </CruResource>
</template> </template>
<style lang="scss" scoped>
.remove-btn {
align-self: center;
}
.trunk-span{
flex: 5;
}
</style>
+26 -3
View File
@@ -7,6 +7,7 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
import LabeledSelect from '@shell/components/form/LabeledSelect'; import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput'; import { LabeledInput } from '@components/Form/LabeledInput';
import NameNsDescription from '@shell/components/form/NameNsDescription'; import NameNsDescription from '@shell/components/form/NameNsDescription';
import Conditions from '@shell/components/form/Conditions';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import { get } from '@shell/utils/object'; import { get } from '@shell/utils/object';
@@ -40,6 +41,7 @@ export default {
LabeledSelect, LabeledSelect,
LabeledInput, LabeledInput,
NameNsDescription, NameNsDescription,
Conditions
}, },
mixins: [CreateEditView], mixins: [CreateEditView],
@@ -267,7 +269,19 @@ export default {
rebuildStatus() { rebuildStatus() {
return this.value.longhornEngine?.status?.rebuildStatus; return this.value.longhornEngine?.status?.rebuildStatus;
} },
isLHV2VolExpansionFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
},
isResizeDisabled() {
return (
!this.isLHV2VolExpansionFeatureEnabled &&
this.value?.isLonghornV2 &&
this.isEdit
);
},
}, },
watch: { watch: {
@@ -463,7 +477,7 @@ export default {
:output-modifier="true" :output-modifier="true"
:increment="1024" :increment="1024"
:mode="mode" :mode="mode"
:disabled="value?.isLonghornV2 && isEdit" :disabled="isResizeDisabled"
required required
class="mb-20" class="mb-20"
:suffix="GIBIBYTE" :suffix="GIBIBYTE"
@@ -471,7 +485,7 @@ export default {
/> />
<Banner <Banner
v-if="value?.isLonghornV2 && isEdit" v-if="isResizeDisabled"
color="warning" color="warning"
> >
<span>{{ t('harvester.volume.longhorn.disableResize') }}</span> <span>{{ t('harvester.volume.longhorn.disableResize') }}</span>
@@ -581,6 +595,15 @@ export default {
:label="t('nameNsDescription.name.label')" :label="t('nameNsDescription.name.label')"
/> />
</Tab> </Tab>
<Tab
v-if="!isCreate"
name="conditions"
:label="t('harvester.volume.tabs.conditions')"
class="bordered-table"
:mode="mode"
>
<Conditions :value="value" />
</Tab>
</ResourceTabs> </ResourceTabs>
</CruResource> </CruResource>
</template> </template>
@@ -136,6 +136,10 @@ export default {
pvcs() { pvcs() {
return this.$store.getters['harvester/all'](PVC) || []; return this.$store.getters['harvester/all'](PVC) || [];
}, },
isLHV2VolExpansionFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
},
}, },
watch: { watch: {
@@ -270,8 +274,8 @@ export default {
}, },
isResizeDisabled(volume) { isResizeDisabled(volume) {
if (this.isCreate) return false; if (this.isLHV2VolExpansionFeatureEnabled) return false;
if (volume.newCreateId) return false; if (this.isCreate || volume.newCreateId) return false;
const isStopped = this.vm.stateDisplay === OFF; const isStopped = this.vm.stateDisplay === OFF;
const isLonghornV2 = this.isLonghornV2(volume); const isLonghornV2 = this.isLonghornV2(volume);
@@ -406,7 +410,7 @@ export default {
:label="ucFirst(value.volumeBackups.error.message)" :label="ucFirst(value.volumeBackups.error.message)"
/> />
<Banner <Banner
v-if="isLonghornV2(volume) && !isView" v-if="!isLHV2VolExpansionFeatureEnabled && isLonghornV2(volume) && !isView"
color="warning" color="warning"
:label="t('harvester.volume.longhorn.disableResize')" :label="t('harvester.volume.longhorn.disableResize')"
/> />
@@ -2,7 +2,7 @@
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import Tabbed from '@shell/components/Tabbed'; import Tabbed from '@shell/components/Tabbed';
import { clone, set } from '@shell/utils/object'; import { clone } from '@shell/utils/object';
import Tab from '@shell/components/Tabbed/Tab'; import Tab from '@shell/components/Tabbed/Tab';
import { Checkbox } from '@components/Form/Checkbox'; import { Checkbox } from '@components/Form/Checkbox';
import CruResource from '@shell/components/CruResource'; import CruResource from '@shell/components/CruResource';
@@ -434,38 +434,44 @@ export default {
} }
}, },
_compareDisksIgnoreStorage(disksA = [], disksB = []) {
if (disksA.length !== disksB.length) return false;
const sortedA = [...disksA].sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
const sortedB = [...disksB].sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
for (let i = 0; i < sortedA.length; i++) {
const cloneA = clone(sortedA[i]);
const cloneB = clone(sortedB[i]);
// remove the storage field before comparison
delete cloneA?.spec?.resources?.requests?.storage;
delete cloneB?.spec?.resources?.requests?.storage;
if (!isEqual(cloneA, cloneB)) return false;
}
return true;
},
_shouldPromptRestart(oldVM, newVM) {
const specChanged = !isEqual(oldVM?.spec, newVM?.spec);
// check if the disk defined in annotations has changed, ignoring storage size
const oldDisks = parseVolumeClaimTemplates(oldVM);
const newDisks = parseVolumeClaimTemplates(newVM);
const diskChanged = !this._compareDisksIgnoreStorage(oldDisks, newDisks);
return specChanged || diskChanged;
},
restartVM() { restartVM() {
if (this.mode !== 'edit') { if (this.mode !== 'edit' || !this.value.isRunning) return;
return;
}
if (!this.value.isRunning) {
return;
}
const cloneDeepNewVM = clone(this.value);
// new VM const needsRestart = this._shouldPromptRestart(this.cloneVM, this.value);
delete cloneDeepNewVM?.metadata;
delete cloneDeepNewVM?.__clone;
// old VM // if no restart is needed (either no changes or only size changed)
delete this.cloneVM?.metadata; if (!needsRestart) return;
delete this.cloneVM?.__clone;
// add empty hostDevices to old VM as CRD does not have it.
const devicesObj = this.cloneVM?.spec?.template?.spec?.domain?.devices;
if (devicesObj && devicesObj.hostDevices === undefined) {
set(devicesObj, 'hostDevices', []);
}
const oldVM = JSON.parse(JSON.stringify(this.cloneVM));
const newVM = JSON.parse(JSON.stringify(cloneDeepNewVM));
// we won't show restart dialog in yaml page as we don't have a way to detect change in yaml editor.
// only check VM is changed in form page.
if (isEqual(oldVM, newVM)) {
return;
}
return new Promise((resolve) => { return new Promise((resolve) => {
this.isOpen = true; this.isOpen = true;
+9
View File
@@ -837,6 +837,7 @@ harvester:
snapshots: Snapshots snapshots: Snapshots
datasource: Data Source datasource: Data Source
details: Details details: Details
conditions: Conditions
size: Size size: Size
volumeMode: Volume Mode volumeMode: Volume Mode
source: Source source: Source
@@ -1482,6 +1483,14 @@ harvester:
vlanStatus: vlanStatus:
vlanConfig: vlanConfig:
label: Network Configuration label: Network Configuration
l2VlanMode:
access: Access
trunk: Trunk
label: Mode
vlanTrunk:
minId: Minimum VLAN ID
maxId: Maximum VLAN ID
add: Add VLAN Trunk
clusterNetwork: clusterNetwork:
title: Cluster Network Configuration title: Cluster Network Configuration
@@ -2,7 +2,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
import { HCI } from '@shell/config/labels-annotations'; import { HCI } from '@shell/config/labels-annotations';
import { NETWORK_TYPE } from '../config/types'; import { NETWORK_TYPE } from '../config/types';
const { UNTAGGED, OVERLAY } = NETWORK_TYPE; const { UNTAGGED, OVERLAY, L2TRUNK_VLAN } = NETWORK_TYPE;
export default class NetworkAttachmentDef extends SteveModel { export default class NetworkAttachmentDef extends SteveModel {
applyDefaults() { applyDefaults() {
@@ -45,7 +45,7 @@ export default class NetworkAttachmentDef extends SteveModel {
} }
get vlanId() { get vlanId() {
return this.vlanType === UNTAGGED || this.vlanType === OVERLAY ? 'N/A' : this.parseConfig.vlan; return this.vlanType === UNTAGGED || this.vlanType === OVERLAY || this.vlanType === L2TRUNK_VLAN ? 'N/A' : this.parseConfig.vlan;
} }
get customValidationRules() { get customValidationRules() {
@@ -68,7 +68,7 @@ export default class NetworkAttachmentDef extends SteveModel {
const route = annotations[HCI.NETWORK_ROUTE]; const route = annotations[HCI.NETWORK_ROUTE];
let config = {}; let config = {};
if (this.vlanType === UNTAGGED || this.vlanType === OVERLAY) { if (this.vlanType === UNTAGGED || this.vlanType === OVERLAY || this.vlanType === L2TRUNK_VLAN) {
return 'N/A'; return 'N/A';
} }
+1 -1
View File
@@ -7,7 +7,7 @@
"annotations": { "annotations": {
"catalog.cattle.io/display-name": "Harvester", "catalog.cattle.io/display-name": "Harvester",
"catalog.cattle.io/kube-version": ">= 1.16.0-0", "catalog.cattle.io/kube-version": ">= 1.16.0-0",
"catalog.cattle.io/rancher-version": ">= 2.12.0-0", "catalog.cattle.io/rancher-version": ">= 2.13.0-0",
"catalog.cattle.io/ui-extensions-version": ">= 3.0.0 < 4.0.0" "catalog.cattle.io/ui-extensions-version": ">= 3.0.0 < 4.0.0"
} }
}, },
@@ -215,19 +215,18 @@ export default {
if (this.imageSource === IMAGE_METHOD.NEW) { if (this.imageSource === IMAGE_METHOD.NEW) {
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True'; this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
if (this.sourceType === UPLOAD && this.uploadImageId !== '') { if (this.sourceType === UPLOAD && this.uploadImageId !== '') { // upload new image
this.value.spec.image = this.uploadImageId; this.value.spec.image = this.uploadImageId;
} else if (this.sourceType === DOWNLOAD) { } else if (this.sourceType === DOWNLOAD) { // give URL to download new image
this.imageValue.spec.sourceType = DOWNLOAD; this.imageValue.spec.sourceType = DOWNLOAD;
if (!this.imageValue.spec.url) { if (!this.imageValue.spec.url) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl')); this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl'));
buttonCb(false); buttonCb(false);
return; return;
} }
res = await this.imageValue.save(); res = await this.imageValue.save();
this.value.spec.image = res.id; this.value.spec.image = res.id;
} }
} else if (this.imageSource === IMAGE_METHOD.EXIST) { } else if (this.imageSource === IMAGE_METHOD.EXIST) {
@@ -247,7 +246,6 @@ export default {
if (this.skipSingleReplicaDetachedVolFeatureEnabled) { if (this.skipSingleReplicaDetachedVolFeatureEnabled) {
this.value.metadata.annotations = { [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) }; this.value.metadata.annotations = { [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
} }
await this.value.save(); await this.value.save();
this.done(); this.done();
buttonCb(true); buttonCb(true);
@@ -334,6 +332,22 @@ export default {
}, },
watch: { watch: {
async sourceType(neu) {
if (neu === DOWNLOAD && this.imageValue && this.uploadController) {
if (this.uploadController) {
this.uploadController.abort();
}
try {
await this.imageValue.remove();
await this.initImageValue();
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error occurred while removing imageValue:', error);
}
}
},
imageSource(neu) { imageSource(neu) {
if (neu !== IMAGE_METHOD.DELETE) { if (neu !== IMAGE_METHOD.DELETE) {
this.deleteImageId = ''; this.deleteImageId = '';
+7 -7
View File
@@ -3114,9 +3114,9 @@
"@types/node" "*" "@types/node" "*"
"@types/node@*", "@types/node@20.10.8", "@types/node@^14.14.31", "@types/node@~20.19.0": "@types/node@*", "@types/node@20.10.8", "@types/node@^14.14.31", "@types/node@~20.19.0":
version "20.19.19" version "20.19.22"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.19.tgz#18c8982becd5728f92e5f1939f2f3dc85604abcd" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.22.tgz#f17e80ee1d1fdd10d50bef449abe23bfc0216780"
integrity sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg== integrity sha512-hRnu+5qggKDSyWHlnmThnUqg62l29Aj/6vcYgUaSFL9oc7DVjeWEQN3PRgdSc6F8d9QRMWkf36CLMch1Do/+RQ==
dependencies: dependencies:
undici-types "~6.21.0" undici-types "~6.21.0"
@@ -11525,10 +11525,10 @@ semver-compare@^1.0.0:
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
"semver@2 || 3 || 4 || 5", semver@7.7.2, semver@^5.5.0, semver@^5.7.1, semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@~7.0.0: "semver@2 || 3 || 4 || 5", semver@7.7.3, semver@^5.5.0, semver@^5.7.1, semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@~7.0.0:
version "7.7.2" version "7.7.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946"
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
send@0.17.1: send@0.17.1:
version "0.17.1" version "0.17.1"