Compare commits

..
12 Commits
Author SHA1 Message Date
Harvester Bot eb77d03b97 chore: bump version to 1.9.0-rc7 (#1132)
Signed-off-by: Harvester Bot <94133267+harvesterhci-io-github-bot@users.noreply.github.com>
2026-09-01 18:47:34 +08:00
22deb0e1f6 feat: LHv2: graduate from "Experimental" to "Preview" (#1129) (#1131)
In Harvester v1.9.0, we're moving LHv2 from "Experimental" to "Preview",
so I've added a new preview label to the settings interface.  I figure
given Experimental had the "error" color, I should make "Preview" use
the warning color, to make it a bit less alarming :-)

Related-to: https://github.com/harvester/harvester/issues/9960



(cherry picked from commit 4fa6470343)

Signed-off-by: Tim Serong <tserong@suse.com>
Co-authored-by: Tim Serong <tserong@suse.com>
Co-authored-by: Volker Theile <vtheile@suse.com>
2026-09-01 12:13:39 +10:00
mergify[bot]andAndy Lee f096147aa3 fix: vGPU doesn't display when creating harvester downstream cluster (#1123) (#1128)
* fix: vGPU doesn't display in edit VM page



* fix: align PCI passthrough action buttons height

Match the enable/disable passthrough and Actions dropdown buttons to 40px and move the selected-count label below them.



---------


(cherry picked from commit d550046189)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-08-25 16:50:27 +08:00
Harvester Bot d73a9413be chore: bump version to 1.9.0-rc6 (#1122)
Signed-off-by: Harvester Bot <94133267+harvesterhci-io-github-bot@users.noreply.github.com>
2026-08-19 18:40:46 +08:00
mergify[bot]andPo Han Huang 63ff8ce8fe feat: improve readability for vm migration UI (#1112) (#1120)
(cherry picked from commit 5f32a9ade5)

Signed-off-by: pohanhuang <pohan.huang@suse.com>
Co-authored-by: Po Han Huang <pohan.huang@suse.com>
2026-08-18 12:41:02 +08:00
mergify[bot] b3b467f7b5 fix: exclude forklift namespace from system classification (#1114) (#1118) 2026-08-17 13:37:43 +08:00
mergify[bot]andAndy Lee 20557ee788 fix: ACCESS_MODE undefined error in forklift mapping page (#1111) (#1113)
(cherry picked from commit f63be73b11)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-08-14 22:49:01 +08:00
Harvester Bot 8ab432fb52 chore: bump version to 1.9.0-rc5 (#1107)
Signed-off-by: Harvester Bot <94133267+harvesterhci-io-github-bot@users.noreply.github.com>
2026-08-12 14:54:23 +08:00
mergify[bot]andAndy Lee 61624dc0ff fix: prevent duplicated cloud-config for empty user data (#1109) (#1110)
* fix: prevent duplicated cloud-config for empty user data



* refactor: AI review



---------


(cherry picked from commit fabc1cec21)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-08-12 14:54:09 +08:00
mergify[bot] d1367fc9a9 fix: remove map ownerref writeback and delete maps via plan refs (#1105) (#1106) 2026-08-10 20:58:35 +08:00
mergify[bot]andrenovate[bot] 18c973c8c4 deps: update dependency @types/lodash to v4.17.25 (#1099) (#1104)
(cherry picked from commit 72282ae50e)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 10:14:21 +08:00
mergify[bot]andVolker Theile c9b26ab4bb fix: Rename to Windows Unattended & Sysprep (#1096) (#1098)
Rename title and other mentions of Sysprep to prevent misunderstandings regarding functionality. For the average Windows administrator who simply wants to automate the installation of a Windows ISO, it isn't immediately intuitive when the term “Sysprep” is used on its own.

Related to: https://github.com/harvester/harvester/issues/1836
Related to: https://github.com/harvester/harvester-ui-extension/pull/954


(cherry picked from commit 8fba4e7050)

Signed-off-by: Volker Theile <vtheile@suse.com>
Co-authored-by: Volker Theile <vtheile@suse.com>
2026-08-07 11:29:43 +08:00
17 changed files with 184 additions and 137 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "harvester-ui-extension",
"version": "1.9.0-rc4",
"version": "1.9.0-rc7",
"private": false,
"engines": {
"node": ">=24.0.0"
@@ -35,7 +35,7 @@
"glob": "7.2.3",
"glob-parent": "6.0.2",
"json5": "2.2.3",
"@types/lodash": "4.17.24",
"@types/lodash": "4.17.25",
"merge": "2.1.1",
"node-forge": "1.4.0",
"nth-check": "2.1.1",
+17 -12
View File
@@ -231,14 +231,21 @@ export default {
v-if="setting.customized"
class="modified"
>
Modified
{{ t('advancedSettings.tags.modified.label') }}
</span>
<span
v-if="setting.experimental"
v-clean-tooltip="t('advancedSettings.experimental')"
v-clean-tooltip="t('advancedSettings.tags.experimental.description')"
class="experimental"
>
Experimental
{{ t('advancedSettings.tags.experimental.label') }}
</span>
<span
v-if="setting.preview"
v-clean-tooltip="t('advancedSettings.tags.preview.description')"
class="preview"
>
{{ t('advancedSettings.tags.preview.label') }}
</span>
</h1>
<h2 v-clean-html="t(setting.description, getDocLinkParams(setting) || {}, true)">
@@ -352,21 +359,19 @@ export default {
flex: 1;
}
.modified {
.modified,
.experimental,
.preview {
margin-left: 10px;
border: 1px solid var(--primary);
border: 1px solid;
border-radius: 5px;
padding: 2px 10px;
font-size: 12px;
}
.experimental {
margin-left: 10px;
border: 1px solid var(--error);
border-radius: 5px;
padding: 2px 10px;
font-size: 12px;
}
.modified { border-color: var(--primary); }
.experimental { border-color: var(--error); }
.preview { border-color: var(--warning); }
.no-search-match {
text-align: center;
@@ -15,7 +15,7 @@ import MappingColumn from './MappingColumn.vue';
import StorageDefaultsModal from './StorageDefaultsModal.vue';
const DEFAULT_VOLUME_MODE = VOLUME_MODE.FILE_SYSTEM;
const DEFAULT_ACCESS_MODES = [ACCESS_MODE.READ_WRITE_MANY];
const DEFAULT_ACCESS_MODE = ACCESS_MODE?.READ_WRITE_MANY ?? 'ReadWriteMany';
const props = defineProps({
providerName: { type: String, default: '' },
@@ -268,9 +268,9 @@ const buildStorageEntries = () => {
capacity: 0,
target: '',
volumeMode: DEFAULT_VOLUME_MODE,
accessModes: [...DEFAULT_ACCESS_MODES],
accessModes: [DEFAULT_ACCESS_MODE],
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
inheritedAccessModes: [...DEFAULT_ACCESS_MODES],
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
inheritedFromProvider: false,
overridden: false,
usedBy: [],
@@ -310,9 +310,9 @@ const buildStorageEntriesFromProvider = (datastoresData) => {
capacity: ds.capacity || 0,
target: '',
volumeMode: DEFAULT_VOLUME_MODE,
accessModes: [...DEFAULT_ACCESS_MODES],
accessModes: [DEFAULT_ACCESS_MODE],
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
inheritedAccessModes: [...DEFAULT_ACCESS_MODES],
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
inheritedFromProvider: false,
overridden: false,
usedBy: [],
@@ -273,19 +273,6 @@ const startMigrationAction = async() => {
await plan.save();
const planOwnerRef = {
apiVersion: FORKLIFT_API_VERSION,
kind: 'Plan',
name: plan.metadata.name,
uid: plan.metadata.uid,
blockOwnerDeletion: true,
};
networkMap.metadata.ownerReferences = [planOwnerRef];
await networkMap.save();
storageMap.metadata.ownerReferences = [planOwnerRef];
await storageMap.save();
// Kick off the first migration through the model so the Migration payload
// lives in a single place (also reused by the dashboard start/restart action).
await plan.startMigration();
@@ -8,16 +8,22 @@ import { useI18n } from '@shell/composables/useI18n';
import { VOLUME_MODE, ACCESS_MODE } from '../../config/types';
const VOLUME_MODE_OPTIONS = [VOLUME_MODE.FILE_SYSTEM, VOLUME_MODE.BLOCK];
const ACCESS_MODE_OPTIONS = [ACCESS_MODE.READ_WRITE_ONCE, ACCESS_MODE.READ_WRITE_MANY, ACCESS_MODE.READ_ONLY_MANY];
const {
READ_WRITE_ONCE = 'ReadWriteOnce',
READ_ONLY_MANY = 'ReadOnlyMany',
READ_WRITE_MANY = 'ReadWriteMany',
READ_WRITE_ONCE_POD = 'ReadWriteOncePod'
} = ACCESS_MODE || {};
const ACCESS_MODE_OPTIONS = [READ_WRITE_ONCE, READ_WRITE_MANY, READ_ONLY_MANY, READ_WRITE_ONCE_POD];
const props = defineProps({
storageClassName: { type: String, default: '' },
providerName: { type: String, default: '' },
showInherited: { type: Boolean, default: false },
volumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
accessModes: { type: Array, default: () => [ACCESS_MODE.READ_WRITE_MANY] },
accessModes: { type: Array, default: () => ['ReadWriteMany'] },
inheritedVolumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
inheritedAccessModes: { type: Array, default: () => [ACCESS_MODE.READ_WRITE_MANY] },
inheritedAccessModes: { type: Array, default: () => ['ReadWriteMany'] },
});
const emit = defineEmits(['apply', 'close']);
@@ -26,7 +32,7 @@ const store = useStore();
const { t } = useI18n(store);
const localVolumeMode = ref(props.volumeMode || VOLUME_MODE.FILE_SYSTEM);
const localAccessMode = ref(props.accessModes?.[0] || ACCESS_MODE.READ_WRITE_MANY);
const localAccessMode = ref(props.accessModes?.[0] || READ_WRITE_MANY);
const volumeModeOptions = VOLUME_MODE_OPTIONS.map((value) => ({ label: value, value }));
const accessModeOptions = ACCESS_MODE_OPTIONS.map((value) => ({ label: value, value }));
@@ -42,7 +48,7 @@ const apply = () => {
// Repopulate the dropdowns with the provider default values; the user then applies.
const reset = () => {
localVolumeMode.value = props.inheritedVolumeMode || VOLUME_MODE.FILE_SYSTEM;
localAccessMode.value = props.inheritedAccessModes?.[0] || ACCESS_MODE.READ_WRITE_MANY;
localAccessMode.value = props.inheritedAccessModes?.[0] || READ_WRITE_MANY;
};
const cancel = () => {
+3 -1
View File
@@ -111,15 +111,17 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'number', featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
kind: 'boolean',
experimental: true,
preview: true,
featureFlag: 'longhornV2LVMSupport'
},
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_HUGEPAGE_ENABLED]: {
kind: 'boolean',
preview: true,
featureFlag: 'longhornV2HugepageSettings'
},
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_MEMORY_SIZE]: {
kind: 'number',
preview: true,
featureFlag: 'longhornV2HugepageSettings'
},
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
@@ -390,14 +390,17 @@ export default {
:vmi-resource="vmi"
:vmim-resource="vmim"
/>
<DashboardMetrics
<div
v-if="showVmMetrics && liveMigrationProgressEnabled"
:detail-url="VM_MIGRATION_DETAIL_URL"
graph-height="640px"
:has-summary-and-detail="false"
:vars="graphVars"
class="mb-30"
/>
class="migration-metrics-scroll mb-30"
>
<DashboardMetrics
:detail-url="VM_MIGRATION_DETAIL_URL"
graph-height="960px"
:has-summary-and-detail="false"
:vars="graphVars"
/>
</div>
<Events
v-if="liveMigrationProgressEnabled"
:resource="vmi"
@@ -436,3 +439,10 @@ export default {
</Tabbed>
</div>
</template>
<style lang="scss" scoped>
.migration-metrics-scroll {
max-height: 640px;
overflow-y: auto;
}
</style>
@@ -246,4 +246,27 @@ export default {
.group-actions {
display: inline;
}
// Match the enable/disable passthrough bulk-action buttons height to the .btn (40px) on the right.
:deep(.bulk .rc-button.btn-medium.bulk-action:not(.btn-sm)) {
min-height: 40px;
}
// Make the collapsed "Actions" dropdown button 40px too.
:deep(.bulk .rc-button.btn-medium.bulk-actions-dropdown:not(.btn-sm)) {
min-height: 40px;
}
// Lay the bulk row out with flex so the "N selected" label can wrap below the buttons.
:deep(.bulk) {
display: flex;
flex-wrap: wrap;
align-items: center;
}
:deep(.bulk .action-availability) {
flex-basis: 100%;
margin-left: 0;
margin-top: 6px;
}
</style>
@@ -5,6 +5,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import Banner from '@components/Banner/Banner.vue';
import remove from 'lodash/remove';
import { set } from '@shell/utils/object';
import { uniq } from '@shell/utils/array';
import { HCI } from '../../../types';
import DeviceList from './DeviceList';
import CompatibilityMatrix from '../CompatibilityMatrix';
@@ -60,7 +61,15 @@ export default {
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
});
vmDevices.forEach(({ name }) => {
// When off, read from spec; otherwise the spec name is a placeholder ('provisioned'),
// so read the real allocated device names from the deviceAllocationDetails annotation.
const hostDeviceNames = this.vm.isOff ? [
...vmDevices.map(({ name }) => name),
] : [
...Object.values(this.vm?.provisionedHostDevices || {}).reduce((acc, devices) => [...acc, ...devices], []),
];
uniq(hostDeviceNames).forEach((name) => {
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
selectedDevices.push(name);
}
+12 -4
View File
@@ -835,13 +835,13 @@ harvester:
title: "Network Data:"
tip: "The network-data configuration allows you to customize the instance's networking interfaces by assigning subnet configuration, virtual device creation (bonds, bridges, VLANs) routes and DNS configuration. <a href='https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v1.html' target='_blank'>Learn more</a>"
sysprep:
title: Windows Sysprep Configuration
title: Windows Unattended & Sysprep Configuration
description: "Configure Windows automated installation using autounattend.xml. The configuration will be stored in a Kubernetes Secret and mounted as a CD-ROM during installation. <a href='https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup' target='_blank'>Learn more</a>"
createNew: Create new...
createNewTitle: Create Windows Sysprep Template
createNewTitle: Create Windows Unattended & Sysprep Template
xmlContent: autounattend.xml content
secret:
label: Windows Sysprep Template
label: Windows Unattended & Sysprep Template
validation:
invalidXml: Invalid XML syntax
invalidNamespace: Invalid unattend.xml namespace. Expected 'urn:schemas-microsoft-com:unattend'
@@ -2417,7 +2417,15 @@ typeDescription:
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
advancedSettings:
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
tags:
modified:
label: Modified
experimental:
label: Experimental
description: 'Experimental: The feature is incomplete but its essential functionality is operational. The feature is not recommended for use in production environments.'
preview:
label: Technical Preview
description: 'Technical Preview: The feature is almost complete and its functionality is not expected to change significantly. Explore the feature extensively before using in production environments.'
descriptions:
'harv-vlan': Default Network Interface name of the VLAN network.
'harv-backup-target': Custom backup target to store virtual machine backups.
+12
View File
@@ -38,6 +38,10 @@ export const SSH_EXISTING_TYPE = {
export default {
methods: {
hasCloudConfigComment(userScript) {
if (typeof userScript === 'string' && /^\s*#cloud-config(\s|$)/.test(userScript)) {
return true;
}
// Check that userData contains: #cloud-config
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
const items = userDataDoc?.contents?.items || [];
@@ -52,6 +56,14 @@ export default {
exist = true;
}
if (userDataDoc?.contents?.commentBefore === 'cloud-config' || userDataDoc?.contents?.commentBefore?.includes('cloud-config\n')) {
exist = true;
}
if (userDataDoc?.contents?.comment === 'cloud-config' || userDataDoc?.contents?.comment?.includes('cloud-config\n')) {
exist = true;
}
items.map((item) => {
const key = item.key;
+3 -2
View File
@@ -1209,9 +1209,10 @@ export default {
userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
const userDataYaml = userDataDoc.toString();
const userDataValue = userDataDoc.toJSON();
if (userDataYaml === '{}\n') {
// When the YAML parsed value is '{}\n', it means that the userData is empty, then undefined is returned.
if (userDataValue === null || (typeof userDataValue === 'object' && !Array.isArray(userDataValue) && isEmpty(userDataValue))) {
// Empty userData should not create cloud-init content like `null`.
return undefined;
}
@@ -5,6 +5,7 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
import { get } from '@shell/utils/object';
import { NAMESPACE } from '@shell/config/types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
import { FORKLIFT_NAMESPACE } from '@pkg/harvester/config/harvester-map';
import { HCI } from '../../types';
const OBSCURE_NAMESPACE_PREFIX = [
@@ -93,6 +94,10 @@ export default class HciNamespace extends namespace {
}
get isSystem() {
if (this.metadata?.name === FORKLIFT_NAMESPACE) {
return false;
}
const systemNamespaces = ['fleet-default'];
if (systemNamespaces.includes(this.metadata.name)) {
@@ -1315,6 +1315,18 @@ export default class VirtVm extends HarvesterResource {
}
}
get provisionedHostDevices() {
// Rancher-provisioned VMs store hostDevices.name as a placeholder ('provisioned'); the
// real allocated PCI device names live in this annotation, keyed by resourceName.
try {
const deviceAllocationDetails = JSON.parse(this.metadata?.annotations[HCI_ANNOTATIONS.VM_DEVICE_ALLOCATION_DETAILS] || '{}');
return deviceAllocationDetails?.hostdevices || {};
} catch (error) {
return {};
}
}
get schedulingVMBackupFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.9.0-rc4",
"version": "1.9.0-rc7",
"private": false,
"rancher": {
"annotations": {
@@ -68,29 +68,35 @@ export default defineComponent({
return (this.value || []).map((plan) => {
const planName = plan?.metadata?.name || '-';
const namespace = plan?.metadata?.namespace || '';
const networkMapRef = this.getPlanMapRef(plan, 'network');
const storageMapRef = this.getPlanMapRef(plan, 'storage');
const migrations = existing[HCI.FORKLIFT_MIGRATION]
.filter((resource) => this.ownedByPlan(resource, plan))
.map((resource) => ({
name: resource?.metadata?.name,
namespace: resource?.metadata?.namespace || namespace,
}));
.map((resource) => resource?.metadata?.name)
.filter(Boolean);
const matchedNetworkMap = existing[HCI.FORKLIFT_NETWORK_MAP].find((resource) => this.ownedByPlan(resource, plan));
const matchedStorageMap = existing[HCI.FORKLIFT_STORAGE_MAP].find((resource) => this.ownedByPlan(resource, plan));
const matchedNetworkMap = networkMapRef ? existing[HCI.FORKLIFT_NETWORK_MAP].find((resource) => resource?.metadata?.name === networkMapRef.name && resource?.metadata?.namespace === networkMapRef.namespace) : null;
const matchedStorageMap = storageMapRef ? existing[HCI.FORKLIFT_STORAGE_MAP].find((resource) => resource?.metadata?.name === storageMapRef.name && resource?.metadata?.namespace === storageMapRef.namespace) : null;
return {
planName,
namespace,
migrations,
networkMap: matchedNetworkMap ? {
namespace: matchedNetworkMap?.metadata?.namespace || namespace,
namespace: matchedNetworkMap?.metadata?.namespace || networkMapRef?.namespace || namespace,
name: matchedNetworkMap?.metadata?.name,
} : null,
} : (networkMapRef ? {
namespace: networkMapRef.namespace,
name: networkMapRef.name,
} : null),
storageMap: matchedStorageMap ? {
namespace: matchedStorageMap?.metadata?.namespace || namespace,
namespace: matchedStorageMap?.metadata?.namespace || storageMapRef?.namespace || namespace,
name: matchedStorageMap?.metadata?.name,
} : null,
} : (storageMapRef ? {
namespace: storageMapRef.namespace,
name: storageMapRef.name,
} : null),
};
});
},
@@ -113,6 +119,20 @@ export default defineComponent({
methods: {
resourceNames,
getPlanMapRef(plan, mapType) {
const ref = plan?.spec?.map?.[mapType];
const name = ref?.name;
if (!name) {
return null;
}
return {
name,
namespace: ref?.namespace || plan?.metadata?.namespace || '',
};
},
/**
* Determine whether a related resource (migration / network map / storage map)
* is owned by the given plan by inspecting its `metadata.ownerReferences`.
@@ -137,9 +157,8 @@ export default defineComponent({
const existing = this.existingRelatedResources;
const targets = new Map();
const addTarget = (type, resource) => {
const name = resource?.metadata?.name;
const ns = resource?.metadata?.namespace;
const addTarget = (type, name, namespace) => {
const ns = namespace || '';
if (!name) {
return;
@@ -153,17 +172,20 @@ export default defineComponent({
};
for (const plan of this.value || []) {
existing[HCI.FORKLIFT_NETWORK_MAP]
.filter((resource) => this.ownedByPlan(resource, plan))
.forEach((resource) => addTarget(HCI.FORKLIFT_NETWORK_MAP, resource));
const networkMapRef = this.getPlanMapRef(plan, 'network');
const storageMapRef = this.getPlanMapRef(plan, 'storage');
existing[HCI.FORKLIFT_STORAGE_MAP]
.filter((resource) => this.ownedByPlan(resource, plan))
.forEach((resource) => addTarget(HCI.FORKLIFT_STORAGE_MAP, resource));
if (networkMapRef) {
addTarget(HCI.FORKLIFT_NETWORK_MAP, networkMapRef.name, networkMapRef.namespace);
}
if (storageMapRef) {
addTarget(HCI.FORKLIFT_STORAGE_MAP, storageMapRef.name, storageMapRef.namespace);
}
existing[HCI.FORKLIFT_MIGRATION]
.filter((resource) => this.ownedByPlan(resource, plan))
.forEach((resource) => addTarget(HCI.FORKLIFT_MIGRATION, resource));
.forEach((resource) => addTarget(HCI.FORKLIFT_MIGRATION, resource?.metadata?.name, resource?.metadata?.namespace));
}
return [...targets.values()];
@@ -256,11 +278,11 @@ export default defineComponent({
<div>• {{ t('harvester.addons.vmMigration.labels.migration') }}:</div>
<template v-if="summary.migrations.length">
<div
v-for="migration in summary.migrations"
:key="`${ migration.namespace }/${ migration.name }`"
v-for="migrationName in summary.migrations"
:key="migrationName"
>
<div class="ml-20">
- {{ migration.name }}
- {{ migrationName }}
</div>
</div>
</template>
+6 -61
View File
@@ -2825,10 +2825,10 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash@4.17.24":
version "4.17.24"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
"@types/lodash@4.17.25":
version "4.17.25"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.25.tgz#69765ac7bcddb0eb072961cf292524a8f5b3c2c0"
integrity sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==
"@types/mime@^1":
version "1.3.5"
@@ -4367,24 +4367,6 @@ bn.js@^5.2.1, bn.js@^5.2.2:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e"
integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==
body-parser@~1.20.3:
version "1.20.4"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==
dependencies:
bytes "~3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "~1.2.0"
http-errors "~2.0.1"
iconv-lite "~0.4.24"
on-finished "~2.4.1"
qs "~6.14.0"
raw-body "~2.5.3"
type-is "~1.6.18"
unpipe "~1.0.0"
body-parser@~1.20.5:
version "1.20.6"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76"
@@ -6685,7 +6667,7 @@ expect@^27.5.1:
jest-matcher-utils "^27.5.1"
jest-message-util "^27.5.1"
express@4.22.2:
express@4.22.2, express@^4.17.3:
version "4.22.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700"
integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==
@@ -6722,43 +6704,6 @@ express@4.22.2:
utils-merge "1.0.1"
vary "~1.1.2"
express@^4.17.3:
version "4.22.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069"
integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "~1.20.3"
content-disposition "~0.5.4"
content-type "~1.0.4"
cookie "~0.7.1"
cookie-signature "~1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "~1.3.1"
fresh "~0.5.2"
http-errors "~2.0.0"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "~2.4.1"
parseurl "~1.3.3"
path-to-regexp "~0.1.12"
proxy-addr "~2.0.7"
qs "~6.14.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "~0.19.0"
serve-static "~1.16.2"
setprototypeof "1.2.0"
statuses "~2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -10384,7 +10329,7 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qs@6.15.3, qs@^6.12.3, qs@~6.14.0, qs@~6.15.1:
qs@6.15.3, qs@^6.12.3, qs@~6.15.1:
version "6.15.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==