mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-05 05:36:05 +08:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d996c42dc | ||
|
|
88cd302ce4 | ||
|
|
e6dd8d6771 | ||
|
|
1a3822881e | ||
|
|
ee8bb21a10 | ||
|
|
e3d30a0eec | ||
|
|
532b6c4d50 | ||
|
|
f22644e058 | ||
|
|
cd128d0444 | ||
|
|
0278f51260 | ||
|
|
98efd63110 | ||
|
|
78e78ac8dd |
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ const FEATURE_FLAGS = {
|
|||||||
],
|
],
|
||||||
'v1.6.1': [],
|
'v1.6.1': [],
|
||||||
'v1.7.0': [
|
'v1.7.0': [
|
||||||
'vmMachineTypeAuto'
|
'vmMachineTypeAuto',
|
||||||
|
'lhV2VolExpansion',
|
||||||
|
'l2VlanTrunkMode'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user