Latest changes from harvester/master

Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
Francesco Torchia 2024-09-19 10:37:36 +02:00
parent deeccf3db6
commit ec3d88aeb7
No known key found for this signature in database
GPG Key ID: E6D011B7415D4393
56 changed files with 1498 additions and 2197 deletions

View File

@ -3,7 +3,7 @@ import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
import Tag from '@shell/components/Tag'; import Tag from '@shell/components/Tag';
export default { export default {
name: 'DiskTags', name: 'Tags',
components: { Tag }, components: { Tag },

View File

@ -35,13 +35,23 @@ export default {
version: '', version: '',
enableLogging: true, enableLogging: true,
readyReleaseNote: false, readyReleaseNote: false,
isOpen: false, isOpen: false
}; };
}, },
computed: { computed: {
...mapGetters(['currentCluster']), ...mapGetters(['currentCluster']),
latestUpgrade() {
return this.upgrade?.find(u => u.isLatestUpgrade);
},
isUpgradeInProgress() {
return this.latestUpgrade &&
!this.latestUpgrade.isUpgradeSucceeded &&
!this.latestUpgrade.isUpgradeFailed;
},
versionOptions() { versionOptions() {
const versions = this.$store.getters['harvester/all'](HCI.VERSION); const versions = this.$store.getters['harvester/all'](HCI.VERSION);
@ -133,7 +143,7 @@ export default {
/> />
</h1> </h1>
<button <button
v-if="versionOptions.length" v-if="versionOptions.length && !isUpgradeInProgress"
type="button" type="button"
class="btn bg-warning btn-sm" class="btn bg-warning btn-sm"
@click="open" @click="open"

View File

@ -34,15 +34,7 @@ export default {
}, },
data() { data() {
const categorySettings = this.settings.filter((s) => { const categorySettings = this.filterCategorySettings();
if (this.category !== 'advanced') {
return (CATEGORY[this.category] || []).find(item => item === s.id);
} else if (this.category === 'advanced') {
const allCategory = Object.keys(CATEGORY);
return !allCategory.some(category => (CATEGORY[category] || []).find(item => item === s.id));
}
}) || [];
return { return {
HCI_SETTING, HCI_SETTING,
@ -52,7 +44,27 @@ export default {
computed: { ...mapGetters({ t: 'i18n/t' }) }, computed: { ...mapGetters({ t: 'i18n/t' }) },
watch: {
settings: {
deep: true,
handler() {
this['categorySettings'] = this.filterCategorySettings();
}
}
},
methods: { methods: {
filterCategorySettings() {
return this.settings.filter((s) => {
if (this.category !== 'advanced') {
return (CATEGORY[this.category] || []).find(item => item === s.id);
} else if (this.category === 'advanced') {
const allCategory = Object.keys(CATEGORY);
return !allCategory.some(category => (CATEGORY[category] || []).find(item => item === s.id));
}
}) || [];
},
showActionMenu(e, setting) { showActionMenu(e, setting) {
const actionElement = e.srcElement; const actionElement = e.srcElement;
@ -104,7 +116,7 @@ export default {
<template> <template>
<div> <div>
<div v-for="(setting, i) in categorySettings" class="advanced-setting mb-20" :key="i" > <div v-for="(setting, i) in categorySettings" :key="i">
<div class="header"> <div class="header">
<div class="title"> <div class="title">
<h1> <h1>
@ -112,6 +124,9 @@ export default {
<span v-if="setting.customized" class="modified"> <span v-if="setting.customized" class="modified">
Modified Modified
</span> </span>
<span v-if="setting.technicalPreview" v-clean-tooltip="t('advancedSettings.technicalPreview')" class="technical-preview">
Technical Preview
</span>
</h1> </h1>
<h2 v-clean-html="t(setting.description, {}, true)"> <h2 v-clean-html="t(setting.description, {}, true)">
</h2> </h2>
@ -200,4 +215,12 @@ export default {
padding: 2px 10px; padding: 2px 10px;
font-size: 12px; font-size: 12px;
} }
.technical-preview {
margin-left: 10px;
border: 1px solid var(--warning);
border-radius: 5px;
padding: 2px 10px;
font-size: 12px;
}
</style> </style>

View File

@ -1,5 +1,6 @@
<script> <script>
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { DOC_LINKS } from '../config/doc-links';
export default { export default {
name: 'HarvesterUpgradeInfo', name: 'HarvesterUpgradeInfo',
@ -16,6 +17,10 @@ export default {
computed: { computed: {
releaseVersion() { releaseVersion() {
return !!this.version ? `https://github.com/harvester/harvester/releases/tag/${ this.version }` : `https://github.com/harvester/harvester/releases`; return !!this.version ? `https://github.com/harvester/harvester/releases/tag/${ this.version }` : `https://github.com/harvester/harvester/releases`;
},
upgradeLink() {
return DOC_LINKS.UPGRADE_URL;
} }
}, },
}; };
@ -26,15 +31,14 @@ export default {
<Banner color="warning"> <Banner color="warning">
<div> <div>
<strong>{{ t('harvester.upgradePage.upgradeInfo.warning') }}:</strong> <strong>{{ t('harvester.upgradePage.upgradeInfo.warning') }}:</strong>
<p v-clean-html="t('harvester.upgradePage.upgradeInfo.doc', {}, true)" class="mb-5"> <p v-clean-html="t('harvester.upgradePage.upgradeInfo.doc', {url: upgradeLink}, true)" class="mb-5"></p>
</p>
<p class="mb-5"> <p class="mb-5">
{{ t('harvester.upgradePage.upgradeInfo.tip') }} {{ t('harvester.upgradePage.upgradeInfo.tip') }}
</p> </p>
<p class="mb-5"> <p class="mb-5">
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a :href="releaseVersion" target="_blank">{{ t('generic.moreInfo') }} </a> {{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a :href="releaseVersion" target="_blank">{{ t('generic.moreInfo') }}</a>
</p> </p>
</div> </div>
</Banner> </Banner>

View File

@ -61,7 +61,7 @@ export default {
window.open( window.open(
url, url,
'_blank', '_blank',
'toolbars=0,width=900,height=700,left=0,top=0,noreferrer' `toolbars=0,width=${ screen.width - 200 },height=${ screen.height - 200 },left=0,top=0,noreferrer`
); );
}, },

View File

@ -0,0 +1,39 @@
<script>
import CreateEditView from '@shell/mixins/create-edit-view';
import { LabeledInput } from '@components/Form/LabeledInput';
export default {
name: 'AdditionalGuestMemoryOverheadRatio',
components: { LabeledInput },
mixins: [CreateEditView],
data() {
return { ratio: this.value.value || this.value.default };
},
methods: {
update() {
this.value['value'] = this.ratio;
},
useDefault() {
this['ratio'] = this.value.default;
this.update();
},
},
};
</script>
<template>
<div class="row">
<div class="col span-12">
<LabeledInput
v-model:value="ratio"
:label="t('harvester.setting.ratio')"
@update:value="update"
/>
</div>
</div>
</template>

View File

@ -10,6 +10,7 @@ import Tip from '@shell/components/Tip';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import { NODE } from '@shell/config/types'; import { NODE } from '@shell/config/types';
import { HCI } from '../../types'; import { HCI } from '../../types';
import { DOC_LINKS } from '../../config/doc-links';
export default { export default {
name: 'HarvesterEditStorageNetwork', name: 'HarvesterEditStorageNetwork',
@ -85,6 +86,9 @@ export default {
}, },
computed: { computed: {
storageNetworkExampleLink() {
return DOC_LINKS.STORAGE_NETWORK_EXAMPLE;
},
clusterNetworkOptions() { clusterNetworkOptions() {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || []; const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
@ -206,8 +210,8 @@ export default {
:placeholder="t('harvester.setting.storageNetwork.range.placeholder')" :placeholder="t('harvester.setting.storageNetwork.range.placeholder')"
label-key="harvester.setting.storageNetwork.range.label" label-key="harvester.setting.storageNetwork.range.label"
/> />
<Tip class="mb-20" icon="icon icon-info" :text="t('harvester.setting.storageNetwork.tip')"> <Tip class="mb-20" icon="icon icon-info">
<t k="harvester.setting.storageNetwork.tip" :raw="true" /> <t k="harvester.setting.storageNetwork.tip" :raw="true" :url="storageNetworkExampleLink" />
</Tip> </Tip>
<ArrayList <ArrayList

View File

@ -0,0 +1,12 @@
const pkgJson = require('../package.json');
import semver from 'semver';
const docVersion = `v${ semver.major(pkgJson.version) }.${ semver.minor(pkgJson.version) }`;
export const DOC_LINKS = {
CONSOLE_URL: `https://docs.harvesterhci.io/${ docVersion }/host/`,
RANCHER_INTEGRATION_URL: `https://docs.harvesterhci.io/${ docVersion }/rancher/rancher-integration`,
STORAGE_NETWORK_EXAMPLE: `https://docs.harvesterhci.io/${ docVersion }/advanced/storagenetwork#configuration-example`,
KSMTUNED_MODE: `https://docs.harvesterhci.io/${ docVersion }/host/#ksmtuned-mode`,
UPGRADE_URL: `https://docs.harvesterhci.io/${ docVersion }/upgrade/index`
};

View File

@ -23,7 +23,7 @@ export const InterfaceOption = [{
export const SOURCE_TYPE = { export const SOURCE_TYPE = {
NEW: 'New', NEW: 'New',
IMAGE: 'VM Image', IMAGE: 'Virtual Machine Image',
ATTACH_VOLUME: 'Existing Volume', ATTACH_VOLUME: 'Existing Volume',
CONTAINER: 'Container' CONTAINER: 'Container'
}; };
@ -41,7 +41,14 @@ export const ACCESS_CREDENTIALS = {
INJECT_SSH: 'sshPublicKey' INJECT_SSH: 'sshPublicKey'
}; };
export const RunStrategys = ['Always', 'RerunOnFailure', 'Manual', 'Halted']; export const runStrategies = ['Always', 'RerunOnFailure', 'Manual', 'Halted'];
export const maintenanceStrategies = [
'Migrate',
'ShutdownAndRestartAfterEnable',
'ShutdownAndRestartAfterDisable',
'Shutdown'
];
export const VOLUME_DATA_SOURCE_KIND = { export const VOLUME_DATA_SOURCE_KIND = {
VolumeSnapshot: 'VolumeSnapshot', VolumeSnapshot: 'VolumeSnapshot',
@ -55,10 +62,10 @@ export const FLOW_TYPE = {
}; };
export const ADD_ONS = { export const ADD_ONS = {
HARVESTER_SEEDER: 'harvester-seeder', HARVESTER_SEEDER: 'harvester-seeder',
PCI_DEVICE_CONTROLLER: 'pcidevices-controller', PCI_DEVICE_CONTROLLER: 'pcidevices-controller',
RANCHER_LOGGING: 'rancher-logging', NVIDIA_DRIVER_TOOLKIT_CONTROLLER: 'nvidia-driver-toolkit',
RANCHER_MONITORING: 'rancher-monitoring', RANCHER_LOGGING: 'rancher-logging',
VM_IMPORT_CONTROLLER: 'vm-import-controller', RANCHER_MONITORING: 'rancher-monitoring',
NVIDIA_DRIVER_TOOLKIT_CONTROLLER: 'nvidia-driver-toolkit' VM_IMPORT_CONTROLLER: 'vm-import-controller',
}; };

View File

@ -1,49 +1,55 @@
export const HCI = { export const HCI = {
CLOUD_INIT: 'harvesterhci.io/cloud-init-template', CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
CURRENT_IP: 'rke2.io/internal-ip', CURRENT_IP: 'rke2.io/internal-ip',
OWNED_BY: 'harvesterhci.io/owned-by', IMAGE_ID: 'harvesterhci.io/imageId',
IMAGE_ID: 'harvesterhci.io/imageId', SSH_NAMES: 'harvesterhci.io/sshNames',
SSH_NAMES: 'harvesterhci.io/sshNames', NETWORK_IPS: 'network.harvesterhci.io/ips',
NETWORK_IPS: 'network.harvesterhci.io/ips', TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName', CREATOR: 'harvesterhci.io/creator',
CREATOR: 'harvesterhci.io/creator', OS: 'harvesterhci.io/os',
OS: 'harvesterhci.io/os', NETWORK_TYPE: 'network.harvesterhci.io/type',
NETWORK_TYPE: 'network.harvesterhci.io/type', VM_NAME: 'harvesterhci.io/vmName',
VM_NAME: 'harvesterhci.io/vmName', VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix', VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory', MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status', HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name', HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url', RESTORE_NAME: 'restore.harvesterhci.io/name',
RESTORE_NAME: 'restore.harvesterhci.io/name', NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master', NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane', NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
PROMOTE_STATUS: 'harvesterhci.io/promote-status', PROMOTE_STATUS: 'harvesterhci.io/promote-status',
MIGRATION_STATE: 'harvesterhci.io/migrationState', MIGRATION_STATE: 'harvesterhci.io/migrationState',
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates', VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
IMAGE_NAME: 'harvesterhci.io/image-name', IMAGE_NAME: 'harvesterhci.io/image-name',
INIT_IP: 'etcd.rke2.cattle.io/node-address', INIT_IP: 'etcd.rke2.cattle.io/node-address',
NODE_SCHEDULABLE: 'kubevirt.io/schedulable', NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
NETWORK_ROUTE: 'network.harvesterhci.io/route', NETWORK_ROUTE: 'network.harvesterhci.io/route',
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image', MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade', OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
UPGRADE_STATE: 'harvesterhci.io/upgradeState', LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
REAY_MESSAGE: 'harvesterhci.io/read-message', UPGRADE_STATE: 'harvesterhci.io/upgradeState',
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names', REAY_MESSAGE: 'harvesterhci.io/read-message',
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users', DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
IMAGE_SUFFIX: 'harvesterhci.io/image-type', DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
OS_TYPE: 'harvesterhci.io/os-type', IMAGE_SUFFIX: 'harvesterhci.io/image-type',
HOST_REQUEST: 'management.cattle.io/pod-requests', OS_TYPE: 'harvesterhci.io/os-type',
STORAGE_CLASS: 'harvesterhci.io/storageClassName', STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io', HOST_REQUEST: 'management.cattle.io/pod-requests',
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental', STORAGE_CLASS: 'harvesterhci.io/storageClassName',
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error', STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm', ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx', VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
NODE_MANUFACTURER: 'manufacturer', KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
NODE_MODEL: 'model', KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
NODE_SERIAL_NUMBER: 'serialNumber', NODE_MANUFACTURER: 'manufacturer',
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota', NODE_MODEL: 'model',
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service', NODE_SERIAL_NUMBER: 'serialNumber',
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device', VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
CPU_MANAGER: 'cpumanager'
}; };

View File

@ -1,33 +1,39 @@
export const HCI_SETTING = { export const HCI_SETTING = {
BACKUP_TARGET: 'backup-target', BACKUP_TARGET: 'backup-target',
CONTAINERD_REGISTRY: 'containerd-registry', CONTAINERD_REGISTRY: 'containerd-registry',
LOG_LEVEL: 'log-level', LOG_LEVEL: 'log-level',
SERVER_VERSION: 'server-version', SERVER_VERSION: 'server-version',
UI_INDEX: 'ui-index', UI_INDEX: 'ui-index',
UI_PLUGIN_INDEX: 'ui-plugin-index', UI_PLUGIN_INDEX: 'ui-plugin-index',
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled', UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
UPGRADE_CHECKER_URL: 'upgrade-checker-url', UPGRADE_CHECKER_URL: 'upgrade-checker-url',
VLAN: 'vlan', VLAN: 'vlan',
UI_SOURCE: 'ui-source', UI_SOURCE: 'ui-source',
UI_PL: 'ui-pl', UI_PL: 'ui-pl',
HTTP_PROXY: 'http-proxy', HTTP_PROXY: 'http-proxy',
ADDITIONAL_CA: 'additional-ca', ADDITIONAL_CA: 'additional-ca',
OVERCOMMIT_CONFIG: 'overcommit-config', OVERCOMMIT_CONFIG: 'overcommit-config',
CLUSTER_REGISTRATION_URL: 'cluster-registration-url', CLUSTER_REGISTRATION_URL: 'cluster-registration-url',
DEFAULT_STORAGE_CLASS: 'default-storage-class', DEFAULT_STORAGE_CLASS: 'default-storage-class',
SUPPORT_BUNDLE_TIMEOUT: 'support-bundle-timeout', SUPPORT_BUNDLE_TIMEOUT: 'support-bundle-timeout',
SUPPORT_BUNDLE_IMAGE: 'support-bundle-image', SUPPORT_BUNDLE_EXPIRATION: 'support-bundle-expiration',
STORAGE_NETWORK: 'storage-network', SUPPORT_BUNDLE_IMAGE: 'support-bundle-image',
VM_FORCE_RESET_POLICY: 'vm-force-reset-policy', SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT: 'support-bundle-node-collection-timeout',
SSL_CERTIFICATES: 'ssl-certificates', STORAGE_NETWORK: 'storage-network',
SSL_PARAMETERS: 'ssl-parameters', VM_FORCE_RESET_POLICY: 'vm-force-reset-policy',
SUPPORT_BUNDLE_NAMESPACES: 'support-bundle-namespaces', SSL_CERTIFICATES: 'ssl-certificates',
AUTO_DISK_PROVISION_PATHS: 'auto-disk-provision-paths', SSL_PARAMETERS: 'ssl-parameters',
RELEASE_DOWNLOAD_URL: 'release-download-url', SUPPORT_BUNDLE_NAMESPACES: 'support-bundle-namespaces',
CCM_CSI_VERSION: 'harvester-csi-ccm-versions', AUTO_DISK_PROVISION_PATHS: 'auto-disk-provision-paths',
CSI_DRIVER_CONFIG: 'csi-driver-config', RELEASE_DOWNLOAD_URL: 'release-download-url',
VM_TERMINATION_PERIOD: 'default-vm-termination-grace-period-seconds', CCM_CSI_VERSION: 'harvester-csi-ccm-versions',
NTP_SERVERS: 'ntp-servers', CSI_DRIVER_CONFIG: 'csi-driver-config',
VM_TERMINATION_PERIOD: 'default-vm-termination-grace-period-seconds',
NTP_SERVERS: 'ntp-servers',
AUTO_ROTATE_RKE2_CERTS: 'auto-rotate-rke2-certs',
KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
LONGHORN_V2_DATA_ENGINE_ENABLED: 'longhorn-v2-data-engine-enabled',
ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO: 'additional-guest-memory-overhead-ratio'
}; };
export const HCI_ALLOWED_SETTINGS = { export const HCI_ALLOWED_SETTINGS = {
@ -41,6 +47,7 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.VLAN]: { [HCI_SETTING.VLAN]: {
kind: 'custom', from: 'import', alias: 'vlan' kind: 'custom', from: 'import', alias: 'vlan'
}, },
[HCI_SETTING.AUTO_ROTATE_RKE2_CERTS]: { kind: 'json', from: 'import' },
[HCI_SETTING.CSI_DRIVER_CONFIG]: { kind: 'json', from: 'import' }, [HCI_SETTING.CSI_DRIVER_CONFIG]: { kind: 'json', from: 'import' },
[HCI_SETTING.SERVER_VERSION]: { readOnly: true }, [HCI_SETTING.SERVER_VERSION]: { readOnly: true },
[HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' }, [HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' },
@ -49,14 +56,16 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.ADDITIONAL_CA]: { [HCI_SETTING.ADDITIONAL_CA]: {
kind: 'multiline', canReset: true, from: 'import' kind: 'multiline', canReset: true, from: 'import'
}, },
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' }, [HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: {}, [HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: {},
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' }, [HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' }, [HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: {},
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' }, [HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
[HCI_SETTING.RANCHER_MANAGER_SUPPORT]: { kind: 'boolean' }, [HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' }, [HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_PARAMETERS]: { [HCI_SETTING.RANCHER_MANAGER_SUPPORT]: { kind: 'boolean' },
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_PARAMETERS]: {
kind: 'json', from: 'import', canReset: true kind: 'json', from: 'import', canReset: true
}, },
[HCI_SETTING.SUPPORT_BUNDLE_NAMESPACES]: { from: 'import', canReset: true }, [HCI_SETTING.SUPPORT_BUNDLE_NAMESPACES]: { from: 'import', canReset: true },
@ -75,6 +84,9 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.NTP_SERVERS]: { [HCI_SETTING.NTP_SERVERS]: {
kind: 'json', from: 'import', canReset: true kind: 'json', from: 'import', canReset: true
}, },
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: {},
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: { kind: 'boolean', technicalPreview: true },
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
}; };
export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = { export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = {

View File

@ -11,6 +11,14 @@ export const IMAGE_DOWNLOAD_SIZE = {
width: 120 width: 120
}; };
export const IMAGE_VIRTUAL_SIZE = {
name: 'virtualSize',
labelKey: 'harvester.tableHeaders.virtualSize',
value: 'virtualSize',
sort: 'status.virtualSize',
width: 120
};
export const IMAGE_PROGRESS = { export const IMAGE_PROGRESS = {
name: 'Uploaded', name: 'Uploaded',
labelKey: 'tableHeaders.progress', labelKey: 'tableHeaders.progress',

View File

@ -48,6 +48,13 @@ export default {
customName() { customName() {
return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME]; return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME];
}, },
cpuManagerStatus() {
if (this.value.isCPUManagerEnableInProgress) {
return this.t('generic.loading');
}
return this.t(`generic.${ this.value.isCPUManagerEnabled ? 'enabled' : 'disabled' }`);
},
consoleUrl() { consoleUrl() {
const consoleUrl = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL]; const consoleUrl = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
@ -224,6 +231,9 @@ export default {
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<div v-if="!value.isEtcd" class="col span-6">
<LabelValue :name="t('harvester.host.detail.cpuManager')" :value="cpuManagerStatus" />
</div>
<div class="col span-6"> <div class="col span-6">
<LabelValue :name="t('harvester.host.detail.consoleUrl')" :value="consoleUrl.value"> <LabelValue :name="t('harvester.host.detail.consoleUrl')" :value="consoleUrl.value">
<a slot="value" :href="consoleUrl.value" target="_blank">{{ consoleUrl.display }}</a> <a slot="value" :href="consoleUrl.value" target="_blank">{{ consoleUrl.display }}</a>
@ -268,7 +278,7 @@ export default {
<HarvesterStorageUsed <HarvesterStorageUsed
:row="value" :row="value"
:resource-name="t('harvester.host.detail.storage')" :resource-name="t('harvester.host.detail.storage')"
:show-reserved="true" :show-allocated="true"
/> />
</div> </div>
</div> </div>

View File

@ -4,13 +4,15 @@ 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 HarvesterDisk from '../../mixins/harvester-disk'; import HarvesterDisk from '../../mixins/harvester-disk';
import { RadioGroup } from '@components/Form/Radio';
export default { export default {
components: { components: {
LabelValue, LabelValue,
BadgeState, BadgeState,
Banner, Banner,
Tag Tag,
RadioGroup
}, },
mixins: [ mixins: [
@ -37,6 +39,18 @@ export default {
return {}; return {};
}, },
computed: { computed: {
targetDisk() {
return this.disks.find(disk => disk.name === this.value.name);
},
schedulableTooltipMessage() {
const { name, path } = this.value;
if (this.targetDisk && !this.targetDisk.allowScheduling && name && path) {
return this.t('harvester.host.disk.allowScheduling.tooltip', { name, path });
} else {
return this.schedulableCondition.message;
}
},
allowSchedulingOptions() { allowSchedulingOptions() {
return [{ return [{
label: this.t('generic.enabled'), label: this.t('generic.enabled'),
@ -117,6 +131,16 @@ export default {
</div> </div>
<div class="row mt-10"> <div class="row mt-10">
<div class="col span-12"> <div class="col span-12">
<div class="pull-left">
<RadioGroup
v-model:value="value.allowScheduling"
name="diskScheduling"
:label="t('harvester.host.disk.allowScheduling.label')"
:mode="mode"
:options="allowSchedulingOptions"
:row="true"
/>
</div>
<div class="pull-right"> <div class="pull-right">
{{ t('harvester.host.disk.conditions') }}: {{ t('harvester.host.disk.conditions') }}:
<BadgeState <BadgeState
@ -127,9 +151,9 @@ export default {
class="mr-10 ml-10 state" class="mr-10 ml-10 state"
/> />
<BadgeState <BadgeState
v-clean-tooltip="schedulableCondition.message" v-clean-tooltip="schedulableTooltipMessage"
:color="schedulableCondition.status === 'True' ? 'bg-success' : 'bg-error' " :color="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'bg-success' : 'bg-error' "
:icon="schedulableCondition.status === 'True' ? 'icon-checkmark' : 'icon-warning' " :icon="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'icon-checkmark' : 'icon-warning' "
label="Schedulable" label="Schedulable"
class="mr-10 state" class="mr-10 state"
/> />

View File

@ -104,7 +104,7 @@ export default {
key-field="_key" key-field="_key"
> >
<template cell:state="scope"> <template cell:state="scope" class="state-col">
<div class="state"> <div class="state">
<HarvesterVmState class="vmstate" :row="scope.row" :all-cluster-network="allClusterNetwork" /> <HarvesterVmState class="vmstate" :row="scope.row" :all-cluster-network="allClusterNetwork" />
</div> </div>

View File

@ -311,14 +311,12 @@ export default {
}; };
} }
const current = this.ntpSync?.currentNtpServers || '';
if (status === 'unsynced') { if (status === 'unsynced') {
return { return {
status: 'unsynced', status: 'unsynced',
warning: { warning: {
key: 'harvester.host.ntp.ntpSyncStatus.isUnsynced', key: 'harvester.host.ntp.ntpSyncStatus.isUnsynced',
current current: this.ntpSync?.currentNtpServers ? `<code>${ this.ntpSync.currentNtpServers }</code>` : '',
} }
}; };
} }

View File

@ -35,6 +35,10 @@ export default {
return this.value?.downSize; return this.value?.downSize;
}, },
virtualSize() {
return this.value?.virtualSize;
},
url() { url() {
return this.value?.spec?.url || '-'; return this.value?.spec?.url || '-';
}, },
@ -100,6 +104,12 @@ export default {
</div> </div>
</div> </div>
<div class="row">
<div class="col span-12">
<LabelValue :name="t('harvester.image.virtualSize')" :value="virtualSize" class="mb-20" />
</div>
</div>
<div class="row"> <div class="row">
<div class="col span-12"> <div class="col span-12">
<LabelValue :name="t('nameNsDescription.description.label')" :value="description" class="mb-20" /> <LabelValue :name="t('nameNsDescription.description.label')" :value="description" class="mb-20" />

View File

@ -11,6 +11,7 @@ import NodeScheduling from '@shell/components/form/NodeScheduling';
import PodAffinity from '@shell/components/form/PodAffinity'; import PodAffinity from '@shell/components/form/PodAffinity';
import KeyValue from '@shell/components/form/KeyValue'; import KeyValue from '@shell/components/form/KeyValue';
import Labels from '@shell/components/form/Labels'; import Labels from '@shell/components/form/Labels';
import LabelValue from '@shell/components/LabelValue';
import { HCI } from '../../types'; import { HCI } from '../../types';
import VM_MIXIN from '../../mixins/harvester-vm'; import VM_MIXIN from '../../mixins/harvester-vm';
@ -22,6 +23,7 @@ import Events from './VirtualMachineTabs/VirtualMachineEvents';
import Migration from './VirtualMachineTabs/VirtualMachineMigration'; import Migration from './VirtualMachineTabs/VirtualMachineMigration';
import OverviewBasics from './VirtualMachineTabs/VirtualMachineBasics'; import OverviewBasics from './VirtualMachineTabs/VirtualMachineBasics';
import OverviewKeypairs from './VirtualMachineTabs/VirtualMachineKeypairs'; import OverviewKeypairs from './VirtualMachineTabs/VirtualMachineKeypairs';
import { formatSi } from '@shell/utils/units';
const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1'; const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1';
@ -33,6 +35,7 @@ export default {
Tabbed, Tabbed,
Events, Events,
OverviewBasics, OverviewBasics,
LabelValue,
Volume, Volume,
Network, Network,
OverviewKeypairs, OverviewKeypairs,
@ -57,15 +60,18 @@ export default {
data() { data() {
return { return {
switchToCloud: false, hasResourceQuotaSchema: false,
switchToCloud: false,
VM_METRICS_DETAIL_URL, VM_METRICS_DETAIL_URL,
showVmMetrics: false, showVmMetrics: false,
}; };
}, },
async created() { async created() {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
this.hasResourceQuotaSchema = !!this.$store.getters[`${ inStore }/schemaFor`](HCI.RESOURCE_QUOTA);
const hash = { const hash = {
pods: this.$store.dispatch(`${ inStore }/findAll`, { type: POD }), pods: this.$store.dispatch(`${ inStore }/findAll`, { type: POD }),
services: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE }), services: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE }),
@ -75,6 +81,10 @@ export default {
restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }), restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }),
}; };
if (this.hasResourceQuotaSchema) {
hash.resourceQuotas = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESOURCE_QUOTA });
}
await allHash(hash); await allHash(hash);
setPromiseResult( setPromiseResult(
@ -88,6 +98,22 @@ export default {
computed: { computed: {
...mapGetters(['currentCluster']), ...mapGetters(['currentCluster']),
totalSnapshotSize() {
if (this.value.snapshotSizeQuota === undefined || this.value.snapshotSizeQuota === null) {
return ' - ';
}
if (this.value.snapshotSizeQuota === 0) {
return '0';
}
return formatSi(this.value.snapshotSizeQuota, {
increment: 1024,
addSuffix: true,
suffix: 'i',
});
},
vmi() { vmi() {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
@ -175,10 +201,17 @@ export default {
<Network v-model:value="networkRows" mode="view" /> <Network v-model:value="networkRows" mode="view" />
</Tab> </Tab>
<Tab name="keypairs" :label="t('harvester.virtualMachine.detail.tabs.keypairs')" class="bordered-table" :weight="3"> <Tab name="keypairs" :label="t('harvester.virtualMachine.detail.tabs.keypairs')" class="bordered-table" :weight="4">
<OverviewKeypairs :value="value" /> <OverviewKeypairs :value="value" />
</Tab> </Tab>
<Tab v-if="hasResourceQuotaSchema" name="quotas" :label="t('harvester.tab.quotas')" :weight="3">
<LabelValue
:name="t('harvester.snapshot.totalSnapshotSize')"
:value="totalSnapshotSize"
/>
</Tab>
<Tab <Tab
v-if="showVmMetrics" v-if="showVmMetrics"
name="vm-metrics" name="vm-metrics"

View File

@ -8,6 +8,7 @@ import { STATE, NAME, AGE } from '@shell/config/table-headers';
import { matching } from '@shell/utils/selector'; import { matching } from '@shell/utils/selector';
import { NODE } from '@shell/config/types'; import { NODE } from '@shell/config/types';
import { isEmpty } from '@shell/utils/object'; import { isEmpty } from '@shell/utils/object';
import { HCI } from '@pkg/harvester/config/labels-annotations';
export default { export default {
components: { components: {
@ -55,10 +56,13 @@ export default {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const nodes = this.$store.getters[`${ inStore }/all`](NODE); const nodes = this.$store.getters[`${ inStore }/all`](NODE);
const matchedNodes = this.value?.metadata?.annotations?.[HCI.MATCHED_NODES];
const selector = this.value?.spec?.nodeSelector; const selector = this.value?.spec?.nodeSelector;
if (!isEmpty(selector)) { if (!isEmpty(selector)) {
return matching(nodes, selector); return matching(nodes, selector);
} else if (matchedNodes && matchedNodes.length > 0) {
return nodes.filter(node => matchedNodes.includes(node.id));
} else { } else {
return nodes; return nodes;
} }

View File

@ -6,6 +6,7 @@ import { Banner } from '@components/Banner';
import { Checkbox } from '@components/Form/Checkbox'; import { Checkbox } from '@components/Form/Checkbox';
import { exceptionToErrorsArray } from '@shell/utils/error'; import { exceptionToErrorsArray } from '@shell/utils/error';
import { BadgeState } from '@components/BadgeState'; import { BadgeState } from '@components/BadgeState';
import { ucFirst } from '@shell/utils/string';
export default { export default {
components: { components: {
@ -25,9 +26,9 @@ export default {
data() { data() {
return { return {
errors: [], errors: [],
unhealthyVM: '', unhealthyVMs: [],
force: false force: false
}; };
}, },
@ -40,13 +41,21 @@ export default {
}, },
methods: { methods: {
ucFirst,
onInputForce(v) {
if (v) {
this.unhealthyVMs = [];
}
},
close() { close() {
this.$emit('close'); this.$emit('close');
}, },
async apply(buttonCb) { async apply(buttonCb) {
this.errors = []; this.errors = [];
this.unhealthyVM = ''; this.unhealthyVMs = [];
try { try {
const res = await this.actionResource.doAction('maintenancePossible'); const res = await this.actionResource.doAction('maintenancePossible');
@ -62,8 +71,8 @@ export default {
} else if (res._status === 200 || res._status === 204) { } else if (res._status === 200 || res._status === 204) {
const res = await this.actionResource.doAction('listUnhealthyVM'); const res = await this.actionResource.doAction('listUnhealthyVM');
if (res.message) { if (res?.length) {
this.unhealthyVM = res; this.unhealthyVMs = res;
buttonCb(false); buttonCb(false);
} else { } else {
await this.actionResource.doAction('enableMaintenanceMode', { force: 'false' }); await this.actionResource.doAction('enableMaintenanceMode', { force: 'false' });
@ -95,15 +104,20 @@ export default {
<Checkbox <Checkbox
v-model:value="force" v-model:value="force"
label-key="harvester.host.enableMaintenance.force" label-key="harvester.host.enableMaintenance.force"
@update:value="onInputForce"
/> />
</div> </div>
<Banner color="warning" :label="t('harvester.host.enableMaintenance.protip')" class="mb-0" />
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
<div v-if="unhealthyVM"> <Banner color="warning" :label="t('harvester.host.enableMaintenance.protip')" />
<Banner color="error mb-5">
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="ucFirst(err)" />
<Banner v-if="!force" class="mt-0" color="warning" :labelKey="'harvester.host.enableMaintenance.shutDownVMs'" />
<div v-for="(unhealthyVM, i) in unhealthyVMs" :key="i">
<Banner color="error mt-0 mb-5">
<p> <p>
{{ unhealthyVM.message }} {{ ucFirst(unhealthyVM.message) }}
</p> </p>
</Banner> </Banner>

View File

@ -72,8 +72,11 @@ export default {
const nodes = this.$store.getters['harvester/all'](NODE); const nodes = this.$store.getters['harvester/all'](NODE);
return nodes.filter((n) => { return nodes.filter((n) => {
// do not allow to migrate to self node const isNotSelfNode = !!this.availableNodes.includes(n.id);
return !!this.availableNodes.includes(n.id); const isNotWitnessNode = n.isEtcd !== 'true'; // do not allow to migrate to self node and witness node
const matchingCpuManagerConfig = n.isCPUManagerEnabled; // If cpu-pinning is enabled, filter-out non-enabled CPU manager nodes.
return isNotSelfNode && isNotWitnessNode && matchingCpuManagerConfig;
}).map((n) => { }).map((n) => {
let label = n?.metadata?.name; let label = n?.metadata?.name;
const value = n?.metadata?.name; const value = n?.metadata?.name;

View File

@ -0,0 +1,121 @@
<script>
import { mapGetters, mapState } from 'vuex';
import { Card } from '@components/Card';
import { Banner } from '@components/Banner';
import AsyncButton from '@shell/components/AsyncButton';
import UnitInput from '@shell/components/form/UnitInput';
import { exceptionToErrorsArray } from '@shell/utils/error';
export default {
name: 'HarvesterVMQuotaDialog',
components: {
AsyncButton,
Card,
UnitInput,
Banner
},
props: {
resources: {
type: Array,
required: true
}
},
created() {
this.totalSnapshotSize = this.modalData.snapshotSizeQuota;
},
data() {
return {
totalSnapshotSize: '',
errors: []
};
},
computed: {
...mapState('action-menu', ['modalData']),
...mapGetters({ t: 'i18n/t' }),
actionResource() {
return this.resources[0];
},
},
methods: {
close() {
this.totalSnapshotSize = '';
this.$emit('close');
},
async save(buttonDone) {
try {
// call delete action if user input 0Gi or empty string
if (this.totalSnapshotSize === null || this.totalSnapshotSize === '0Gi' ) {
await this.actionResource.doAction('deleteResourceQuota');
} else {
await this.actionResource.doAction('updateResourceQuota', { totalSnapshotSizeQuota: this.totalSnapshotSize });
}
this.close();
buttonDone(true);
} catch (err) {
const error = err?.data || err;
const message = exceptionToErrorsArray(error);
this['errors'] = message;
buttonDone(false);
}
},
}
};
</script>
<template>
<Card :show-highlight-border="false">
<h4
slot="title"
v-clean-html="t('harvester.modal.quota.editQuota')"
class="text-default-text"
/>
<template #body>
<Banner color="info">
{{ t('harvester.modal.quota.bannerMessage') }}
</Banner>
<UnitInput
v-model:value="totalSnapshotSize"
v-int-number
:label="t('harvester.snapshot.totalSnapshotSize')"
:disabled="false"
:mode="create"
:input-exponent="3"
:increment="1024"
:output-modifier="true"
suffix="GiB"
class="mb-20"
/>
</template>
<div slot="actions" class="actions">
<div class="buttons">
<button class="btn role-secondary mr-10" @click="close">
{{ t('generic.cancel') }}
</button>
<AsyncButton @click="save" />
</div>
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
</div>
</Card>
</template>
<style lang="scss" scoped>
.actions {
width: 100%;
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
</style>

View File

@ -149,7 +149,7 @@ export default {
/> />
</div> </div>
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" /> <Banner v-for="(err, i) in errors" :key="i"/>
</div> </div>
</Card> </Card>
</template> </template>

View File

@ -5,7 +5,7 @@ import { BadgeState } from '@components/BadgeState';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { RadioGroup, RadioButton } from '@components/Form/Radio'; import { RadioGroup, RadioButton } from '@components/Form/Radio';
import HarvesterDisk from '../../mixins/harvester-disk'; import HarvesterDisk from '../../mixins/harvester-disk';
import DiskTags from '../../components/DiskTags'; import Tags from '../../components/DiskTags';
import { HCI } from '../../types'; import { HCI } from '../../types';
import { LONGHORN_SYSTEM } from './index'; import { LONGHORN_SYSTEM } from './index';
@ -17,7 +17,7 @@ export default {
Banner, Banner,
RadioGroup, RadioGroup,
RadioButton, RadioButton,
DiskTags, Tags,
}, },
mixins: [ mixins: [
@ -44,6 +44,18 @@ export default {
return {}; return {};
}, },
computed: { computed: {
targetDisk() {
return this.disks.find(disk => disk.name === this.value.name);
},
schedulableTooltipMessage() {
const { name, path } = this.value;
if (this.targetDisk && !this.targetDisk.allowScheduling && name && path) {
return this.t('harvester.host.disk.allowScheduling.tooltip', { name, path });
} else {
return this.schedulableCondition.message;
}
},
allowSchedulingOptions() { allowSchedulingOptions() {
return [{ return [{
label: this.t('generic.enabled'), label: this.t('generic.enabled'),
@ -171,7 +183,7 @@ export default {
<div v-if="!value.isNew"> <div v-if="!value.isNew">
<div class="row"> <div class="row">
<div class="col span-12"> <div class="col span-12">
<DiskTags <Tags
v-model:value="value.tags" v-model:value="value.tags"
:label="t('harvester.host.disk.tags.label')" :label="t('harvester.host.disk.tags.label')"
:add-label="t('harvester.host.disk.tags.addLabel')" :add-label="t('harvester.host.disk.tags.addLabel')"
@ -181,6 +193,16 @@ export default {
</div> </div>
<div class="row mt-10"> <div class="row mt-10">
<div class="col span-12"> <div class="col span-12">
<div class="pull-left">
<RadioGroup
v-model:value="value.allowScheduling"
name="diskScheduling"
:label="t('harvester.host.disk.allowScheduling.label')"
:mode="mode"
:options="allowSchedulingOptions"
:row="true"
/>
</div>
<div class="pull-right"> <div class="pull-right">
{{ t('harvester.host.disk.conditions') }}: {{ t('harvester.host.disk.conditions') }}:
<BadgeState <BadgeState
@ -191,9 +213,9 @@ export default {
class="mr-10 ml-10 state" class="mr-10 ml-10 state"
/> />
<BadgeState <BadgeState
v-clean-tooltip="schedulableCondition.message" v-clean-tooltip="schedulableTooltipMessage"
:color="schedulableCondition.status === 'True' ? 'bg-success' : 'bg-error' " :color="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'bg-success' : 'bg-error' "
:icon="schedulableCondition.status === 'True' ? 'icon-checkmark' : 'icon-warning' " :icon="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'icon-checkmark' : 'icon-warning' "
label="Schedulable" label="Schedulable"
class="mr-10 state" class="mr-10 state"
/> />

View File

@ -5,6 +5,7 @@ import UnitInput from '@shell/components/form/UnitInput';
import { RadioGroup } from '@components/Form/Radio'; import { RadioGroup } from '@components/Form/Radio';
import { Checkbox } from '@components/Form/Checkbox'; import { Checkbox } from '@components/Form/Checkbox';
import { HCI } from '../../types'; import { HCI } from '../../types';
import { DOC_LINKS } from '../../config/doc-links';
export const ksmtunedMode = [{ export const ksmtunedMode = [{
value: 'standard', value: 'standard',
@ -86,6 +87,10 @@ export default {
showKsmt() { showKsmt() {
return this.spec.run === 'run'; return this.spec.run === 'run';
},
ksmtunedLink() {
return DOC_LINKS.KSMTUNED_MODE;
} }
}, },
@ -135,7 +140,7 @@ export default {
<Checkbox v-model:value="enableMergeAcrossNodes" :mode="mode" class="check mb-20" type="checkbox" :label="t('harvester.host.ksmtuned.enableMergeNodes')" /> <Checkbox v-model:value="enableMergeAcrossNodes" :mode="mode" class="check mb-20" type="checkbox" :label="t('harvester.host.ksmtuned.enableMergeNodes')" />
<h3> <h3>
<t k="harvester.host.ksmtuned.modeLink" :raw="true" /> <t k="harvester.host.ksmtuned.modeLink" :raw="true" :url="ksmtunedLink" />
</h3> </h3>
<RadioGroup <RadioGroup
v-model:value="spec.mode" v-model:value="spec.mode"

View File

@ -25,10 +25,10 @@ import { _EDIT } from '@shell/config/query-params';
import { sortBy } from '@shell/utils/sort'; import { sortBy } from '@shell/utils/sort';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { HCI } from '../../types'; import { HCI } from '../../types';
import DiskTags from '../../components/DiskTags';
import HarvesterDisk from './HarvesterDisk'; import HarvesterDisk from './HarvesterDisk';
import HarvesterSeeder from './HarvesterSeeder'; import HarvesterSeeder from './HarvesterSeeder';
import HarvesterKsmtuned from './HarvesterKsmtuned'; import HarvesterKsmtuned from './HarvesterKsmtuned';
import Tags from '../../components/DiskTags';
export const LONGHORN_SYSTEM = 'longhorn-system'; export const LONGHORN_SYSTEM = 'longhorn-system';
@ -46,7 +46,7 @@ export default {
ButtonDropdown, ButtonDropdown,
KeyValue, KeyValue,
Banner, Banner,
DiskTags, Tags,
Loading, Loading,
HarvesterSeeder, HarvesterSeeder,
MessageLink, MessageLink,
@ -478,10 +478,11 @@ export default {
const disks = this.longhornNode?.spec?.disks || {}; const disks = this.longhornNode?.spec?.disks || {};
// update each disk tags and scheduling
this.newDisks.map((disk) => { this.newDisks.map((disk) => {
(disks[disk.name] || {}).tags = disk.tags; (disks[disk.name] || {}).tags = disk.tags;
(disks[disk.name] || {}).allowScheduling = disk.allowScheduling;
}); });
let count = 0; let count = 0;
const retrySave = async() => { const retrySave = async() => {
@ -505,7 +506,9 @@ export default {
} }
}; };
await retrySave(); if (this.longhornNode) {
await retrySave();
}
}, },
}, },
}; };
@ -547,7 +550,7 @@ export default {
class="row mb-20" class="row mb-20"
> >
<div class="col span-12"> <div class="col span-12">
<DiskTags <Tags
v-model:value="longhornNode.spec.tags" v-model:value="longhornNode.spec.tags"
:label="t('harvester.host.tags.label')" :label="t('harvester.host.tags.label')"
:add-label="t('harvester.host.tags.addLabel')" :add-label="t('harvester.host.tags.addLabel')"
@ -650,7 +653,6 @@ export default {
:value="filteredLabels" :value="filteredLabels"
:add-label="t('labels.addLabel')" :add-label="t('labels.addLabel')"
:mode="mode" :mode="mode"
:title="t('labels.labels.title')"
:read-allowed="false" :read-allowed="false"
:value-can-be-empty="true" :value-can-be-empty="true"
@update:value="updateHostLabels" @update:value="updateHostLabels"

View File

@ -143,10 +143,19 @@ export default {
}, },
input(neu) { input(neu) {
const pattern = /^([1-9]|[1-9][0-9]{1,2}|[1-3][0-9]{3}|40[0-9][0-4])$/; if (neu === '') {
this.config.vlan = '';
if (!pattern.test(neu) && neu !== '') { return;
this.config.vlan = neu > 4094 ? 4094 : 1; }
const newValue = Number(neu);
if (newValue > 4094) {
this.config.vlan = 4094;
} else if (newValue < 1) {
this.config.vlan = 1;
} else {
this.config.vlan = newValue;
} }
}, },

View File

@ -17,7 +17,7 @@ import { STORAGE_CLASS, LONGHORN } from '@shell/config/types';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import { clone } from '@shell/utils/object'; import { clone } from '@shell/utils/object';
import { CSI_DRIVER } from '../../types'; import { CSI_DRIVER } from '../../types';
import DiskTags from '../../components/DiskTags'; import Tags from '../../components/DiskTags';
const LONGHORN_DRIVER = 'driver.longhorn.io'; const LONGHORN_DRIVER = 'driver.longhorn.io';
@ -34,7 +34,7 @@ export default {
Tab, Tab,
Tabbed, Tabbed,
Loading, Loading,
DiskTags, Tags,
}, },
mixins: [CreateEditView], mixins: [CreateEditView],
@ -304,7 +304,7 @@ export default {
/> />
</div> </div>
<div class="col span-8 value"> <div class="col span-8 value">
<DiskTags <Tags
v-model:value="scope.row.value.values" v-model:value="scope.row.value.values"
:add-label="t('generic.add')" :add-label="t('generic.add')"
:mode="modeOverride" :mode="modeOverride"

View File

@ -162,6 +162,7 @@ export default {
buttonCb(false); buttonCb(false);
} }
} else { } else {
this.value.spec.url = this.value.spec.url?.trim() || '';
this.save(buttonCb); this.save(buttonCb);
} }
}, },
@ -277,6 +278,7 @@ export default {
:can-yaml="showEditAsYaml ? true : false" :can-yaml="showEditAsYaml ? true : false"
:apply-hooks="applyHooks" :apply-hooks="applyHooks"
@finish="saveImage" @finish="saveImage"
@error="e=>errors=e"
> >
<NameNsDescription <NameNsDescription
ref="nd" ref="nd"

View File

@ -18,7 +18,6 @@ import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations
import CreateEditView from '@shell/mixins/create-edit-view'; import CreateEditView from '@shell/mixins/create-edit-view';
import { AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook'; import { AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook';
import { HCI } from '../types'; import { HCI } from '../types';
import { RunStrategys } from '../config/harvester-map';
import VM_MIXIN from '../mixins/harvester-vm'; import VM_MIXIN from '../mixins/harvester-vm';
import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved'; import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved';
import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume'; import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume';
@ -74,7 +73,6 @@ export default {
description: '', description: '',
defaultVersion: null, defaultVersion: null,
isDefaultVersion: false, isDefaultVersion: false,
RunStrategys,
}; };
}, },
@ -263,7 +261,7 @@ export default {
<Tab <Tab
name="nodeScheduling" name="nodeScheduling"
:label="t('workload.container.titles.nodeScheduling')" :label="t('workload.container.titles.nodeScheduling')"
:weight="-89" :weight="-3"
> >
<template #default="{active}"> <template #default="{active}">
<NodeScheduling <NodeScheduling
@ -274,7 +272,7 @@ export default {
</template> </template>
</Tab> </Tab>
<Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="-90"> <Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="-4">
<template #default="{active}"> <template #default="{active}">
<PodAffinity <PodAffinity
:mode="mode" :mode="mode"
@ -287,13 +285,42 @@ export default {
</template> </template>
</Tab> </Tab>
<Tab
:name="t('generic.labels')"
:label="t('harvester.tab.instanceLabel')"
:weight="-5"
>
<Labels
:default-container-class="'labels-and-annotations-container'"
:value="value"
:mode="mode"
:display-side-by-side="false"
:show-annotations="false"
:show-label-title="false"
>
<template #labels="{toggler}">
<KeyValue
key="labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@input="value.setInstanceLabels($event)"
/>
</template>
</Labels>
</Tab>
<Tab name="advanced" :label="t('harvester.tab.advanced')" :weight="-99"> <Tab name="advanced" :label="t('harvester.tab.advanced')" :weight="-99">
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6"> <div class="col span-6">
<LabeledSelect <LabeledSelect
v-model:value="runStrategy" v-model:value="runStrategy"
label-key="harvester.virtualMachine.runStrategy" label-key="harvester.virtualMachine.runStrategy"
:options="RunStrategys" :options="runStrategies"
:mode="mode" :mode="mode"
/> />
</div> </div>
@ -308,6 +335,37 @@ export default {
</div> </div>
</div> </div>
<div class="row mb-20">
<div class="col span-6">
<LabeledSelect
v-model:value="maintenanceStrategy"
label-key="harvester.virtualMachine.maintenanceStrategy.label"
:options="maintenanceStrategies"
:get-option-label="getMaintenanceStrategyOptionLabel"
:mode="mode"
/>
</div>
<div class="col span-6">
<Reserved
:reserved-memory="reservedMemory"
:mode="mode"
@updateReserved="updateReserved"
/>
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<Checkbox
v-model:value="cpuPinning"
class="check"
type="checkbox"
label-key="harvester.virtualMachine.cpuPinning.label"
:mode="mode"
/>
</div>
</div>
<div class="row mb-20"> <div class="row mb-20">
<a v-if="showAdvanced" v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" /> <a v-if="showAdvanced" v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
<a v-else v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" /> <a v-else v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
@ -315,13 +373,6 @@ export default {
<div v-if="showAdvanced"> <div v-if="showAdvanced">
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6">
<Reserved
:reserved-memory="reservedMemory"
:mode="mode"
@updateReserved="updateReserved"
/>
</div>
<div class="col span-6"> <div class="col span-6">
<UnitInput <UnitInput
v-model:value="terminationGracePeriodSeconds" v-model:value="terminationGracePeriodSeconds"
@ -386,35 +437,6 @@ export default {
:mode="mode" :mode="mode"
/> />
</Tab> </Tab>
<Tab
:name="t('generic.labels')"
:label="t('harvester.tab.instanceLabel')"
:weight="-99"
>
<Labels
:default-container-class="'labels-and-annotations-container'"
:value="value"
:mode="mode"
:display-side-by-side="false"
:show-annotations="false"
:show-label-title="false"
>
<template #labels="{toggler}">
<KeyValue
key="labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setInstanceLabels($event)"
/>
</template>
</Labels>
</Tab>
</Tabbed> </Tabbed>
</CruResource> </CruResource>
</template> </template>

View File

@ -56,6 +56,7 @@ export default {
const hash = await allHash(_hash); const hash = await allHash(_hash);
this.snapshots = hash.snapshots; this.snapshots = hash.snapshots;
this.images = hash.images;
const defaultStorage = this.$store.getters[`harvester/all`](STORAGE_CLASS).find( O => O.isDefault); const defaultStorage = this.$store.getters[`harvester/all`](STORAGE_CLASS).find( O => O.isDefault);
@ -77,6 +78,7 @@ export default {
storage, storage,
imageId, imageId,
snapshots: [], snapshots: [],
images: [],
}; };
}, },
@ -108,10 +110,8 @@ export default {
}, },
imageOption() { imageOption() {
const choices = this.$store.getters['harvester/all'](HCI.IMAGE);
return sortBy( return sortBy(
choices this.images
.filter(obj => obj.isReady) .filter(obj => obj.isReady)
.map((obj) => { .map((obj) => {
return { return {
@ -249,7 +249,17 @@ export default {
this.value['spec'] = spec; this.value['spec'] = spec;
}, },
updateImage() {
if (this.isVMImage && this.imageId) {
const imageResource = this.images?.find(image => this.imageId === image.id);
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
if (imageSize) {
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }Gi`;
}
}
this.update();
},
generateYaml() { generateYaml() {
const out = saferDump(this.value); const out = saferDump(this.value);
@ -300,7 +310,7 @@ export default {
required required
:mode="mode" :mode="mode"
class="mb-20" class="mb-20"
@update:value="update" @update:value="updateImage"
/> />
<LabeledSelect <LabeledSelect

View File

@ -140,15 +140,24 @@ export default {
onImageChange() { onImageChange() {
const imageResource = this.$store.getters['harvester/all'](HCI.IMAGE)?.find( I => this.value.image === I.id); const imageResource = this.$store.getters['harvester/all'](HCI.IMAGE)?.find( I => this.value.image === I.id);
const isIsoImage = /iso$/i.test(imageResource?.imageSuffix);
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
if (this.idx === 0) { if (isIsoImage) {
if (/iso$/i.test(imageResource?.imageSuffix)) { this.value['type'] = 'cd-rom';
this.value['type'] = 'cd-rom'; this.value['bus'] = 'sata';
this.value['bus'] = 'sata'; } else {
} else { this.value['type'] = 'disk';
this.value['type'] = 'disk'; this.value['bus'] = 'virtio';
this.value['bus'] = 'virtio'; }
if (imageSize) {
let imageSizeGiB = Math.ceil(imageSize / 1024 / 1024 / 1024);
if (!isIsoImage) {
imageSizeGiB = Math.max(imageSizeGiB, 10);
} }
this.value['size'] = `${ imageSizeGiB }Gi`;
} }
this.update(); this.update();

View File

@ -28,7 +28,6 @@ import CreateEditView from '@shell/mixins/create-edit-view';
import { parseVolumeClaimTemplates } from '@pkg/utils/vm'; import { parseVolumeClaimTemplates } from '@pkg/utils/vm';
import VM_MIXIN from '../../mixins/harvester-vm'; import VM_MIXIN from '../../mixins/harvester-vm';
import { RunStrategys } from '../../config/harvester-map';
import { HCI } from '../../types'; import { HCI } from '../../types';
import RestartVMDialog from '../../dialog/RestartVMDialog'; import RestartVMDialog from '../../dialog/RestartVMDialog';
import VirtualMachineVGpuDevices from './VirtualMachineVGpuDevices/index'; import VirtualMachineVGpuDevices from './VirtualMachineVGpuDevices/index';
@ -40,6 +39,8 @@ import Network from './VirtualMachineNetwork';
import Volume from './VirtualMachineVolume'; import Volume from './VirtualMachineVolume';
import SSHKey from './VirtualMachineSSHKey'; import SSHKey from './VirtualMachineSSHKey';
import Reserved from './VirtualMachineReserved'; import Reserved from './VirtualMachineReserved';
import { Banner } from '@components/Banner';
import MessageLink from '@shell/components/MessageLink';
export default { export default {
name: 'HarvesterEditVM', name: 'HarvesterEditVM',
@ -68,6 +69,8 @@ export default {
UnitInput, UnitInput,
VirtualMachineVGpuDevices, VirtualMachineVGpuDevices,
KeyValue, KeyValue,
Banner,
MessageLink
}, },
mixins: [CreateEditView, VM_MIXIN], mixins: [CreateEditView, VM_MIXIN],
@ -96,13 +99,19 @@ export default {
isOpen: false, isOpen: false,
hostname, hostname,
isRestartImmediately, isRestartImmediately,
RunStrategys,
}; };
}, },
computed: { computed: {
...mapGetters({ t: 'i18n/t' }), ...mapGetters({ t: 'i18n/t' }),
to() {
return {
name: 'harvester-c-cluster-resource',
params: { cluster: this.$store.getters['clusterId'], resource: HCI.HOST },
};
},
machineTypeOptions() { machineTypeOptions() {
return [{ return [{
label: 'None', label: 'None',
@ -173,6 +182,26 @@ export default {
hasStartAction() { hasStartAction() {
return this.value.hasAction('start'); return this.value.hasAction('start');
}, },
enableCpuPinningCheckbox() {
if (this.mode === 'create') {
return this.nodes.some(node => node.isCPUManagerEnabled); // any one of nodes has label cpuManager=true
}
return true;
},
showCpuPinningBanner() {
if (this.mode === 'edit') {
return this.cpuPinning !== !!this.cloneVM.spec.template.spec.domain.cpu.dedicatedCpuPlacement;
}
if (this.mode === 'create') {
return this.nodes.every(node => !node.isCPUManagerEnabled); // no node enabled CPU manager
}
return false;
}
}, },
watch: { watch: {
@ -273,6 +302,15 @@ export default {
}, },
methods: { methods: {
cancelAction() {
const { fromPage = HCI.VM } = this.$route?.query; // default back to VM list page
const cancelOverride = {
name: this.doneRoute,
params: { resource: fromPage }
};
this.$router.replace(cancelOverride);
},
saveVM(buttonCb) { saveVM(buttonCb) {
clear(this.errors); clear(this.errors);
@ -437,12 +475,14 @@ export default {
id="vm" id="vm"
:done-route="doneRoute" :done-route="doneRoute"
:resource="value" :resource="value"
:cancelEvent="true"
:mode="mode" :mode="mode"
:can-yaml="isSingle ? true : false" :can-yaml="isSingle ? true : false"
:errors="errors" :errors="errors"
:generate-yaml="generateYaml" :generate-yaml="generateYaml"
:apply-hooks="applyHooks" :apply-hooks="applyHooks"
@finish="saveVM" @finish="saveVM"
@cancel="cancelAction"
> >
<RadioGroup <RadioGroup
v-if="isCreate" v-if="isCreate"
@ -606,44 +646,54 @@ export default {
/> />
</Tab> </Tab>
<Tab <Tab v-if="enabledSriovgpu" :label="t('harvester.tab.vGpuDevices')" name="vGpuDevices" :weight="-6">
v-if="enabledSriovgpu" <VirtualMachineVGpuDevices :mode="mode" :value="spec.template.spec" :vm="value" />
:label="t('harvester.tab.vGpuDevices')" </Tab>
name="vGpuDevices"
:weight="-6" <Tab v-if="isEdit" :label="t('harvester.tab.accessCredentials')" name="accessCredentials" :weight="-7">
> <AccessCredentials v-model:value="accessCredentials" :mode="mode" :resource="value" :is-qemu-installed="isQemuInstalled" />
<VirtualMachineVGpuDevices
:mode="mode"
:value="spec.template.spec"
:vm="value"
/>
</Tab> </Tab>
<Tab <Tab
v-if="isEdit" name="instanceLabel"
:label="t('harvester.tab.accessCredentials')" :label="t('harvester.tab.instanceLabel')"
name="accessCredentials" :weight="-8"
:weight="-7"
> >
<AccessCredentials <Labels
v-model:value="accessCredentials" :default-container-class="'labels-and-annotations-container'"
:value="value"
:mode="mode" :mode="mode"
:resource="value" :display-side-by-side="false"
:is-qemu-installed="isQemuInstalled" :show-annotations="false"
/> :show-label-title="false"
>
<template #labels="{toggler}">
<KeyValue
key="labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setInstanceLabels($event)"
/>
</template>
</Labels>
</Tab> </Tab>
<Tab <Tab
name="advanced" name="advanced"
:label="t('harvester.tab.advanced')" :label="t('harvester.tab.advanced')"
:weight="-8" :weight="-9"
> >
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6"> <div class="col span-6">
<LabeledSelect <LabeledSelect
v-model:value="runStrategy" v-model:value="runStrategy"
label-key="harvester.virtualMachine.runStrategy" label-key="harvester.virtualMachine.runStrategy"
:options="RunStrategys" :options="runStrategies"
:mode="mode" :mode="mode"
/> />
</div> </div>
@ -659,24 +709,30 @@ export default {
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<a <div class="col span-6">
v-if="showAdvanced" <LabeledSelect
v-t="'harvester.generic.showMore'" v-model:value="maintenanceStrategy"
role="button" label-key="harvester.virtualMachine.maintenanceStrategy.label"
@click="toggleAdvanced" :options="maintenanceStrategies"
/> :get-option-label="getMaintenanceStrategyOptionLabel"
<a :mode="mode"
v-else />
v-t="'harvester.generic.showMore'" </div>
role="button" <div class="col span-6">
@click="toggleAdvanced" <Reserved
/> :reserved-memory="reservedMemory"
:mode="mode"
@updateReserved="updateReserved"
/>
</div>
</div> </div>
<div <div class="row mb-20">
v-if="showAdvanced" <a v-if="showAdvanced" v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
class="mb-20" <a v-else v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
> </div>
<div v-if="showAdvanced" class="mb-20">
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6"> <div class="col span-6">
<LabeledInput <LabeledInput
@ -698,13 +754,6 @@ export default {
</div> </div>
<div class="row mb-20"> <div class="row mb-20">
<div class="col span-6">
<Reserved
:reserved-memory="reservedMemory"
:mode="mode"
@updateReserved="updateReserved"
/>
</div>
<div class="col span-6"> <div class="col span-6">
<UnitInput <UnitInput
v-model:value="terminationGracePeriodSeconds" v-model:value="terminationGracePeriodSeconds"
@ -730,6 +779,16 @@ export default {
@updateDataTemplateId="updateDataTemplateId" @updateDataTemplateId="updateDataTemplateId"
/> />
<Checkbox
v-model:value="cpuPinning"
:disabled="!enableCpuPinningCheckbox"
class="check"
type="checkbox"
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
label-key="harvester.virtualMachine.cpuPinning.label"
:mode="mode"
/>
<Checkbox <Checkbox
v-model:value="installUSBTablet" v-model:value="installUSBTablet"
class="check mt-20" class="check mt-20"
@ -773,35 +832,21 @@ export default {
:label="t('harvester.virtualMachine.secureBoot')" :label="t('harvester.virtualMachine.secureBoot')"
:mode="mode" :mode="mode"
/> />
</Tab> <Banner
v-if="showCpuPinningBanner"
<Tab color="warning"
name="instanceLabel"
:label="t('harvester.tab.instanceLabel')"
:weight="-99"
>
<Labels
:default-container-class="'labels-and-annotations-container'"
:value="value"
:mode="mode"
:display-side-by-side="false"
:show-annotations="false"
:show-label-title="false"
> >
<template #labels="{toggler}"> <MessageLink
<KeyValue v-if="mode === 'create'"
key="labels" :to="to"
:value="value.instanceLabels" prefix-label="harvester.virtualMachine.advancedOptions.cpuManager.prefix"
:protected-keys="value.systemLabels || []" middle-label="harvester.virtualMachine.advancedOptions.cpuManager.middle"
:toggle-filter="toggler" suffix-label="harvester.virtualMachine.advancedOptions.cpuManager.suffix"
:add-label="t('labels.addLabel')" />
:mode="mode" <span v-if="mode==='edit'">
:read-allowed="false" {{ t('harvester.virtualMachine.cpuPinning.restartVMMessage') }}
:value-can-be-empty="true" </span>
@update:value="value.setInstanceLabels($event)" </Banner>
/>
</template>
</Labels>
</Tab> </Tab>
</Tabbed> </Tabbed>

View File

@ -314,12 +314,21 @@ export default {
} else if (selector[HOSTNAME] && Object.keys(selector).length === 1) { } else if (selector[HOSTNAME] && Object.keys(selector).length === 1) {
const matchNode = allNodes.find(n => n.id === selector[HOSTNAME]); const matchNode = allNodes.find(n => n.id === selector[HOSTNAME]);
this.matchingNodes = { if (matchNode) {
matched: 1, this.matchingNodes = {
total: allNodes.length, matched: 1,
none: false, total: allNodes.length,
sample: matchNode ? matchNode.nameDisplay : selector[HOSTNAME], none: false,
}; sample: matchNode.nameDisplay,
};
} else {
this.matchingNodes = {
matched: 0,
total: 0,
none: true,
sample: null,
};
}
} else { } else {
const match = matching(allNodes, selector); const match = matching(allNodes, selector);

View File

@ -0,0 +1,44 @@
<script>
export default {
name: 'HarvesterCPUPinningFormatter',
props: {
value: {
type: String, // id
default: '',
},
rows: {
type: Array,
required: true,
},
},
computed: {
row() {
return this.rows.find(r => r.id === this.value);
},
cpuManagerStatus() {
if (this.row?.isCPUManagerEnableInProgress) {
return this.t('generic.loading');
}
return this.row?.isCPUManagerEnabled ? this.t('generic.enabled') : this.t('generic.disabled');
},
}
};
</script>
<template>
<span v-if="row?.isCPUManagerEnableInProgress" v-clean-tooltip="cpuManagerStatus">
<i class="icon icon-spinner icon-spin" />
</span>
<span v-else-if="row?.isCPUManagerEnabled" v-clean-tooltip="cpuManagerStatus">
<i class="icon icon-checkmark" />
</span>
<span
v-else
v-clean-tooltip="cpuManagerStatus"
class="text-muted"
>
&mdash;
</span>
</template>

View File

@ -23,77 +23,76 @@ export default {
default: '' default: ''
}, },
showReserved: { showAllocated: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;
this.longhornSettings = await this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.SETTINGS });
},
data() { data() {
return {}; const inStore = this.$store.getters['currentProduct'].inStore;
const longhornSettings = this.$store.getters[`${ inStore }/all`](LONGHORN.SETTINGS) || [];
return { longhornSettings };
}, },
computed: { computed: {
usage() { storageStats() {
const stats = {
used: 0,
scheduled: 0,
maximum: 0,
reserved: 0,
total: 0
};
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`) || {}; const node = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`) || {};
const storageOverProvisioningPercentageSetting = this.longhornSettings.find(s => s.id === 'longhorn-system/storage-over-provisioning-percentage');
const disks = node?.spec?.disks || {};
const diskStatus = node?.status?.diskStatus || {};
return longhornNode?.used || 0; stats.used += node?.spec?.allowScheduling ? node.used : 0;
},
reserved() { Object.keys(disks).map((key) => {
const inStore = this.$store.getters['currentProduct'].inStore; stats.scheduled += node?.spec?.allowScheduling ? (diskStatus[key]?.storageScheduled || 0) : 0;
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`); stats.reserved += disks[key]?.storageReserved || 0;
let reserved = 0; });
Object.values(diskStatus).map((diskStat) => {
const disks = longhornNode?.spec?.disks || {}; stats.maximum += diskStat?.storageMaximum || 0;
Object.values(disks).map((disk) => {
if (disk.allowScheduling) {
reserved += disk.storageReserved;
}
}); });
return reserved; stats.total = ((stats.maximum - stats.reserved) * Number(storageOverProvisioningPercentageSetting?.value ?? 0)) / 100;
},
total() { return stats;
const inStore = this.$store.getters['currentProduct'].inStore;
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`);
let out = 0;
const diskStatus = longhornNode?.status?.diskStatus || {};
Object.values(diskStatus).map((disk) => {
if (disk?.storageMaximum) {
out += disk.storageMaximum;
}
});
return out;
}, },
units() { units() {
const exponent = exponentNeeded(this.total, 1024); const exponent = exponentNeeded(this.storageStats.maximum, 1024);
return `${ UNITS[exponent] }iB`; return `${ UNITS[exponent] }iB`;
}, },
used() { used() {
let out = this.formatter(this.usage || 0); let out = this.formatter(this.storageStats.used);
if (!Number.parseFloat(out) > 0) { if (!Number.parseFloat(out) > 0) {
out = this.formatter(this.usage || 0, { canRoundToZero: false }); out = this.formatter(this.storageStats.used, { canRoundToZero: false });
} }
return out; return out;
}, },
formatReserved() { formatAllocated() {
let out = this.formatter(this.reserved || 0); let out = this.formatter(this.storageStats.scheduled);
if (!Number.parseFloat(out) > 0) { if (!Number.parseFloat(out) > 0) {
out = this.formatter(this.reserved || 0, { canRoundToZero: false }); out = this.formatter(this.storageStats.scheduled, { canRoundToZero: false });
} }
return out; return out;
@ -102,15 +101,15 @@ export default {
usedAmountTemplateValues() { usedAmountTemplateValues() {
return { return {
used: this.used, used: this.used,
total: this.formatter(this.total || 0), total: this.formatter(this.storageStats.maximum),
unit: this.units, unit: this.units,
}; };
}, },
reservedAmountTemplateValues() { allocatedAmountTemplateValues() {
return { return {
used: this.formatReserved, used: this.formatAllocated,
total: this.formatter(this.total || 0), total: this.formatter(this.storageStats.total),
unit: this.units, unit: this.units,
}; };
}, },
@ -118,7 +117,7 @@ export default {
methods: { methods: {
formatter(value, format) { formatter(value, format) {
const minExponent = exponentNeeded(this.total, 1024); const minExponent = exponentNeeded(this.storageStats.maximum, 1024);
const formatOptions = { const formatOptions = {
addSuffix: false, addSuffix: false,
increment: 1024, increment: 1024,
@ -137,21 +136,21 @@ export default {
<template> <template>
<div> <div>
<div <div
v-if="showReserved" v-if="showAllocated"
> >
<ConsumptionGauge <ConsumptionGauge
:capacity="total" :capacity="storageStats.total"
:used="reserved" :used="storageStats.scheduled"
:units="units" :units="units"
:number-formatter="formatter" :number-formatter="formatter"
:resource-name="resourceName" :resource-name="resourceName"
> >
<template #title="{formattedPercentage}"> <template #title="{formattedPercentage}">
<span> <span>
{{ t('clusterIndexPage.hardwareResourceGauge.reserved') }} {{ t('clusterIndexPage.hardwareResourceGauge.allocated') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }}
<span class="ml-10 percentage"> <span class="ml-10 percentage">
/&nbsp;{{ formattedPercentage }} /&nbsp;{{ formattedPercentage }}
</span> </span>
@ -160,13 +159,13 @@ export default {
</ConsumptionGauge> </ConsumptionGauge>
</div> </div>
<ConsumptionGauge <ConsumptionGauge
:capacity="total" :capacity="storageStats.maximum"
:used="usage" :used="storageStats.used"
:units="units" :units="units"
:number-formatter="formatter" :number-formatter="formatter"
:resource-name="showReserved ? '' : resourceName" :resource-name="showAllocated ? '' : resourceName"
:class="{ :class="{
'mt-10': showReserved, 'mt-10': showAllocated,
}" }"
> >
<template #title="{formattedPercentage}"> <template #title="{formattedPercentage}">

View File

@ -6,14 +6,15 @@ generic:
labels: Labels labels: Labels
inProgress: In Progress inProgress: In Progress
basic: Basic basic: Basic
loading: Loading...
nav: nav:
group: group:
networks: Networks networks: Networks
backupAndSnapshot: Backup & Snapshot backupAndSnapshot: Backup and Snapshots
Monitoring: Monitoring Monitoring: Monitoring
Logging: Logging Logging: Logging
'Monitoring & Logging': Monitoring & Logging 'Monitoring and Logging': Monitoring and Logging
resourceTable: resourceTable:
groupBy: groupBy:
@ -28,7 +29,7 @@ members:
asyncButton: asyncButton:
restart: restart:
action: Save & Restart action: Save and Restart
success: Restarted success: Restarted
waiting: Restarting&hellip; waiting: Restarting&hellip;
@ -38,9 +39,13 @@ harvester:
backup: backup:
success: 'Backup { backUpName } has been initiated.' success: 'Backup { backUpName } has been initiated.'
addBackup: Add Backup addBackup: Add Backup
quota:
editVMQuota: Edit VM Quota
editQuota: Edit Quota
bannerMessage: Set to empty string or 0 to remove total snapshot size quota.
restore: restore:
success: 'Restore { name } created successfully.' success: 'Restore { name } created successfully.'
title: Backup and restore title: Backup and Restore
selectBackup: Select Backup selectBackup: Select Backup
message: message:
backup: Please select the backup that needs to be restored. backup: Please select the backup that needs to be restored.
@ -53,16 +58,16 @@ harvester:
success: 'Template { templateName } created successfully.' success: 'Template { templateName } created successfully.'
failed: 'Failed generated template!' failed: 'Failed generated template!'
cloneVM: cloneVM:
title: Clone VM title: Clone Virtual Machine
name: New VM Name name: New Virtual Machine Name
type: Clone volume data type: Clone volume data
action: action:
create: Create create: Create
clone: Clone clone: Clone
message: message:
tip: Please enter a VM name! tip: Please enter a virtual machine name!
success: 'VM { name } cloned successfully.' success: 'Virtual machine { name } cloned successfully.'
failed: 'Failed clone VM!' failed: 'Failed clone virtual machine!'
exportImage: exportImage:
title: Export to Image title: Export to Image
name: Name name: Name
@ -77,21 +82,21 @@ harvester:
label: Target Node label: Target Node
placeholder: Choose Target Node placeholder: Choose Target Node
ejectCDROM: ejectCDROM:
title: Eject CDROM title: Eject CD-ROM
warnTip: Eject volume will restart the virtual machine. warnTip: Eject volume will restart the virtual machine.
operationTip: 'Select the volume you want to delete:' operationTip: 'Select the volume you want to delete:'
delete: Delete delete: Delete
bundle: bundle:
title: Generate Support Bundle title: Generate a Support Bundle
url: Issue URL url: Issue URL
description: Description description: Description
requiredDesc: Description is required! requiredDesc: Description is required!
titleDescription: Collect system-related log in Harvester, to help with troubleshooting and support. titleDescription: Collect system-related logs in Harvester to help with troubleshooting and support.
hotplug: hotplug:
success: 'Volume { diskName } is mounted to the VM { vm }.' success: 'Volume { diskName } is mounted to the virtual machine { vm }.'
title: Add Volume title: Add Volume
hotunplug: hotunplug:
success: 'Volume { name } is detach successfully.' success: 'Volume { name } is detached successfully.'
snapshot: snapshot:
title: Take Snapshot title: Take Snapshot
name: Name name: Name
@ -109,10 +114,10 @@ harvester:
vmSnapshot: vmSnapshot:
title: Take VM Snapshot title: Take VM Snapshot
name: Name name: Name
success: 'Take VM Snapshot { name } successfully.' success: 'Take virtual machine Snapshot { name } successfully.'
restart: restart:
title: Restart VM title: Restart Virtual Machine
tip: Restart the Virtual Machine now for configuration changes to take effect. tip: Restart the virtual machine for configuration changes to take effect.
cancel: Save cancel: Save
notification: notification:
title: title:
@ -130,11 +135,12 @@ harvester:
deepClone: Clone deepClone: Clone
shallowClone: Clone Template shallowClone: Clone Template
unpause: Unpause unpause: Unpause
ejectCDROM: Eject CDROM ejectCDROM: Eject CD-ROM
editVMQuota: Edit VM Quota
launchFormTemplate: Launch instance from template launchFormTemplate: Launch instance from template
modifyTemplate: Modify template (Create new version) modifyTemplate: Modify template (Create new version)
setDefaultVersion: Set default version setDefaultVersion: Set default version
addTemplateVersion: Add templateVersion addTemplateVersion: Add template version
backup: Take Backup backup: Take Backup
restore: Restore restore: Restore
restoreNewVM: Restore New restoreNewVM: Restore New
@ -144,6 +150,8 @@ harvester:
createTemplate: Generate Template createTemplate: Generate Template
enableMaintenance: Enable Maintenance Mode enableMaintenance: Enable Maintenance Mode
disableMaintenance: Disable Maintenance Mode disableMaintenance: Disable Maintenance Mode
enableCPUManager: Enable CPU Manager
disableCPUManager: Disable CPU Manager
cordon: Cordon cordon: Cordon
uncordon: Uncordon uncordon: Uncordon
addHotplug: Add Volume addHotplug: Add Volume
@ -152,23 +160,25 @@ harvester:
cancelExpand: Cancel Expand cancelExpand: Cancel Expand
snapshot: Take Snapshot snapshot: Take Snapshot
pvcClone: Clone Volume pvcClone: Clone Volume
vmSnapshot: Take VM Snapshot vmSnapshot: Take Virtual Machine Snapshot
shutdown: Shut Down shutdown: Shut Down
powerOn: Power On powerOn: Power On
reboot: Reboot reboot: Reboot
forceStop: Force Stop forceStop: Force Stop
tableHeaders: tableHeaders:
size: Size size: Size
virtualSize: Virtual Size
progress: Progress progress: Progress
message: Message message: Message
phase: Phase phase: Phase
attachedVM: Attached VM attachedVM: Attached Virtual Machine
cpuManager: CPU Manager
fingerprint: Fingerprint fingerprint: Fingerprint
value: Value value: Value
actions: Actions actions: Actions
readyToUse: Ready To Use readyToUse: Ready To Use
backupTarget: Backup Target backupTarget: Backup Target
targetVm: Target VM targetVm: Target Virtual Machine
hostIp: Host IP hostIp: Host IP
vm: vm:
ipAddress: IP Address ipAddress: IP Address
@ -176,10 +186,11 @@ harvester:
defaultVersion: Default Version defaultVersion: Default Version
network: network:
type: Type type: Type
vlan: Vlan ID vlan: VLAN ID
snapshotTargetVolume: Original Volume snapshotTargetVolume: Original Volume
volumeSnapshotCounts: Snapshot Counts volumeSnapshotCounts: Snapshot Counts
networkState: Network State networkState: Network State
totalSnapshotQuota: Total Snapshot Quota
storageClass: Storage Class storageClass: Storage Class
restore: Restore restore: Restore
tab: tab:
@ -188,8 +199,10 @@ harvester:
advanced: Advanced Options advanced: Advanced Options
accessCredentials: Access Credentials accessCredentials: Access Credentials
pciDevices: PCI Devices pciDevices: PCI Devices
vGpuDevices: VGPU Devices vGpuDevices: vGPU Devices
vmScheduling: VM Scheduling vmScheduling: Virtual Machine Scheduling
quotas: Quotas
snapshots: Snapshots
instanceLabel: Instance Labels instanceLabel: Instance Labels
fields: fields:
version: Version version: Version
@ -200,24 +213,24 @@ harvester:
volume: Volume volume: Volume
network: Network network: Network
model: Model model: Model
macAddress: Mac Address macAddress: MAC address
port: Port port: Port
protocol: Protocol protocol: Protocol
remove: REMOVE remove: Remove
PhysicalNic: Physical NIC PhysicalNic: Physical NIC
cpu: Cpu cpu: CPU
memory: Memory memory: Memory
virtualName: Virtual machine name virtualName: Virtual machine name
promiscuous: Promiscuous promiscuous: Promiscuous
ipv4Address: IPv4 Address ipv4Address: IPv4 address
filterLabels: Filter Labels filterLabels: Filter labels
storageClass: Storage Class storageClass: Storage class
dockerImage: Docker Image dockerImage: Docker image
pci: pci:
available: Available Devices available: Available Devices
compatibleNodes: Compatible Nodes compatibleNodes: Compatible Nodes
impossibleSelection: 'There are no hosts with all of the selected devices.' impossibleSelection: 'There are no hosts with all of the selected devices.'
howToUseDevice: 'Use the table below to enable PCI passthrough on each device you want to use in this VM.' howToUseDevice: 'Use the table below to enable PCI passthrough on each device you want to use in this virtual machine.'
deviceInTheSameHost: 'You can only select devices on the same host.' deviceInTheSameHost: 'You can only select devices on the same host.'
oldFormatDevices: oldFormatDevices:
help: |- help: |-
@ -228,11 +241,11 @@ harvester:
{oldFormatDevicesHTML} {oldFormatDevicesHTML}
</ul> </ul>
<p> <p>
Please use the following instructions to update the VM: Please use the following instructions to update the virtual machine:
</p> </p>
<ol> <ol>
<li>Stop the VM, edit the VM YAML, and remove the <Code>hostDevices</Code> section, and save VM the changes to the YAML file.</li> <li>Stop the virtual machine, edit the virtual machine YAML, and remove the <Code>hostDevices</Code> section, and save virtual machine the changes to the YAML file.</li>
<li>Edit the VM, and add the already enabled PCIDevice from the list of available PCIDevices, and save and start VM.</li> <li>Edit the virtual machine, and add the already enabled PCI Device from the list of available PCIDevices, and save and start VM.</li>
</ol> </ol>
showCompatibility: Show device compatibility matrix showCompatibility: Show device compatibility matrix
hideCompatibility: Hide device compatibility matrix hideCompatibility: Hide device compatibility matrix
@ -241,12 +254,12 @@ harvester:
cantUnclaim: You cannot disable passthrough on a device claimed by another user. cantUnclaim: You cannot disable passthrough on a device claimed by another user.
enableGroup: Enable Group enableGroup: Enable Group
disableGroup: Disable Group disableGroup: Disable Group
labelRequired: "This rule should not be manually altered: it ensures that the PCI devices selected for this virtual machine are available on the VM's host." labelRequired: "This rule should not be manually altered: it ensures that the PCI devices selected for this virtual machine are available on the virtual machine's host."
goSetting: goSetting:
prefix: The pcidevices-controller addon is not enabled, click prefix: The pcidevices-controller add-on is not enabled, click
middle: here middle: here
suffix: to enable it to manage your PCI devices. suffix: to enable it to manage your PCI devices.
noPCIPermission: Please contact system admin to enable the PCI devices first. noPCIPermission: Please contact your system administrator to enable the PCI devices first.
enablePassthroughWarning: Please be careful not to use host-owned PCI devices (e.g., management and VLAN NICs). Incorrect device allocation may cause damage to your cluster, including node failure. enablePassthroughWarning: Please be careful not to use host-owned PCI devices (e.g., management and VLAN NICs). Incorrect device allocation may cause damage to your cluster, including node failure.
matrixHostName: Host Name matrixHostName: Host Name
matrixDeviceClaimName: Device Claim Name matrixDeviceClaimName: Device Claim Name
@ -299,20 +312,21 @@ harvester:
events: events:
label: Events label: Events
vmMetrics: vmMetrics:
label: VM Metrics label: Virtual Machine Metrics
version: Version version: Version
host: host:
console: Console console: Console
label: Hosts label: Hosts
inconsistentIP: "Host IP is inconsistent, current IP: { currentIP }, initial IP: { initIP }" inconsistentIP: "Host IP is inconsistent, current IP: { currentIP }, initial IP: { initIP }"
noConsoleUrl: 'Console URL not specified'
promote: promote:
none: ' ' none: ' '
running: promoting running: Promoting
failed: promote failed failed: Promote failed
unknown: promote halted unknown: Promote halted
promoteRestart: restarting promoteRestart: Restarting
promoteSucceed: promote completed promoteSucceed: Promote completed
tabs: tabs:
network: Network network: Network
overview: Overview overview: Overview
@ -322,10 +336,10 @@ harvester:
storage: Storage storage: Storage
labels: Labels labels: Labels
ksmtuned: Ksmtuned ksmtuned: Ksmtuned
seeder: Out-of-Band Access seeder: Out-of-band Access
detail: detail:
kvm: kvm:
disableMessage: Hardware-based virtualization is disabled or not supported. Hardware-based virtualization must be enabled before creating any Virtual Machines. disableMessage: Hardware-based virtualization is disabled or not supported. Hardware-based virtualization must be enabled before creating any virtual machines.
title: title:
network: Network Configuration network: Network Configuration
hostIP: Host IP hostIP: Host IP
@ -349,9 +363,11 @@ harvester:
serialNumber: Serial Number serialNumber: Serial Number
model: Model model: Model
etcd: Witness Node etcd: Witness Node
cpuManager: CPU Manager
enableMaintenance: enableMaintenance:
title: Enable Maintenance Mode title: Enable Maintenance Mode
protip: The operation will migrate all virtual machines on this node to other nodes. protip: The operation will migrate all virtual machines on this node to other nodes.
shutDownVMs: Check <b>Force</b> option to shutdown virtual machines which cannot be migrated in live mode.
force: Force force: Force
cordon: cordon:
title: Cordon title: Cordon
@ -360,15 +376,15 @@ harvester:
run: Run Strategy run: Run Strategy
configure: Configure configure: Configure
mode: Mode mode: Mode
modeLink: Mode <a href="https://docs.harvesterhci.io/v1.1/host/#ksmtuned-mode" target="_blank"><i class="icon icon-info" /></a> modeLink: Mode <a href="{url}" target="_blank"><i class="icon icon-info" /></a>
thresCoef: Threshold Coefficient thresCoef: Threshold Coefficient
enableMergeNodes: Enable Merge Across Nodes enableMergeNodes: Enable merging across nodes
enable: Enable enable: Enable
disable: Disable disable: Disable
ksmStatus: KSM Status ksmStatus: KSM Status
modeOption: modeOption:
standard: Standard standard: Standard
high: High-Perfomanace high: High-performance
customized: Customized customized: Customized
parameters: parameters:
title: Ksmtuned Parameters title: Ksmtuned Parameters
@ -400,11 +416,12 @@ harvester:
label: Storage Reserved label: Storage Reserved
allowScheduling: allowScheduling:
label: Scheduling label: Scheduling
tooltip: Disk {name} ({path}) scheduling is disabled
evictionRequested: evictionRequested:
label: Eviction Requested label: Eviction Requested
forceFormatted: forceFormatted:
label: Force Formatted label: Force Formatted
toolTip: Force formatted will cleanup disk data, make sure you backup all available data to prevent data loss. toolTip: Force formatted will clean up disk data, make sure you backup all available data to prevent data loss.
yes: Yes (Ext4 File System) yes: Yes (Ext4 File System)
description: description:
label: Description label: Description
@ -437,10 +454,8 @@ harvester:
tips: You can configure multiple IPv4 addresses or host addresses. tips: You can configure multiple IPv4 addresses or host addresses.
placeholder: e.g. IPv4 placeholder: e.g. IPv4
ntpSyncStatus: ntpSyncStatus:
isDisabled: 'The NTP is disabled. Please check the NTP service is active.' isDisabled: 'NTP is disabled. Please check the NTP service is active.'
isUnsynced: 'The NTP is not synced with the NTP server <code>{current}</code>' isUnsynced: 'NTP is not synchronized with the NTP server {current}.'
ntpConfigSyncStatus:
isUnsynced: 'The NTP config is not synced. Current: <code>{current}</code>, Wanted: <code>{wanted}</code> '
virtualMachine: virtualMachine:
label: Virtual Machines label: Virtual Machines
@ -453,7 +468,7 @@ harvester:
nameLabel: Name nameLabel: Name
host: host:
label: Hostname label: Hostname
placeholder: default to the virtual machine name. placeholder: Default to the virtual machine name.
multiple: multiple:
label: Multiple Instance label: Multiple Instance
nameNsDescription: Name prefix for each instance nameNsDescription: Name prefix for each instance
@ -462,20 +477,20 @@ harvester:
nameLabel: Name Prefix nameLabel: Name Prefix
host: host:
label: Host Prefix Name label: Host Prefix Name
placeholder: default to the virtual machine name. placeholder: Default to the virtual machine name.
useTemplate: useTemplate:
label: "Use VM Template:" label: "Use the virtual machine template:"
template: template:
label: Template label: Template
version: version:
label: Version label: Version
console: console:
novnc: Open in Web VNC novnc: Open in WebVNC
serial: Open in Serial Console serial: Open in Serial Console
promptRemove: promptRemove:
title: 'Select the volume you want to delete:' title: 'Select the volume you want to delete:'
deleteAll: Delete All deleteAll: Delete All
tips: "Warn: The snapshots of vm will be deleted with VM and the snapshots of volume will be deleted with volume." tips: "Warn: The snapshots of the virtual machine will be deleted with virtual machine and the snapshots of volume will be deleted with volume."
unplug: unplug:
title: 'Are you sure that you want to detach volume {name} ?' title: 'Are you sure that you want to detach volume {name} ?'
actionLabel: Detach actionLabel: Detach
@ -487,6 +502,17 @@ harvester:
other {Start} other {Start}
} the virtual machine now to take effect of the configuration changes. } the virtual machine now to take effect of the configuration changes.
runStrategy: Run Strategy runStrategy: Run Strategy
maintenanceStrategy:
label: Maintenance Strategy
options:
Migrate: Migrate
ShutdownAndRestartAfterEnable: Shutdown and Restart After Enable
ShutdownAndRestartAfterDisable: Shutdown and Restart After Disable
Shutdown: Shutdown
cpuPinning:
label: Enable CPU Pinning
tooltip: Enable CPU Pinning brings better performance and reduce latency for the virtual machine
restartVMMessage: Changing the CPU Pinning setting requires a virtual machine reboot for the change to take effect
restartNow: |- restartNow: |-
{restart, select, {restart, select,
true {Restart} true {Restart}
@ -497,6 +523,10 @@ harvester:
enableUsb: Enable USB Tablet enableUsb: Enable USB Tablet
advancedOptions: advancedOptions:
tpm: Enable TPM tpm: Enable TPM
cpuManager:
prefix: You must enable CPU Manager for at least one node in
middle: 'host page'
suffix: to enable CPU Pinning for VM
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC. usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
sshTitle: Add Public SSH Key sshTitle: Add Public SSH Key
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from. imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
@ -504,7 +534,7 @@ harvester:
secureBoot: Secure Boot secureBoot: Secure Boot
volume: volume:
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order. dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
volumeTip: The VM only contains a cd-rom volume. You may want to add additional disk volumes. volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
macTip: "MAC address as seen inside the guest system." macTip: "MAC address as seen inside the guest system."
volumeUpdate: 'Set volume { name } successfully' volumeUpdate: 'Set volume { name } successfully'
type: Type type: Type
@ -516,7 +546,7 @@ harvester:
dockerImage: Docker Image dockerImage: Docker Image
addVolume: Add Volume addVolume: Add Volume
addExistingVolume: Add Existing Volume addExistingVolume: Add Existing Volume
addVmImage: Add VM Image addVmImage: Add a Virtual Machine Image
addContainer: Add Container addContainer: Add Container
setFirst: Set as root volume setFirst: Set as root volume
saveVolume: Update Volume saveVolume: Update Volume
@ -546,23 +576,23 @@ harvester:
network: network:
label: Network Data Template label: Network Data Template
title: "Network Data:" title: "Network Data:"
tip: "The network-data configuration allows you to customize the instances 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>" tip: "The network-data configuration allows you to customize the instances 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>"
scheduling: scheduling:
affinity: affinity:
anyNode: 'Run VM on any available node' anyNode: 'Run virtual machine on any available node'
schedulingRules: 'Run VM on node(s) matching scheduling rules' schedulingRules: 'Run virtual machine on node(s) matching scheduling rules'
specificNode: Run VM on specific node - (Live migration is not supported) specificNode: Run virtual machine on specific node - (Live migration is not supported)
networkNotSupport: Network not support schedule networkNotSupport: Network not support schedule
accessCredentials: accessCredentials:
resetPwd: resetPwd:
label: Add Basic Auth label: Add Basic Auth
injectSSH: injectSSH:
label: Add SSHKey label: Add SSH Key
users: Select Users users: Select Users
addUser: Add User addUser: Add User
tips: qemu-guest-agent must be installed to enable Access Credentials, the VM should be restarted after credentials added. Need to enter the VM to edit password or remove SSH-Key after deleting the credentials. tips: qemu-guest-agent must be installed to enable the accessing of credentials. The virtual machine needs to be restarted after credentials added. You need to be in the virtual machine to edit your password or remove an SSH-Key after deleting the credentials.
userTips: The user to be added must already exist; otherwise, the credentials will not take effect. userTips: The user to be added must already exist; otherwise, the credentials will not take effect.
duplicatedUser: User already exist. duplicatedUser: User already exists.
invalidUser: Invalid Username. invalidUser: Invalid Username.
input: input:
name: Name name: Name
@ -594,7 +624,7 @@ harvester:
monitor: Monitor Data monitor: Monitor Data
keypairs: SSH Keys keypairs: SSH Keys
cloudConfig: Cloud Config cloudConfig: Cloud Config
metrics: VM Metrics metrics: Virtual Machine Metrics
details: details:
title: title:
vmDetails: Virtual Machine Details vmDetails: Virtual Machine Details
@ -602,6 +632,7 @@ harvester:
services: Services services: Services
users: Logged in users users: Logged in users
name: Name name: Name
totalSnapshotQuota: Total Snapshot Quota
namespace: Namespace namespace: Namespace
created: Created created: Created
hostname: Hostname hostname: Hostname
@ -623,7 +654,7 @@ harvester:
flavor: Flavor flavor: Flavor
tolerations: Tolerations tolerations: Tolerations
dedicatedResources: Dedicated Resources dedicatedResources: Dedicated Resources
down: VM not running down: Virtual machine not running
affinityRules: Affinity Rules affinityRules: Affinity Rules
sourceNode: Source Node sourceNode: Source Node
targetNode: Target Node targetNode: Target Node
@ -636,7 +667,7 @@ harvester:
from: Generated from from: Generated from
down: No events in the past hour down: No events in the past hour
console: console:
down: This Virtual Machine is down. Please start it to access its console. down: This virtual machine is down. Please start it to access its console.
shortcutKeys: Shortcut Keys shortcutKeys: Shortcut Keys
customShortcutKeys: Custom Shortcut Keys customShortcutKeys: Custom Shortcut Keys
management: Management Shortcut Keys management: Management Shortcut Keys
@ -644,13 +675,13 @@ harvester:
start: Record start: Record
recording: Recording recording: Recording
stop: Stop Recording stop: Stop Recording
tips: Pressing the record button will capture your keyboard inputs. tips: Press the record button to capture your keyboard inputs.
send: Send send: Send
preferredKeys: Preferred Custom Shortcut Keys preferredKeys: Preferred Custom Shortcut Keys
terminationGracePeriodSeconds: terminationGracePeriodSeconds:
label: Termination Grace Period label: Termination Grace Period
affinity: affinity:
thisPodNamespace: This VM's namespace thisPodNamespace: This virtual machine's namespace
matchExpressions: matchExpressions:
inNamespaces: "Workloads in these namespaces" inNamespaces: "Workloads in these namespaces"
namespaces: namespaces:
@ -668,7 +699,7 @@ harvester:
kind: Kind kind: Kind
sourceOptions: sourceOptions:
new: New new: New
vmImage: VM Image vmImage: Virtual Machine Image
image: Image image: Image
frontend: Frontend frontend: Frontend
blockdev: Block Device blockdev: Block Device
@ -680,7 +711,7 @@ harvester:
lastBackupAt: Last Backup At lastBackupAt: Last Backup At
replicasNumber: Replicas Number replicasNumber: Replicas Number
promptRemove: promptRemove:
tips: "Warn: The snapshots of volume will be deleted with volume." tips: "Warn: The volume's snapshots will be deleted with this volume."
externalLink: externalLink:
tips: Check volume details tips: Check volume details
rebuildingMessage: 'Rebuilding: {percentage}%' rebuildingMessage: 'Rebuilding: {percentage}%'
@ -691,7 +722,8 @@ harvester:
basics: Basics basics: Basics
url: URL url: URL
size: Size size: Size
urlTip: 'supports the <code>raw</code> and <code>qcow2</code> image formats which are supported by <a href="https://www.qemu.org/docs/master/system/images.html#disk-image-file-formats" target="_blank">qemu</a>. Bootable ISO images can also be used and are treated like <code>raw</code> images.' virtualSize: Virtual Size
urlTip: 'Supports the <code>raw</code> and <code>qcow2</code> image formats which are supported by <a href="https://www.qemu.org/docs/master/system/images.html#disk-image-file-formats" target="_blank">qemu</a>. Bootable ISO images can also be used and are treated like <code>raw</code> images.'
fileName: File Name fileName: File Name
uploadFile: Upload File uploadFile: Upload File
source: Source source: Source
@ -716,10 +748,10 @@ harvester:
tips: tips:
notExistImage: notExistImage:
title: Image {name} does not exist! title: Image {name} does not exist!
message: Please select a new Image. message: Please select a new image.
notExistNode: notExistNode:
title: Node {name} does not exist! title: Node {name} does not exist!
message: Please select a new Node. message: Please select a new node.
upgradePage: upgradePage:
upgradeApp: Upgrade Software upgradeApp: Upgrade Software
@ -736,8 +768,8 @@ harvester:
selectExisting: Select Existing Image selectExisting: Select Existing Image
createRepository: Creating Upgrade Repository createRepository: Creating Upgrade Repository
succeeded: Succeeded succeeded: Succeeded
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Note</a>. releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>.
checkReady: I have read and understood the upgrade content related to this Harvester version. checkReady: I have read and understood the upgrade instructions related to this Harvester version.
pending: Pending pending: Pending
repoInfo: repoInfo:
upgradeStatus: Upgrade Status upgradeStatus: Upgrade Status
@ -747,29 +779,29 @@ harvester:
harvesterChart: Harvester Chart harvesterChart: Harvester Chart
success: Success success: Success
fail: Fail fail: Fail
ongoing: on-going ongoing: On-going
downloadLog: Download Log downloadLog: Download Log
logStatus: Log Download Status logStatus: Log Download Status
dismissMessage: Dismiss it dismissMessage: Dismiss it
upgradeInfo: upgradeInfo:
warning: WARNING warning: WARNING
doc: Before you upgrade to the newer Harvester version, you must perform the required <a href="https://docs.harvesterhci.io/v1.0/upgrade/automatic/" target="_blank"> pre-upgrade checks </a>for your cluster. Complete only those tasks that apply to your environment. doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
tip: Failure to perform these checks may result in a failed upgrade or hitting known issues that require a manual workaround fix. tip: Unmet system requirements and incorrectly performed procedures may cause complete upgrade failure and other issues that require manual workarounds.
moreNotes: For more details about the release notes, please visit - moreNotes: For more details about the release notes, please visit -
backup: backup:
label: VM Backups label: Virtual Machine Backups
createText: Restore Backup createText: Restore Backup
title: Restore Virtual Machine title: Restore Virtual Machine
backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported. backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported.
message: message:
noSetting: noSetting:
prefix: You must configure the backup target in prefix: You must configure the backup target
middle: 'setting' middle: 'setting'
suffix: before creating a new backup. suffix: before creating a new backup.
errorTip: errorTip:
prefix: Backup Target value in prefix: Backup target value in
middle: Setting middle: setting
suffix: "is invalid, error: " suffix: "is invalid, error: "
viewSetting: viewSetting:
prefix: Click prefix: Click
@ -804,21 +836,21 @@ harvester:
complete: Restore completed complete: Restore completed
network: network:
label: VM Networks label: Virtual Machine Networks
tabs: tabs:
basics: Basics basics: Basics
layer3Network: Route layer3Network: Route
clusterNetwork: clusterNetwork:
label: Cluster Network label: Cluster Network
create: Create a New Cluster Network create: Create a new cluster network
toolTip: Define your custom cluster scope network name toolTip: Define your custom cluster scope network name
createPlaceholder: Input a new Cluster Network name createPlaceholder: Input a new cluster network name
selectOrCreatePlaceholder: Select or Create a new Cluster Network selectOrCreatePlaceholder: Select or create a new cluster network
selectPlaceholder: Select a Cluster Network selectPlaceholder: Select a cluster network
layer3Network: layer3Network:
mode: mode:
label: Mode label: Mode
auto: Auto(DHCP) auto: Auto (DHCP)
manual: Manual manual: Manual
serverIPAddr: serverIPAddr:
label: DHCP Server IP label: DHCP Server IP
@ -842,9 +874,9 @@ harvester:
validation: validation:
physicalNIC: DefaultPhysicalNIC physicalNIC: DefaultPhysicalNIC
placeholder: placeholder:
accessKeyId: specify your access key id accessKeyId: Specify your access key ID
secretAccessKey: specify your secret access key secretAccessKey: Specify your secret access key
cert: upload a self-signed SSL certificate cert: Upload a self-signed SSL certificate
vlanChangeTip: The newly modified default network interface only applies to newly added nodes, not existing ones. vlanChangeTip: The newly modified default network interface only applies to newly added nodes, not existing ones.
defaultPhysicalNIC: Default Network Interface defaultPhysicalNIC: Default Network Interface
percentTip: The value in parentheses represents the distribution percentage of the network interface on all hosts. If an interface less than 100% is selected, the user needs to manually specify the network interface on the host where the vlan network configuration fails. percentTip: The value in parentheses represents the distribution percentage of the network interface on all hosts. If an interface less than 100% is selected, the user needs to manually specify the network interface on the host where the vlan network configuration fails.
@ -869,10 +901,11 @@ harvester:
placeholder: e.g. 172.16.0.1/32 placeholder: e.g. 172.16.0.1/32
invalid: '"Exclude list" is invalid.' invalid: '"Exclude list" is invalid.'
addIp: Add Exclude IP addIp: Add Exclude IP
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all VMs before applying this setting. <br/> Users have to ensure the Cluster Network is configured and VLAN Config will cover all nodes and ensure the network connectivity is working and expected in all nodes.' warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
tip: 'IP Range should be in IPV4 format. <code>Number of IPs Required = Number of Nodes * 4 + Number of Disks * 2 + Number of Images to Download/Upload </code>. See <a href="https://docs.harvesterhci.io/v1.2/advanced/storagenetwork#configuration-example" target="_blank">doc</a> for more details.' tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 4 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
vmForceDeletionPolicy: vmForceDeletionPolicy:
period: Period period: Period
ratio : Ratio
autoRotateRKE2Certs: autoRotateRKE2Certs:
expiringInHours: Expiring in expiringInHours: Expiring in
httpProxy: httpProxy:
@ -907,7 +940,7 @@ harvester:
upgrade: upgrade:
selectExitImage: Please select the OS image to upgrade. selectExitImage: Please select the OS image to upgrade.
imageUrl: Please input a valid image url. imageUrl: Please input a valid image URL.
chooseFile: Please select to upload an image. chooseFile: Please select to upload an image.
checksum: Checksum checksum: Checksum
harvesterMonitoring: harvesterMonitoring:
@ -923,12 +956,12 @@ harvester:
retention: How long to retain metrics retention: How long to retain metrics
retentionSize: Maximum size of metrics retentionSize: Maximum size of metrics
clusterRegistrationUrl: clusterRegistrationUrl:
message: To completely unset the imported Harvester cluster, please also remove it on the Rancher dashboard UI via the <code> Virtualization Management </code> page. message: To completely unset the imported Harvester cluster, please also remove it on the Rancher Dashboard UI via the <code> Virtualization Management </code> page.
ntpServers: ntpServers:
isNotIPV4: The address you entered is not IPv4 or host. Please enter a valid IPv4 address or a host address. isNotIPV4: The address you entered is not IPv4 or host. Please enter a valid IPv4 address or a host address.
isDuplicate: There are duplicate NTP server configurations. isDuplicate: There are duplicate NTP server configurations.
cloudTemplate: cloudTemplate:
label: Cloud Config Templates label: Cloud Configuration Templates
templateType: Template Type templateType: Template Type
userData: User Data userData: User Data
networkData: Network Data networkData: Network Data
@ -941,7 +974,7 @@ harvester:
internal: internal:
rancher: rancher:
title: Access Embedded Rancher UI title: Access Embedded Rancher UI
titleDescription: We only support to use the embedded Rancher dashboard for debugging and validation purpose. For Rancher's multi-cluster and multi-tenant integration, please refer to the docs <a target="_blank" href="https://docs.harvesterhci.io/v1.2/rancher/index" rel="noopener noreferrer nofollow">here</a>. titleDescription: You can only use the embedded Rancher UI for debugging and validation purposes. For more information about how Harvester integrates with Rancher, see the <a target="_blank" href="{url}" rel="noopener noreferrer nofollow">documentation</a>.
longhorn: longhorn:
title: Access Embedded Longhorn UI title: Access Embedded Longhorn UI
titleDescription: We only support to use the embedded Longhorn UI for debugging and validation purpose. titleDescription: We only support to use the embedded Longhorn UI for debugging and validation purpose.
@ -961,7 +994,7 @@ harvester:
cidr: cidr:
label: CIDR/IP Range label: CIDR/IP Range
invalid: '"CIDR/IP Range" is invalid.' invalid: '"CIDR/IP Range" is invalid.'
toolTip: "We can apply multiple pools or ranges by seperating them with commas. i.e. 192.168.0.200/30,192.168.0.200/29 or 192.168.0.10-192.168.0.11" toolTip: "We can apply multiple pools or ranges by separating them with commas. For example: 192.168.0.200/30,192.168.0.200/29 or 192.168.0.10-192.168.0.11"
add: add:
label: Add IP Pools label: Add IP Pools
@ -977,15 +1010,15 @@ harvester:
alertmanagerConfig: alertmanagerConfig:
label: Alertmanager Configs label: Alertmanager Configs
diabledMonitoringTips: diabledMonitoringTips:
prefix: 'You must enable' prefix: 'Enable the'
middle: 'Monitoring' middle: 'monitoring'
suffix: 'addon at first.' suffix: 'add-on first.'
diabledAlertingTips: diabledAlertingTips:
prefix: 'You must enable' prefix: 'Enable'
middle: 'Alertmanager' middle: 'Alertmanager'
suffix: 'for configs to take effect.' suffix: 'for configuration to take effect.'
disabledAddon: disabledAddon:
prefix: 'Monitoring Addon is disabled now, click' prefix: 'The monitoring add-on is disabled, click'
middle: 'here' middle: 'here'
suffix: 'to enable it.' suffix: 'to enable it.'
@ -1003,18 +1036,19 @@ harvester:
output: output:
label: Output label: Output
diabledTips: diabledTips:
prefix: 'You must enable' prefix: 'Enable'
middle: 'Logging' middle: 'logging'
suffix: 'for configs to take effect.' suffix: 'for configuration to take effect.'
snapshot: snapshot:
totalSnapshotSize: Total Snapshot Size
label: Volume Snapshots label: Volume Snapshots
targetVolume: Original Volume targetVolume: Original Volume
size: Size size: Size
image: Image image: Image
vmSnapshot: vmSnapshot:
label: VM Snapshots label: Virtual Machine Snapshots
createText: Restore Snapshot createText: Restore Snapshot
snapshot: Snapshot snapshot: Snapshot
@ -1036,7 +1070,7 @@ harvester:
title: Storage Classes title: Storage Classes
customize: customize:
volumeBindingMode: volumeBindingMode:
later: Bind and provision a persistent volume once a VM using the PersistentVolumeClaim is created later: Bind and provision a persistent volume once a virtual machine using the PersistentVolumeClaim is created
parameters: parameters:
numberOfReplicas: numberOfReplicas:
label: Number Of Replicas label: Number Of Replicas
@ -1052,11 +1086,11 @@ harvester:
label: Migratable label: Migratable
allowedTopologies: allowedTopologies:
title: Allowed Topologies title: Allowed Topologies
tooltip: Allowed Topologies helps scheduling VMs on hosts which match all of below expressions. tooltip: Allowed Topologies helps scheduling virtual machines on hosts which match all of below expressions.
vlanConfig: vlanConfig:
title: Network Configs title: Network Configuration
createNetworkConfig: Create Network Config createNetworkConfig: Create Network Configuration
action: action:
migrate: Migrate migrate: Migrate
titles: titles:
@ -1097,39 +1131,39 @@ harvester:
vlanStatus: vlanStatus:
vlanConfig: vlanConfig:
label: Network Config label: Network Configuration
clusterNetwork: clusterNetwork:
title: Cluster Networks/Configs title: Cluster Network Configuration
create: create:
button: button:
label: Create Cluster Network label: Create a Cluster Network
clusterNetwork: There are no network configs defined. clusterNetwork: There are no network configurations defined.
mgmt: mgmt is a built-in cluster management network and does not support any additional network configurations. mgmt: mgmt is a built-in cluster management network and does not support any additional network configurations.
notExist: 'Cluster Network "{ clusterNetwork }" does not exist' notExist: 'Cluster Network "{ clusterNetwork }" does not exist'
notReady: 'Cluster Network "{ clusterNetwork }" is not ready' notReady: 'Cluster Network "{ clusterNetwork }" is not ready'
addons: addons:
descriptions: descriptions:
'harvester-system/vm-import-controller': vm-import-controller is an addon to help migrate VM workloads from other source clusters to an existing Harvester cluster. 'harvester-system/vm-import-controller': vm-import-controller is an add-on to help migrate virtual machine workloads from other source clusters to an existing Harvester cluster.
'harvester-system/pcidevices-controller': pcidevices-controller is an addon to help discover PCI devices for nodes in your cluster and allow users to prepare devices for PCI Passthrough, for use with Harvester VM and guest Clusters. 'harvester-system/pcidevices-controller': pcidevices-controller is an add-on to help discover PCI devices for nodes in your cluster and allow users to prepare devices for PCI Passthrough, for use with Harvester virtual machine and guest clusters.
'cattle-logging-system/rancher-logging': rancher-logging is an addon to collect versatile logs, events and audits from the Harvester cluster and route them to many kinds of servers based on flows. 'cattle-logging-system/rancher-logging': rancher-logging is an add-on to collect versatile logs, events, and audits from the Harvester cluster and route them to many kinds of servers based on flows.
'harvester-system/rancher-vcluster': rancher-vcluster deploys a vcluster with rancher installed. 'harvester-system/rancher-vcluster': rancher-vcluster deploys a virtual cluster (vcluster) with Rancher installed.
'cattle-monitoring-system/rancher-monitoring': rancher-monitoring is an addon to collect Harvester cluster and VM metrics, view them on the embedded dashboard, and send alert(s) to remote servers. 'cattle-monitoring-system/rancher-monitoring': rancher-monitoring is an add-on that collects Harvester cluster and virtual machine metrics and allows you to view the metrics on an embedded dashboard and send alert(s) to remote servers.
'vm-import-controller': vm-import-controller is an addon to help migrate VM workloads from other source clusters to an existing Harvester cluster. 'vm-import-controller': vm-import-controller is an add-on to help migrate virtual machine workloads from other source clusters to an existing Harvester cluster.
'pcidevices-controller': pcidevices-controller is an addon to help discover PCI devices for nodes in your cluster and allow users to prepare devices for PCI Passthrough, for use with Harvester VM and guest Clusters. 'pcidevices-controller': pcidevices-controller is an add-on to help discover PCI devices for nodes in your cluster and allow users to prepare devices for PCI Passthrough, for use with Harvester virtual machines and guest clusters.
'nvidia-driver-toolkit': 'nvidia-driver-toolkit is an addon to enable vGPU devices and assign them to Harvester VMs.' 'nvidia-driver-toolkit': 'nvidia-driver-toolkit is an add-on to enable vGPU devices and assign them to Harvester virtual machines.'
'rancher-logging': rancher-logging is an addon to collect versatile logs, events and audits from the Harvester cluster and route them to many kinds of servers based on flows. 'rancher-logging': rancher-logging is an add-on to collect versatile logs, events, and audits from the Harvester cluster and route them to many kinds of servers based on flows.
'rancher-monitoring': rancher-monitoring is an addon to collect Harvester cluster and VM metrics, view them on the embedded dashboard, and send alert(s) to remote servers. 'rancher-monitoring': rancher-monitoring is an add-on to collect Harvester cluster and virtual machine metrics, view them on the embedded dashboard, and send alert(s) to remote servers.
'rancher-vcluster': rancher-vcluster deploys a vcluster with rancher installed. 'rancher-vcluster': rancher-vcluster deploys a virtual cluster (vcluster) with Rancher installed.
'harvester-seeder': harvester-seeder is an addon that uses ipmi and redfish to discover hardware information and perform out-of-band operations. 'harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
'harvester-system/harvester-seeder': harvester-seeder is an addon that uses ipmi and redfish to discover hardware information and perform out-of-band operations. 'harvester-system/harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
vmImport: vmImport:
titles: titles:
basic: Basic basic: Basic
pvc: Volume pvc: Volume
rancherVcluster: rancherVcluster:
accessRancher: Access Rancher Dashboard accessRancher: Access the Rancher Dashboard
hostname: Hostname hostname: Hostname
rancherVersion: Rancher Version rancherVersion: Rancher Version
password: Bootstrap Password password: Bootstrap Password
@ -1190,12 +1224,12 @@ harvester:
label: Backend Servers label: Backend Servers
healthCheck: healthCheck:
warning: warning:
portInUse: Warning, Backend Port {port} is in use in Health Check settings; in case of updating the port, update the Health Check settings accordingly. portInUse: Warning, the Backend Port {port} is in use in Health Check settings. If you need to update the port, update the Health Check settings accordingly.
ipPool: ipPool:
label: IP Pools label: IP Pools
network: network:
label: VM Network label: Virtual Machine Network
tabs: tabs:
range: Range range: Range
scope: Scope scope: Scope
@ -1231,7 +1265,7 @@ harvester:
label: Health Check Port label: Health Check Port
healthCheckSuccessThreshold: healthCheckSuccessThreshold:
label: Health Check Success Threshold label: Health Check Success Threshold
description: If the number of times the prober continuously detects an address successfully reaches the success threshold, then the backend server can start to forward traffic. description: If the number of times the probe continuously detects an address successfully reaches the success threshold, then the backend server can start to forward traffic.
healthCheckFailureThreshold: healthCheckFailureThreshold:
label: Health Check Failure Threshold label: Health Check Failure Threshold
description: The backend server will stop forwarding traffic if the number of health check failures reaches the failure threshold. description: The backend server will stop forwarding traffic if the number of health check failures reaches the failure threshold.
@ -1256,22 +1290,22 @@ harvester:
sriovgpu: sriovgpu:
label: SR-IOV GPU Devices label: SR-IOV GPU Devices
nodeName: Node nodeName: Node
numVFs: Number Of Virtual Functions numVFs: Number of Virtual Functions
vfAddresses: Virtual Functions Addresses vfAddresses: Virtual Functions Addresses
vGpuDevices: vGPU Devices vGpuDevices: vGPU Devices
showMore: Show More showMore: Show More
parentSriov: Filter By Parent SR-IOV GPU parentSriov: Filter By Parent SR-IOV GPU
noPermission: Please contact system admin to add Harvester addons first noPermission: Please contact your system admiistrator to add Harvester add-ons first.
goSetting: goSetting:
prefix: The nvidia-driver-toolkit addon is not enabled, click prefix: The nvidia-driver-toolkit add-on is not enabled, click
middle: here middle: here
suffix: to enable it to manage your SR-IOV GPU devices. suffix: to enable it to manage your SR-IOV GPU devices.
vgpu: vgpu:
label: vGPU Devices label: vGPU Devices
noPermission: Please contact system admin to add Harvester addons first noPermission: Please contact system administrator to add Harvester add-ons first.
goSetting: goSetting:
prefix: The nvidia-driver-toolkit addon is not enabled, click prefix: The nvidia-driver-toolkit add-on is not enabled, click
middle: here middle: here
suffix: to enable it to manage your vGPU devices. suffix: to enable it to manage your vGPU devices.
enableGroup: Enable Group enableGroup: Enable Group
@ -1282,7 +1316,7 @@ harvester:
available: Available Devices available: Available Devices
compatibleNodes: Compatible Nodes compatibleNodes: Compatible Nodes
impossibleSelection: 'There are no hosts with all of the selected devices.' impossibleSelection: 'There are no hosts with all of the selected devices.'
howToUseDevice: 'Use the table below to enable vGPU devices you want to use in this VM.' howToUseDevice: 'Use the table below to enable vGPU devices you want to use in this virtual machine.'
deviceInTheSameHost: 'You can only select devices on the same host.' deviceInTheSameHost: 'You can only select devices on the same host.'
harvesterVlanConfigMigrateDialog: harvesterVlanConfigMigrateDialog:
@ -1293,11 +1327,11 @@ harvester:
seeder: seeder:
banner: banner:
enable: enable:
prefix: Addon "harvester-seeder" is disabled now, prefix: The "harvester-seeder" add-on is disabled.
middle: click here middle: Click here
suffix: to enable it. suffix: to enable it.
noAccess: Please contact system admin to enable the Out-of-Band Access first. noAccess: Please contact your system administrator to enable the Out-of-Band Access first.
noAddon: Addon "harvester-seeder" is not exist, please check if it is installed. noAddon: The "harvester-seeder" add-on does not exist, please check if it is installed.
noInventory: Waiting for "inventories.metal.harvesterhci.io" to be ready. noInventory: Waiting for "inventories.metal.harvesterhci.io" to be ready.
inventory: inventory:
host: host:
@ -1318,10 +1352,10 @@ harvester:
label: Polling Interval label: Polling Interval
affinity: affinity:
thisPodNamespace: This VM's namespace thisPodNamespace: This virtual machine's namespace
matchExpressions: matchExpressions:
inNamespaces: "Workloads in these namespaces" inNamespaces: "Workloads in these namespaces"
vmAffinityTitle: VM Scheduling vmAffinityTitle: Virtual Machine Scheduling
namespaces: namespaces:
placeholder: e.g. default,system,base placeholder: e.g. default,system,base
label: Namespaces label: Namespaces
@ -1330,35 +1364,40 @@ harvester:
placeholder: 'topology.kubernetes.io/zone' placeholder: 'topology.kubernetes.io/zone'
advancedSettings: advancedSettings:
technicalPreview: 'Technical Previews allow users to test and evaluate early-access functionality prior to official supported releases'
descriptions: descriptions:
'harv-vlan': Default Network Interface name of the VLAN network. 'harv-vlan': Default Network Interface name of the VLAN network.
'harv-backup-target': Custom backup target to store VM backups. 'harv-backup-target': Custom backup target to store virtual machine backups.
'branding': Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme. 'branding': Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos, and color scheme.
'harv-csi-driver-config': Configure additional information for csi drivers. 'harv-csi-driver-config': Configure additional information for CSI drivers.
'harv-containerd-registry': Containerd Registry Configuration to connect private registries. 'harv-containerd-registry': Containerd Registry Configuration to connect private registries.
'harv-log-level': Configure Harvester server log level. Default to info. 'harv-log-level': Configure Harvester server log level. Defaults to Info.
'harv-server-version': Harvester server version. 'harv-server-version': Harvester server version.
'harv-upgrade-checker-enabled': Specify whether to enable Harvester upgrade check or not. Default is true. 'harv-upgrade-checker-enabled': Specifies whether to enable Harvester upgrade check or not. Default is True.
'harv-upgrade-checker-url': Default Harvester upgrade check url. Only used when the <code>upgrade-checker-enabled</code> is equal to true. 'harv-upgrade-checker-url': Default Harvester upgrade check url. Only used when the <code>upgrade-checker-enabled</code> is equal to True.
'harv-ui-source': Config how to load the UI source. 'harv-ui-source': Configure how to load the UI source.
'harv-ui-index': 'HTML index location for the UI.' 'harv-ui-index': 'HTML index location for the UI.'
'harv-ui-plugin-index': 'JS index location for the harvester plugin UI.' 'harv-ui-plugin-index': 'JS index location for the Harvester plugin UI.'
'harv-cluster-registration-url': Registration URL for mutil-cluster management. 'harv-cluster-registration-url': Registration URL for multi-cluster management.
'harv-http-proxy': 'HTTP proxy for Harvester to access external services.' 'harv-http-proxy': 'HTTP proxy for Harvester to access external services.'
'harv-additional-ca': 'Custom CA root certificates for TLS validation.' 'harv-additional-ca': 'Custom CA root certificates for TLS validation.'
'harv-overcommit-config': 'Resource overcommit configuration.' 'harv-overcommit-config': 'Resource overcommit configuration.'
'harv-support-bundle-timeout': 'Support Bundle timeout config in minutes, use 0 to disable the timeout.' 'harv-support-bundle-timeout': 'Support bundle timeout configuration in minutes, use 0 to disable the timeout.'
'harv-support-bundle-expiration': 'Support Bundle expiration config in minutes.' 'harv-support-bundle-expiration': 'Support bundle expiration configuration in minutes.'
'harv-vm-force-reset-policy': Config the force-reset action when a VM is stuck on a node that is down. 'harv-support-bundle-node-collection-timeout': 'Support bundle node collection timeout configuration in minutes.'
'harv-vm-force-reset-policy': Configuration for the force-reset action when a virtual machine is stuck on a node that is down.
'harv-ssl-parameters': Custom SSL Parameters for TLS validation. 'harv-ssl-parameters': Custom SSL Parameters for TLS validation.
'harv-storage-network': 'Longhorn storage-network setting.' 'harv-storage-network': 'Longhorn storage-network setting.'
'harv-support-bundle-namespaces': Specify resources in other namespaces to be collected by the support package. 'harv-support-bundle-namespaces': Specify resources in other namespaces to be collected by the support package.
'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as VM storage. 'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as virtual machine storage.
'harv-support-bundle-image': Support bundle image configuration. Find different versions in <a href="https://hub.docker.com/r/rancher/support-bundle-kit/tags" target="_blank">rancher/support-bundle-kit</a>. 'harv-support-bundle-image': Support bundle image configuration. Find different versions in <a href="https://hub.docker.com/r/rancher/support-bundle-kit/tags" target="_blank">rancher/support-bundle-kit</a>.
'harv-release-download-url': This setting allows you to configure the <code>upgrade release download</code> URL address. Harvester will get the ISO URL and checksum value from the (<code>$URL</code>/<code>$VERSION</code>/version.yaml) file hosted by the configured URL. 'harv-release-download-url': This setting allows you to configure the <code>upgrade release download</code> URL address. Harvester will get the ISO URL and checksum value from the (<code>$URL</code>/<code>$VERSION</code>/version.yaml) file hosted by the configured URL.
'harv-default-vm-termination-grace-period-seconds': Config the VM termination grace period for VM stop. 'harv-default-vm-termination-grace-period-seconds': Configure the virtual machine termination grace period for virtual machine stop.
'harv-ntp-servers': Configure NTP server. You can configure multiple IPv4 addresses or host addresses. 'harv-ntp-servers': Configure NTP server. You can configure multiple IPv4 addresses or host addresses.
'harv-auto-rotate-rke2-certs': The certificate rotation mechanism relies on Rancher. Harvester will automatically update certificates generation to trigger rotation. 'harv-auto-rotate-rke2-certs': The certificate rotation mechanism relies on Rancher. Harvester will automatically update certificates generation to trigger rotation.
'harv-kubeconfig-default-token-ttl-minutes': 'TTL (in minutes) applied on Harvester administration kubeconfig files. Default is 0, which means to never expire.'
'harv-longhorn-v2-data-engine-enabled': 'Enable the Longhorn V2 data engine. Default is false. <ul><li>Changing this setting will restart RKE2 on all nodes. This will not affect running VM workloads.</li><li>If you see "not enough hugepages-2Mi capacity" errors when enabling this setting, wait a minute for the error to clear. If the error remains, reboot the affected node.</li></ul>'
'harv-additional-guest-memory-overhead-ratio': 'The ratio for kubevirt to adjust the VM overhead memory. The value could be zero, empty value or floating number between 1.0 and 10.0, default to 1.5.'
typeLabel: typeLabel:
kubevirt.io.virtualmachine: |- kubevirt.io.virtualmachine: |-
@ -1383,8 +1422,8 @@ typeLabel:
} }
harvesterhci.io.networkattachmentdefinition: |- harvesterhci.io.networkattachmentdefinition: |-
{count, plural, {count, plural,
one { VM Network } one { Virtual Machines Network }
other { VM Networks } other { Virtual Machines Networks }
} }
harvesterhci.io.volume: |- harvesterhci.io.volume: |-
{count, plural, {count, plural,
@ -1408,13 +1447,13 @@ typeLabel:
} }
harvesterhci.io.virtualmachinebackup: |- harvesterhci.io.virtualmachinebackup: |-
{count, plural, {count, plural,
one { VM Backup } one { Virtual Machines Backup }
other { VM Backups } other { Virtual Machines Backups }
} }
harvesterhci.io.cloudtemplate: |- harvesterhci.io.cloudtemplate: |-
{count, plural, {count, plural,
one { Cloud Config Template } one { Cloud Configuration Template }
other { Cloud Config Templates } other { Cloud Configuration Templates }
} }
harvesterhci.io.volumesnapshot: |- harvesterhci.io.volumesnapshot: |-
{count, plural, {count, plural,
@ -1423,18 +1462,18 @@ typeLabel:
} }
harvesterhci.io.vmsnapshot: |- harvesterhci.io.vmsnapshot: |-
{count, plural, {count, plural,
one { VM Snapshot } one { Virtual Machines Snapshot }
other { VM Snapshots } other { Virtual Machines Snapshots }
} }
network.harvesterhci.io.vlanconfig: |- network.harvesterhci.io.vlanconfig: |-
{count, plural, {count, plural,
one { Network Config } one { Network Configuration }
other { Network Configs } other { Network Configurations }
} }
harvesterhci.io.monitoring.alertmanagerconfig: |- harvesterhci.io.monitoring.alertmanagerconfig: |-
{count, plural, {count, plural,
one { Alertmanager Config } one { Alertmanager Configuration }
other { Alertmanager Configs } other { Alertmanager Configurations }
} }
harvesterhci.io.logging.clusterflow: |- harvesterhci.io.logging.clusterflow: |-
{count, plural, {count, plural,
@ -1514,30 +1553,3 @@ typeLabel:
one { IP Pool } one { IP Pool }
other { IP Pools } other { IP Pools }
} }
harvesterManager:
manage: Manage
cluster:
label: Harvester Clusters
none: There are no Harvester Clusters
learnMore: Learn more about Harvester from the <a target="_blank" href="https://harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Web Site</a> or read the the <a target="_blank" href="https://docs.harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Docs</a>
description: Harvester is a modern Hyperconverged infrastructure (HCI) solution built for bare metal servers using enterprise-grade open source technologies including Kubernetes, Kubevirt and Longhorn.
plugins:
loadError: Error loading harvester plugin
rke:
templateError: Incorrect template format
affinity:
thisPodNamespace: This VM's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
vmAffinityTitle: VM Scheduling
namespaces:
placeholder: e.g. default,system,base
label: Namespaces
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
vGpu:
title: VGPUs
label: VGPU type
placeholder: 'Please select a VGPU'

File diff suppressed because it is too large Load Diff

View File

@ -134,15 +134,16 @@ export default {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const hash = { const hash = {
vms: this.fetchClusterResources(HCI.VM), vms: this.fetchClusterResources(HCI.VM),
nodes: this.fetchClusterResources(NODE), nodes: this.fetchClusterResources(NODE),
events: this.fetchClusterResources(EVENT), events: this.fetchClusterResources(EVENT),
metricNodes: this.fetchClusterResources(METRIC.NODE), metricNodes: this.fetchClusterResources(METRIC.NODE),
settings: this.fetchClusterResources(HCI.SETTING), settings: this.fetchClusterResources(HCI.SETTING),
services: this.fetchClusterResources(SERVICE), services: this.fetchClusterResources(SERVICE),
metric: this.fetchClusterResources(METRIC.NODE), metric: this.fetchClusterResources(METRIC.NODE),
longhornNode: this.fetchClusterResources(LONGHORN.NODES) || [], longhornNodes: this.fetchClusterResources(LONGHORN.NODES),
_pods: this.$store.dispatch('harvester/findAll', { type: POD }), longhornSettings: this.fetchClusterResources(LONGHORN.SETTINGS),
_pods: this.$store.dispatch('harvester/findAll', { type: POD }),
}; };
(this.accessibleResources || []).map((a) => { (this.accessibleResources || []).map((a) => {
@ -155,6 +156,10 @@ export default {
hash.addons = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }); hash.addons = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS });
} }
if (this.$store.getters[`${ inStore }/schemaFor`](LONGHORN.NODES)) {
this.hasLonghornSchema = true;
}
const res = await allHash(hash); const res = await allHash(hash);
for ( const k in res ) { for ( const k in res ) {
@ -226,6 +231,7 @@ export default {
showClusterMetrics: false, showClusterMetrics: false,
showVmMetrics: false, showVmMetrics: false,
enabledMonitoringAddon: false, enabledMonitoringAddon: false,
hasLonghornSchema: false,
}; };
}, },
@ -305,8 +311,7 @@ export default {
currentVersion() { currentVersion() {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const settings = this.$store.getters[`${ inStore }/all`](HCI.SETTING); const setting = this.$store.getters[`${ inStore }/byId`](HCI.SETTING, 'server-version');
const setting = settings.find( S => S.id === 'server-version');
return setting?.value || setting?.default; return setting?.value || setting?.default;
}, },
@ -364,53 +369,46 @@ export default {
return out; return out;
}, },
storageUsage() { storageStats() {
const inStore = this.$store.getters['currentProduct'].inStore; const storageOverProvisioningPercentageSetting = this.longhornSettings.find(s => s.id === 'longhorn-system/storage-over-provisioning-percentage');
const longhornNodes = this.$store.getters[`${ inStore }/all`](LONGHORN.NODES) || []; const stats = this.longhornNodes.reduce((total, node) => {
return longhornNodes.filter(node => node.spec?.allowScheduling).reduce((total, node) => {
return total + node.used;
}, 0);
},
storageReservedTotal() {
let out = 0;
(this.longhornNode || []).filter(node => node.spec?.allowScheduling).forEach((node) => {
const disks = node?.spec?.disks || {}; const disks = node?.spec?.disks || {};
Object.values(disks).map((disk) => {
if (disk.allowScheduling) {
out += disk.storageReserved;
}
});
});
return out;
},
storageTotal() {
let out = 0;
(this.longhornNode || []).forEach((node) => {
const diskStatus = node?.status?.diskStatus || {}; const diskStatus = node?.status?.diskStatus || {};
Object.values(diskStatus).map((disk) => { total.used += node?.spec?.allowScheduling ? node.used : 0;
if (disk?.storageMaximum) {
out += disk.storageMaximum; Object.keys(disks).map((key) => {
} total.scheduled += node?.spec?.allowScheduling ? (diskStatus[key]?.storageScheduled || 0) : 0;
total.reserved += disks[key]?.storageReserved || 0;
}); });
Object.values(diskStatus).map((diskStat) => {
total.maximum += diskStat?.storageMaximum || 0;
});
return total;
}, {
used: 0,
scheduled: 0,
maximum: 0,
reserved: 0,
total: 0
}); });
return out; stats.total = ((stats.maximum - stats.reserved) * Number(storageOverProvisioningPercentageSetting?.value ?? 0)) / 100;
return stats;
}, },
storageUsed() { storageUsed() {
return this.createMemoryValues(this.storageTotal, this.storageUsage); const stats = this.storageStats;
return this.createMemoryValues(stats.maximum, stats.used);
}, },
storageReserved() { storageAllocated() {
return this.createMemoryValues(this.storageTotal, this.storageReservedTotal); const stats = this.storageStats;
return this.createMemoryValues(stats.total, stats.scheduled);
}, },
vmEvents() { vmEvents() {
@ -644,7 +642,7 @@ export default {
<div <div
class="hardware-resource-gauges" class="hardware-resource-gauges"
:class="{ :class="{
live: !storageTotal, live: !hasLonghornSchema,
}" }"
> >
<HardwareResourceGauge <HardwareResourceGauge
@ -658,10 +656,11 @@ export default {
:used="ramUsed" :used="ramUsed"
/> />
<HardwareResourceGauge <HardwareResourceGauge
v-if="storageTotal" v-if="hasLonghornSchema"
:name="t('harvester.dashboard.hardwareResourceGauge.storage')" :name="t('harvester.dashboard.hardwareResourceGauge.storage')"
:used="storageUsed" :used="storageUsed"
:reserved="storageReserved" :reserved="storageAllocated"
:reserved-title="t('clusterIndexPage.hardwareResourceGauge.allocated')"
/> />
</div> </div>
</template> </template>

View File

@ -8,6 +8,7 @@ import {
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import metricPoller from '@shell/mixins/metric-poller'; import metricPoller from '@shell/mixins/metric-poller';
import { HCI } from '../types'; import { HCI } from '../types';
import { DOC_LINKS } from '../config/doc-links';
const schema = { const schema = {
id: HCI.HOST, id: HCI.HOST,
@ -91,6 +92,7 @@ export default {
value: 'internalIp', value: 'internalIp',
formatter: 'CopyToClipboard', formatter: 'CopyToClipboard',
sort: ['internalIp'], sort: ['internalIp'],
align: 'center',
}, },
]; ];
@ -98,7 +100,7 @@ export default {
const metricCol = [ const metricCol = [
{ {
name: 'cpu', name: 'cpu',
labelKey: 'tableHeaders.cpu', labelKey: 'node.detail.glance.consumptionGauge.cpu',
value: 'id', value: 'id',
formatter: 'HarvesterCPUUsed', formatter: 'HarvesterCPUUsed',
formatterOpts: { showUsed: true }, formatterOpts: { showUsed: true },
@ -121,12 +123,23 @@ export default {
labelKey: 'tableHeaders.storage', labelKey: 'tableHeaders.storage',
value: 'id', value: 'id',
formatter: 'HarvesterStorageUsed', formatter: 'HarvesterStorageUsed',
formatterOpts: { showReserved: true }, formatterOpts: { showAllocated: true },
}; };
out.splice(-1, 0, storageHeader); out.splice(-1, 0, storageHeader);
} }
out.push({
name: 'cpuManager',
labelKey: 'harvester.tableHeaders.cpuManager',
value: 'id',
formatter: 'HarvesterCPUPinning',
formatterOpts: { rows: this.rows },
width: 150,
align: 'center',
});
if (this.hasLonghornSchema) { if (this.hasLonghornSchema) {
out.push({ out.push({
name: 'diskState', name: 'diskState',
@ -143,7 +156,7 @@ export default {
name: 'console', name: 'console',
label: ' ', label: ' ',
align: 'right', align: 'right',
width: 65, width: 80,
}); });
return out; return out;
@ -151,6 +164,10 @@ export default {
schema() { schema() {
return schema; return schema;
},
consoleDocLink() {
return DOC_LINKS.CONSOLE_URL;
} }
}, },
methods: { methods: {
@ -169,7 +186,15 @@ export default {
goto(row) { goto(row) {
window.open(row.consoleUrl, '_blank'); window.open(row.consoleUrl, '_blank');
} },
consoleTooltip(row) {
if (!row.consoleUrl) {
return this.t('harvester.host.noConsoleUrl');
}
return '';
},
}, },
typeDisplay() { typeDisplay() {
@ -199,10 +224,19 @@ export default {
> >
<template #cell:console="{row}"> <template #cell:console="{row}">
<button type="button" class="btn btn-sm role-primary" :disabled="!row.consoleUrl" @click="goto(row)"> <div class="console-button">
{{ t('harvester.host.console') }} <button v-clean-tooltip="consoleTooltip(row)" type="button" class="mr-5 btn btn-sm role-primary" :disabled="!row.consoleUrl" @click="goto(row)">
</button> {{ t('harvester.host.console') }}
</button>
<a v-if="!row.consoleUrl" :href="consoleDocLink" target="_blank"><i class="icon icon-info" /></a>
</div>
</template> </template>
</ResourceTable> </ResourceTable>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.console-button {
display: flex;
}
</style>

View File

@ -103,7 +103,6 @@ export default {
:schema="schema" :schema="schema"
:rows="rows" :rows="rows"
key-field="_key" key-field="_key"
/> />
</div> </div>
</template> </template>

View File

@ -56,8 +56,6 @@ export default {
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false }); this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
} }
console.log('ppppppppppp', hash);
this.rows = hash.pvcs; this.rows = hash.pvcs;
}, },
@ -151,23 +149,23 @@ export default {
> >
<template <template
cell:state="scope" cell:state="scope"
> >
<div class="state"> <div class="state">
<HarvesterVolumeState <HarvesterVolumeState
class="vmstate" class="vmstate"
:row="scope.row" :row="scope.row"
/> />
</div> </div>
</template> </template>
<template <template
cell:AttachedVM="scope" cell:AttachedVM="scope"
> >
<div> <div>
<router-link <router-link
v-if="getVMName(scope.row)" v-if="getVMName(scope.row)"
:to="goTo(scope.row)" :to="goTo(scope.row)"
> >
{{ getVMName(scope.row) }} {{ getVMName(scope.row) }}
</router-link> </router-link>
</div> </div>

View File

@ -81,6 +81,10 @@ export default {
backups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BACKUP }), backups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BACKUP }),
}; };
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.RESOURCE_QUOTA)) {
_hash.resourceQuotas = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESOURCE_QUOTA });
}
if (this.$store.getters[`${ inStore }/schemaFor`](NODE)) { if (this.$store.getters[`${ inStore }/schemaFor`](NODE)) {
_hash.nodes = this.$store.dispatch(`${ inStore }/findAll`, { type: NODE }); _hash.nodes = this.$store.dispatch(`${ inStore }/findAll`, { type: NODE });
this.hasNode = true; this.hasNode = true;
@ -116,7 +120,7 @@ export default {
headers() { headers() {
const restoreCol = { const restoreCol = {
name: 'restoreProgress', name: 'restoreProgress',
labelKey: 'tableHeaders.restore', labelKey: 'harvester.tableHeaders.restore',
value: 'restoreProgress', value: 'restoreProgress',
align: 'left', align: 'left',
formatter: 'HarvesterBackupProgressBar', formatter: 'HarvesterBackupProgressBar',
@ -128,7 +132,7 @@ export default {
value: 'nodeName', value: 'nodeName',
sort: ['realAttachNodeName'], sort: ['realAttachNodeName'],
formatter: 'HarvesterHost', formatter: 'HarvesterHost',
labelKey: 'tableHeaders.node' labelKey: 'harvester.tableHeaders.vm.node'
}; };
const cols = clone(VM_HEADERS); const cols = clone(VM_HEADERS);
@ -137,7 +141,7 @@ export default {
cols.splice(-1, 0, nodeCol); cols.splice(-1, 0, nodeCol);
} }
if (this.hasRestoredVMs) { if (this.hasBackUpRestoreInProgress) {
cols.splice(-1, 0, restoreCol); cols.splice(-1, 0, restoreCol);
} }
@ -150,8 +154,11 @@ export default {
return [...this.allVMs, ...matchVMIs]; return [...this.allVMs, ...matchVMIs];
}, },
hasRestoredVMs() { /**
return !!this.rows.find(r => !!r.restoreResource); * We want to show the progress bar only for Backup's restore; snapshot's restore is immediate.
*/
hasBackUpRestoreInProgress() {
return !!this.rows.find(r => r.restoreResource && !r.restoreResource.fromSnapshot && !r.restoreResource.isComplete);
} }
}, },
@ -179,7 +186,7 @@ export default {
key-field="_key" key-field="_key"
> >
<template cell:state="scope"> <template cell:state="scope" class="state-col">
<div class="state"> <div class="state">
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetworks" :all-cluster-network="allClusterNetworks" /> <HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetworks" :all-cluster-network="allClusterNetworks" />
</div> </div>

View File

@ -144,6 +144,10 @@ export default {
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.secureBoot; return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.secureBoot;
}, },
isCpuPinning(spec) {
return !!spec?.template?.spec?.domain?.cpu?.dedicatedCpuPlacement;
},
getCloudInitNoCloud(spec) { getCloudInitNoCloud(spec) {
const secret = this.getSecret(spec); const secret = this.getSecret(spec);
let userData = secret?.decodedData?.userdata; let userData = secret?.decodedData?.userdata;

View File

@ -18,7 +18,7 @@ import {
import { HOSTNAME } from '@shell/config/labels-annotations'; import { HOSTNAME } from '@shell/config/labels-annotations';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations'; import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { uniq } from '@shell/utils/array'; import { uniq } from '@shell/utils/array';
import { ADD_ONS, SOURCE_TYPE, ACCESS_CREDENTIALS } from '../../config/harvester-map'; import { ADD_ONS, SOURCE_TYPE, ACCESS_CREDENTIALS, maintenanceStrategies, runStrategies } from '../../config/harvester-map';
import { HCI_SETTING } from '../../config/settings'; import { HCI_SETTING } from '../../config/settings';
import { HCI } from '../../types'; import { HCI } from '../../types';
import { parseVolumeClaimTemplates } from '../../utils/vm'; import { parseVolumeClaimTemplates } from '../../utils/vm';
@ -136,6 +136,9 @@ export default {
spec: null, spec: null,
osType: 'linux', osType: 'linux',
sshKey: [], sshKey: [],
maintenanceStrategies,
maintenanceStrategy: 'Migrate',
runStrategies,
runStrategy: 'RerunOnFailure', runStrategy: 'RerunOnFailure',
installAgent: true, installAgent: true,
hasCreateVolumes: [], hasCreateVolumes: [],
@ -164,6 +167,7 @@ export default {
enabledSriovgpu: false, enabledSriovgpu: false,
immutableMode: this.realMode === _CREATE ? _CREATE : _VIEW, immutableMode: this.realMode === _CREATE ? _CREATE : _VIEW,
terminationGracePeriodSeconds: '', terminationGracePeriodSeconds: '',
cpuPinning: false,
}; };
}, },
@ -316,6 +320,11 @@ export default {
}; };
} }
if (!vm.metadata.labels) {
vm.metadata.labels = {};
}
const maintenanceStrategy = vm.metadata.labels?.[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] || 'Migrate';
const runStrategy = spec.runStrategy || 'RerunOnFailure'; const runStrategy = spec.runStrategy || 'RerunOnFailure';
const machineType = value.machineType; const machineType = value.machineType;
const cpu = spec.template.spec.domain?.cpu?.cores; const cpu = spec.template.spec.domain?.cpu?.cores;
@ -352,6 +361,7 @@ export default {
const efiEnabled = this.isEfiEnabled(spec); const efiEnabled = this.isEfiEnabled(spec);
const tpmEnabled = this.isTpmEnabled(spec); const tpmEnabled = this.isTpmEnabled(spec);
const secureBoot = this.isSecureBoot(spec); const secureBoot = this.isSecureBoot(spec);
const cpuPinning = this.isCpuPinning(spec);
const secretRef = this.getSecret(spec); const secretRef = this.getSecret(spec);
const accessCredentials = this.getAccessCredentials(spec); const accessCredentials = this.getAccessCredentials(spec);
@ -362,6 +372,7 @@ export default {
} }
this['spec'] = spec; this['spec'] = spec;
this['maintenanceStrategy'] = maintenanceStrategy;
this['runStrategy'] = runStrategy; this['runStrategy'] = runStrategy;
this['secretRef'] = secretRef; this['secretRef'] = secretRef;
this['accessCredentials'] = accessCredentials; this['accessCredentials'] = accessCredentials;
@ -382,6 +393,7 @@ export default {
this['efiEnabled'] = efiEnabled; this['efiEnabled'] = efiEnabled;
this['tpmEnabled'] = tpmEnabled; this['tpmEnabled'] = tpmEnabled;
this['secureBoot'] = secureBoot; this['secureBoot'] = secureBoot;
this['cpuPinning'] = cpuPinning;
this['hasCreateVolumes'] = hasCreateVolumes; this['hasCreateVolumes'] = hasCreateVolumes;
this['networkRows'] = networkRows; this['networkRows'] = networkRows;
@ -401,15 +413,37 @@ export default {
let out = []; let out = [];
if (_disks.length === 0) { if (_disks.length === 0) {
let bus = 'virtio';
let type = HARD_DISK;
let size = '10Gi';
const imageResource = this.images.find( I => this.imageId === I.id);
const isIsoImage = /iso$/i.test(imageResource?.imageSuffix);
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
if (isIsoImage) {
bus = 'sata';
type = CD_ROM;
}
if (imageSize) {
let imageSizeGiB = Math.ceil(imageSize / 1024 / 1024 / 1024);
if (!isIsoImage) {
imageSizeGiB = Math.max(imageSizeGiB, 10);
}
size = `${ imageSizeGiB }Gi`;
}
out.push({ out.push({
id: randomStr(5), id: randomStr(5),
source: SOURCE_TYPE.IMAGE, source: SOURCE_TYPE.IMAGE,
name: 'disk-0', name: 'disk-0',
accessMode: 'ReadWriteMany', accessMode: 'ReadWriteMany',
bus: 'virtio', bus,
volumeName: '', volumeName: '',
size: '10Gi', size,
type: HARD_DISK, type,
storageClassName: '', storageClassName: '',
image: this.imageId, image: this.imageId,
volumeMode: 'Block', volumeMode: 'Block',
@ -574,6 +608,12 @@ export default {
} else { } else {
vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY] = this.reservedMemory; vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY] = this.reservedMemory;
} }
if (this.maintenanceStrategy === 'Migrate') {
delete vm.metadata.labels[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY];
} else {
vm.metadata.labels[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] = this.maintenanceStrategy;
}
}, },
parseDiskRows(disk) { parseDiskRows(disk) {
@ -681,27 +721,24 @@ export default {
spec = this.multiVMScheduler(spec); spec = this.multiVMScheduler(spec);
} }
this.value.metadata['annotations'] = { this.value.metadata['annotations'] = {...this.value.metadata.annotations,
...this.value.metadata.annotations,
[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates), [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
[HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps) [HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps)};
};
this.value.metadata['labels'] = { this.value.metadata['labels'] = {...this.value.metadata.labels,
...this.value.metadata.labels,
[HCI_ANNOTATIONS.CREATOR]: 'harvester', [HCI_ANNOTATIONS.CREATOR]: 'harvester',
[HCI_ANNOTATIONS.OS]: this.osType [HCI_ANNOTATIONS.OS]: this.osType};
};
this.value['spec'] = spec; this.value['spec'] = spec;
this['spec'] = spec; this['spec'] = spec;
} else if (this.resource === HCI.VM_VERSION) { } else if (this.resource === HCI.VM_VERSION) {
this.value.spec.vm['spec'] = spec; this.value.spec.vm['spec'] = spec;
this.value.spec.vm.metadata['annotations'] = { ...this.value.spec.vm.metadata.annotations, [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates) }; this.value.spec.vm.metadata['annotations'] = {
this.value.spec.vm.metadata['labels'] = { ...this.value.spec.vm.metadata.annotations,
...this.value.spec.vm.metadata.labels, [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
[HCI_ANNOTATIONS.OS]: this.osType
}; };
this.value.spec.vm.metadata['labels'] = {...this.value.spec.vm.metadata.labels,
[HCI_ANNOTATIONS.OS]: this.osType,};
this['spec'] = spec; this['spec'] = spec;
} }
}, },
@ -819,6 +856,10 @@ export default {
} }
}, },
getMaintenanceStrategyOptionLabel(opt) {
return this.t(`harvester.virtualMachine.maintenanceStrategy.options.${ opt.label || opt }`);
},
getInitUserData(config) { getInitUserData(config) {
const _QGA_JSON = this.getMatchQGA(config.osType); const _QGA_JSON = this.getMatchQGA(config.osType);
@ -1339,6 +1380,14 @@ export default {
} }
}, },
setCpuPinning(value) {
if (value) {
set(this.spec.template.spec.domain.cpu, 'dedicatedCpuPlacement', true);
} else {
delete this.spec.template.spec.domain.cpu['dedicatedCpuPlacement'];
}
},
setTPM(tpmEnabled) { setTPM(tpmEnabled) {
if (tpmEnabled) { if (tpmEnabled) {
set(this.spec.template.spec.domain.devices, 'tpm', {}); set(this.spec.template.spec.domain.devices, 'tpm', {});
@ -1462,6 +1511,10 @@ export default {
this.setBootMethod({ efi: this.efiEnabled, secureBoot: val }); this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
}, },
cpuPinning(value) {
this.setCpuPinning(value);
},
tpmEnabled(val) { tpmEnabled(val) {
this.setTPM(val); this.setTPM(val);
}, },

View File

@ -5,6 +5,7 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
import { get } from '@shell/utils/object'; import { get } from '@shell/utils/object';
import { NAMESPACE } from '@shell/config/types'; import { NAMESPACE } from '@shell/config/types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester'; import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
import { HCI } from '../../types';
const OBSCURE_NAMESPACE_PREFIX = [ const OBSCURE_NAMESPACE_PREFIX = [
'c-', // cluster namespace 'c-', // cluster namespace
@ -37,15 +38,32 @@ export default class HciNamespace extends namespace {
weight: -10, weight: -10,
}; };
const editQuotaAction = {
action: 'editNSQuota',
label: this.t('harvester.modal.quota.editQuota'),
icon: 'icon icon-storage',
enabled: !!this?.actions?.updateResourceQuota && !!this?.actions?.deleteResourceQuota,
weight: -11,
};
if (remove > -1) { if (remove > -1) {
out.splice(remove, 1); out.splice(remove, 1);
} }
insertAt(out, out.length - 1, promptRemove); insertAt(out, out.length - 1, promptRemove);
insertAt(out, out.length - 5, editQuotaAction);
return out; return out;
} }
editNSQuota(resources = this) {
this.$dispatch('promptModal', {
resources,
snapshotSizeQuota: this.snapshotSizeQuota,
component: 'HarvesterQuotaDialog'
});
}
promptRemove(resources = this) { promptRemove(resources = this) {
this.$dispatch('promptModal', { this.$dispatch('promptModal', {
resources, resources,
@ -54,6 +72,17 @@ export default class HciNamespace extends namespace {
}); });
} }
get nsResourceQuota() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const allResQuotas = this.$rootGetters[`${ inStore }/all`](HCI.RESOURCE_QUOTA);
return allResQuotas.find( RQ => RQ.metadata.namespace === this.id);
}
get snapshotSizeQuota() {
return this.nsResourceQuota?.spec?.snapshotLimit?.namespaceTotalSnapshotSizeQuota;
}
get isSystem() { get isSystem() {
const systemNamespaces = ['fleet-default']; const systemNamespaces = ['fleet-default'];

View File

@ -59,6 +59,22 @@ export default class HciNode extends HarvesterResource {
total: 1 total: 1
}; };
const enableCPUManager = {
action: 'enableCPUManager',
enabled: this.hasAction('enableCPUManager') && !this.isCPUManagerEnableInProgress && !this.isCPUManagerEnabled && !this.isEtcd, // witness node doesn't have CPU manager
icon: 'icon icon-fw icon-os-management',
label: this.t('harvester.action.enableCPUManager'),
total: 1
};
const disableCPUManager = {
action: 'disableCPUManager',
enabled: this.hasAction('disableCPUManager') && !this.isCPUManagerEnableInProgress && this.isCPUManagerEnabled && !this.isEtcd,
icon: 'icon icon-fw icon-os-management',
label: this.t('harvester.action.disableCPUManager'),
total: 1
};
const shutDown = { const shutDown = {
action: 'shutDown', action: 'shutDown',
enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && !this.isStopped && !!this.inventory, enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && !this.isStopped && !!this.inventory,
@ -88,6 +104,8 @@ export default class HciNode extends HarvesterResource {
uncordon, uncordon,
enableMaintenance, enableMaintenance,
disableMaintenance, disableMaintenance,
enableCPUManager,
disableCPUManager,
shutDown, shutDown,
powerOn, powerOn,
reboot, reboot,
@ -134,15 +152,12 @@ export default class HciNode extends HarvesterResource {
get consoleUrl() { get consoleUrl() {
const url = this.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL]; const url = this.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
const validator = /^[a-z]+:\/\//;
if (!url) { if (!url?.match(validator)) {
return false; return false;
} }
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return `http://${ url }`;
}
return url; return url;
} }
@ -308,6 +323,14 @@ export default class HciNode extends HarvesterResource {
this.doAction('disableMaintenanceMode', {}); this.doAction('disableMaintenanceMode', {});
} }
enableCPUManager() {
this.doActionGrowl('enableCPUManager', {});
}
disableCPUManager() {
this.doActionGrowl('disableCPUManager', {});
}
get isUnSchedulable() { get isUnSchedulable() {
return ( return (
this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_SCHEDULABLE] === 'false' || this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_SCHEDULABLE] === 'false' ||
@ -347,6 +370,28 @@ export default class HciNode extends HarvesterResource {
); );
} }
get isCPUManagerEnabled() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.CPU_MANAGER] === 'true';
}
get isCPUManagerEnableInProgress() {
return this.cpuManagerUpdateStatus === 'requested' || this.cpuManagerUpdateStatus === 'running';
}
get isCPUManagerEnableFailed() {
return this.cpuManagerUpdateStatus === 'failed';
}
get cpuManagerUpdateStatus() {
try {
const cpuManagerUpdate = JSON.parse(this.metadata.annotations[HCI_ANNOTATIONS.NODE_CPU_MANAGER_UPDATE_STATUS] || '{}');
return cpuManagerUpdate.status || '';
} catch {
return '';
}
}
get longhornDisks() { get longhornDisks() {
const inStore = this.$rootGetters['currentProduct'].inStore; const inStore = this.$rootGetters['currentProduct'].inStore;
const longhornNode = this.$rootGetters[`${ inStore }/byId`]( const longhornNode = this.$rootGetters[`${ inStore }/byId`](

View File

@ -20,12 +20,10 @@ export default class HciPv extends HarvesterResource {
const storageClassName = const storageClassName =
realMode === _CLONE ? this.spec.storageClassName : ''; realMode === _CLONE ? this.spec.storageClassName : '';
this['spec'] = { this['spec'] = {accessModes,
accessModes,
storageClassName, storageClassName,
volumeName: '', volumeName: '',
resources: { requests: { storage } } resources: { requests: { storage } }};
};
} }
get availableActions() { get availableActions() {
@ -98,7 +96,6 @@ export default class HciPv extends HarvesterResource {
} }
get stateDisplay() { get stateDisplay() {
const ownedBy = this?.metadata?.annotations?.[HCI_ANNOTATIONS.OWNED_BY];
const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR]; const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR];
const degradedVolume = volumeError === DEGRADED_ERROR; const degradedVolume = volumeError === DEGRADED_ERROR;
const status = this?.status?.phase === 'Bound' && !volumeError && this.isLonghornVolumeReady ? 'Ready' : 'Not Ready'; const status = this?.status?.phase === 'Bound' && !volumeError && this.isLonghornVolumeReady ? 'Ready' : 'Not Ready';
@ -107,7 +104,7 @@ export default class HciPv extends HarvesterResource {
if (findBy(conditions, 'type', 'Resizing')?.status === 'True') { if (findBy(conditions, 'type', 'Resizing')?.status === 'True') {
return 'Resizing'; return 'Resizing';
} else if (ownedBy && !volumeError) { } else if (!!this.attachVM && !volumeError) {
return 'In-use'; return 'In-use';
} else if (degradedVolume) { } else if (degradedVolume) {
return 'Degraded'; return 'Degraded';
@ -179,17 +176,19 @@ export default class HciPv extends HarvesterResource {
} }
get attachVM() { get attachVM() {
const allVMs = this.$rootGetters['harvester/all'](HCI.VM); const allVMs = this.$rootGetters['harvester/all'](HCI.VM) || [];
const ownedBy =
get(this, `metadata.annotations."${ HCI_ANNOTATIONS.OWNED_BY }"`) || ''; const findAttachVM = (vm) => {
const attachVolumes = vm.spec.template?.spec?.volumes || [];
if (vm.namespace === this.namespace && attachVolumes.length > 0) {
return attachVolumes.find(vol => vol.persistentVolumeClaim?.claimName === this.name);
}
if (!ownedBy) {
return null; return null;
} };
const ownedId = JSON.parse(ownedBy)[0]?.refs?.[0]; return allVMs.find(findAttachVM);
return allVMs.find(D => D.id === ownedId);
} }
get isAvailable() { get isAvailable() {

View File

@ -9,6 +9,10 @@ export default class HciUpgrade extends HarvesterResource {
return this?.metadata?.labels?.[HCI.LATEST_UPGRADE] === 'true'; return this?.metadata?.labels?.[HCI.LATEST_UPGRADE] === 'true';
} }
get isUpgradeFailed() {
return this?.metadata?.labels?.[HCI.UPGRADE_STATE] === 'Failed';
}
get isUpgradeSucceeded() { get isUpgradeSucceeded() {
return this?.metadata?.labels?.[HCI.UPGRADE_STATE] === 'Succeeded'; return this?.metadata?.labels?.[HCI.UPGRADE_STATE] === 'Succeeded';
} }
@ -117,10 +121,10 @@ export default class HciUpgrade extends HarvesterResource {
get upgradeImageMessage() { get upgradeImageMessage() {
const conditions = this?.status?.conditions || []; const conditions = this?.status?.conditions || [];
const imageReady = conditions.find( cond => cond.type === 'ImageReady'); const imageReady = conditions.find( cond => cond.type === 'ImageReady');
const hasError = imageReady?.status === 'False'; const success = imageReady?.status === 'True';
const message = imageReady?.message || imageReady?.reason; const message = imageReady?.message || imageReady?.reason;
return hasError ? message : ''; return success ? '' : message;
} }
get nodeUpgradeMessage() { get nodeUpgradeMessage() {

View File

@ -47,7 +47,7 @@ export default class HciVmImage extends HarvesterResource {
{ {
action: 'createFromImage', action: 'createFromImage',
enabled: canCreateVM, enabled: canCreateVM,
icon: 'icon icon-fw icon-spinner', icon: 'icon icon-circle-plus',
label: this.t('harvester.action.createVM'), label: this.t('harvester.action.createVM'),
disabled: !this.isReady, disabled: !this.isReady,
}, },
@ -74,7 +74,7 @@ export default class HciVmImage extends HarvesterResource {
router.push({ router.push({
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`, name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
params: { resource: HCI.VM }, params: { resource: HCI.VM },
query: { image: this.id } query: { image: this.id, fromPage: HCI.IMAGE }
}); });
} }
@ -101,6 +101,10 @@ export default class HciVmImage extends HarvesterResource {
const imported = this.getStatusConditionOfType('Imported'); const imported = this.getStatusConditionOfType('Imported');
if (imported?.status === 'Unknown') { if (imported?.status === 'Unknown') {
if (this.spec.sourceType === 'restore') {
return 'Restoring';
}
if (this.spec.sourceType === 'download') { if (this.spec.sourceType === 'download') {
return 'Downloading'; return 'Downloading';
} }
@ -131,7 +135,8 @@ export default class HciVmImage extends HarvesterResource {
const conditions = this?.status?.conditions || []; const conditions = this?.status?.conditions || [];
const initialized = conditions.find( cond => cond.type === 'Initialized'); const initialized = conditions.find( cond => cond.type === 'Initialized');
const imported = conditions.find( cond => cond.type === 'Imported'); const imported = conditions.find( cond => cond.type === 'Imported');
const message = initialized?.message || imported?.message; const retryLimitExceeded = conditions.find( cond => cond.type === 'RetryLimitExceeded');
const message = initialized?.message || imported?.message || retryLimitExceeded?.message;
return ucFirst(message); return ucFirst(message);
} }
@ -167,6 +172,21 @@ export default class HciVmImage extends HarvesterResource {
}); });
} }
get virtualSize() {
const virtualSize = this.status?.virtualSize;
if (!virtualSize) {
return '-';
}
return formatSi(virtualSize, {
increment: 1024,
maxPrecision: 2,
suffix: 'B',
firstSuffix: 'B',
});
}
getStatusConditionOfType(type, defaultValue = []) { getStatusConditionOfType(type, defaultValue = []) {
const conditions = Array.isArray(get(this, 'status.conditions')) ? this.status.conditions : defaultValue; const conditions = Array.isArray(get(this, 'status.conditions')) ? this.status.conditions : defaultValue;

View File

@ -133,7 +133,7 @@ export default class VirtVm extends HarvesterResource {
{ {
action: 'softrebootVM', action: 'softrebootVM',
enabled: !!this.actions?.softreboot, enabled: !!this.actions?.softreboot,
icon: 'icon icon-refresh', icon: 'icon icon-pipeline',
label: this.t('harvester.action.softreboot') label: this.t('harvester.action.softreboot')
}, },
{ {
@ -152,9 +152,15 @@ export default class VirtVm extends HarvesterResource {
{ {
action: 'takeVMSnapshot', action: 'takeVMSnapshot',
enabled: !!this.actions?.backup, enabled: !!this.actions?.backup,
icon: 'icon icon-backup', icon: 'icon icon-snapshot',
label: this.t('harvester.action.vmSnapshot') label: this.t('harvester.action.vmSnapshot')
}, },
{
action: 'editVMQuota',
enabled: !!this.actions?.updateResourceQuota && !!this.actions.deleteResourceQuota,
icon: 'icon icon-storage',
label: this.t('harvester.action.editVMQuota')
},
{ {
action: 'restoreVM', action: 'restoreVM',
enabled: !!this.actions?.restore, enabled: !!this.actions?.restore,
@ -331,6 +337,14 @@ export default class VirtVm extends HarvesterResource {
}); });
} }
editVMQuota(resources = this) {
this.$dispatch('promptModal', {
resources,
snapshotSizeQuota: this.snapshotSizeQuota,
component: 'HarvesterQuotaDialog'
});
}
unplugVolume(diskName) { unplugVolume(diskName) {
const resources = this; const resources = this;
@ -541,6 +555,17 @@ export default class VirtVm extends HarvesterResource {
return null; return null;
} }
get nsResourceQuota() {
const inStore = this.productInStore;
const allResQuotas = this.$rootGetters[`${ inStore }/all`](HCI.RESOURCE_QUOTA);
return allResQuotas.find( RQ => RQ.namespace === this.metadata.namespace);
}
get snapshotSizeQuota() {
return this.nsResourceQuota?.spec?.snapshotLimit?.vmTotalSnapshotSizeQuota?.[this.metadata.name];
}
get vmi() { get vmi() {
const inStore = this.productInStore; const inStore = this.productInStore;
@ -646,6 +671,10 @@ export default class VirtVm extends HarvesterResource {
return null; return null;
} }
get isTerminating() {
return !!this?.metadata?.deletionTimestamp;
}
get otherState() { get otherState() {
const state = (this.vmi && const state = (this.vmi &&
[VMIPhase.Scheduling, VMIPhase.Scheduled].includes( [VMIPhase.Scheduling, VMIPhase.Scheduled].includes(
@ -685,18 +714,21 @@ export default class VirtVm extends HarvesterResource {
const allRestore = this.$rootGetters[`${ inStore }/all`](HCI.RESTORE); const allRestore = this.$rootGetters[`${ inStore }/all`](HCI.RESTORE);
return allRestore.find(O => O.id === id); const res = allRestore.find(O => O.id === id);
if (res) {
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
res.fromSnapshot = !!allBackups
.filter(b => b.spec?.type !== BACKUP_TYPE.BACKUP)
.find(s => s.id === `${ res.spec?.virtualMachineBackupNamespace }/${ res.spec?.virtualMachineBackupName }`);
}
return res;
} }
get restoreProgress() { get restoreProgress() {
const inStore = this.productInStore; if (this.isVMError || this.isTerminating) {
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
const isSnapshotRestore = !!allBackups
.filter(b => b.spec?.type !== BACKUP_TYPE.BACKUP)
.find(s => s.id === `${ this.restoreResource?.spec?.virtualMachineBackupNamespace }/${ this.restoreResource?.spec?.virtualMachineBackupName }`);
if (isSnapshotRestore) {
return {}; return {};
} }
@ -726,7 +758,7 @@ export default class VirtVm extends HarvesterResource {
return 'Restoring'; return 'Restoring';
} }
if (this?.metadata?.deletionTimestamp) { if (this.isTerminating) {
return 'Terminating'; return 'Terminating';
} }

View File

@ -7,6 +7,7 @@ import CommunityLinks from '@shell/components/CommunityLinks';
import { SCHEMA } from '@shell/config/types'; import { SCHEMA } from '@shell/config/types';
import HarvesterSupportBundle from '../../../../dialog/HarvesterSupportBundle'; import HarvesterSupportBundle from '../../../../dialog/HarvesterSupportBundle';
import { HCI } from '../../../../types'; import { HCI } from '../../../../types';
import { DOC_LINKS } from '../../../../config/doc-links';
export default { export default {
components: { components: {
@ -71,13 +72,18 @@ export default {
const { host, params } = this.internalPrefix; const { host, params } = this.internalPrefix;
return `https://${ host }/k8s/clusters/${ params.cluster }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/#/dashboard`; return `https://${ host }/k8s/clusters/${ params.cluster }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/#/dashboard`;
} },
rancherIntegrationLink() {
return DOC_LINKS.RANCHER_INTEGRATION_URL;
},
}, },
methods: { methods: {
open() { open() {
this.$store.commit('harvester-common/toggleBundleModal', true); this.$store.commit('harvester-common/toggleBundleModal', true);
} },
} }
}; };
</script> </script>
@ -89,10 +95,7 @@ export default {
<IndentedPanel> <IndentedPanel>
<div class="content mt-20"> <div class="content mt-20">
<div class="promo"> <div class="promo">
<div <div v-if="showSupportBundle" class="box mb-20 box-primary">
v-if="showSupportBundle"
class="box mb-20 box-primary"
>
<h2> <h2>
{{ t('harvester.modal.bundle.title') }} {{ t('harvester.modal.bundle.title') }}
</h2> </h2>
@ -109,10 +112,7 @@ export default {
</button> </button>
</div> </div>
</div> </div>
<div <div class="box box-primary" :class="{'mb-20': dev }">
class="box box-primary"
:class="{'mb-20': dev }"
>
<h2> <h2>
{{ t('harvester.support.kubeconfig.title') }} {{ t('harvester.support.kubeconfig.title') }}
</h2> </h2>
@ -129,41 +129,24 @@ export default {
</button> </button>
</div> </div>
</div> </div>
<div <div v-if="dev" class="row">
v-if="dev"
class="row"
>
<div class="col span-6 box box-primary"> <div class="col span-6 box box-primary">
<h2> <h2>
<a <a rel="nofollow noopener noreferrer" target="_blank" :href="rancherLink">{{ t('harvester.support.internal.rancher.title') }} <i class="icon icon-external-link" /></a>
rel="nofollow noopener noreferrer"
target="_blank"
:href="rancherLink"
>{{ t('harvester.support.internal.rancher.title') }} <i class="icon icon-external-link" /></a>
</h2> </h2>
<div> <div>
<p class="warning"> <p class="warning">
<t <t k="harvester.support.internal.rancher.titleDescription" :raw="true" :url="rancherIntegrationLink" />
k="harvester.support.internal.rancher.titleDescription"
:raw="true"
/>
</p> </p>
</div> </div>
</div> </div>
<div class="col span-6 box box-primary"> <div class="col span-6 box box-primary">
<h2> <h2>
<a <a rel="nofollow noopener noreferrer" target="_blank" :href="longhornLink">{{ t('harvester.support.internal.longhorn.title') }} <i class="icon icon-external-link" /></a>
rel="nofollow noopener noreferrer"
target="_blank"
:href="longhornLink"
>{{ t('harvester.support.internal.longhorn.title') }} <i class="icon icon-external-link" /></a>
</h2> </h2>
<div> <div>
<p class="warning"> <p class="warning">
<t <t k="harvester.support.internal.longhorn.titleDescription" :raw="true" />
k="harvester.support.internal.longhorn.titleDescription"
:raw="true"
/>
</p> </p>
</div> </div>
</div> </div>
@ -173,17 +156,9 @@ export default {
<CommunityLinks :link-options="options" /> <CommunityLinks :link-options="options" />
</div> </div>
<div class="external"> <div class="external">
<a <a href="https://www.suse.com/suse-harvester/support-matrix/all-supported-versions" target="_blank" rel="noopener noreferrer nofollow">{{ t('harvester.support.community.learnMore') }} <i class="icon icon-external-link" /></a>
href="https://www.suse.com/suse-harvester/support-matrix/all-supported-versions"
target="_blank"
rel="noopener noreferrer nofollow"
>{{ t('harvester.support.community.learnMore') }} <i class="icon icon-external-link" /></a>
or or
<a <a href="https://www.suse.com/products/harvester/" target="_blank" rel="noopener noreferrer nofollow">{{ t('harvester.support.community.pricing') }} <i class="icon icon-external-link" /></a>
href="https://www.suse.com/products/harvester/"
target="_blank"
rel="noopener noreferrer nofollow"
>{{ t('harvester.support.community.pricing') }} <i class="icon icon-external-link" /></a>
</div> </div>
</div> </div>
</IndentedPanel> </IndentedPanel>

View File

@ -18,16 +18,16 @@ export default {
const cluster = await dispatch('management/find', { const cluster = await dispatch('management/find', {
type: MANAGEMENT.CLUSTER, type: MANAGEMENT.CLUSTER,
id, id,
opt: { url: `${MANAGEMENT.CLUSTER}s/${escape(id)}` } opt: { url: `${ MANAGEMENT.CLUSTER }s/${ escape(id) }` }
}, { root: true }); }, { root: true });
let virtualBase = `/k8s/clusters/${escape(id)}/v1/harvester`; let virtualBase = `/k8s/clusters/${ escape(id) }/v1/harvester`;
if (id === 'local') { if (id === 'local') {
virtualBase = `/v1/harvester`; virtualBase = `/v1/harvester`;
} }
if (!cluster) { if ( !cluster ) {
commit('clusterId', null, { root: true }); commit('clusterId', null, { root: true });
commit('applyConfig', { baseUrl: null }); commit('applyConfig', { baseUrl: null });
throw new ClusterNotFoundError(id); throw new ClusterNotFoundError(id);
@ -44,22 +44,22 @@ export default {
const projectArgs = { const projectArgs = {
type: MANAGEMENT.PROJECT, type: MANAGEMENT.PROJECT,
opt: { opt: {
url: `${MANAGEMENT.PROJECT}/${escape(id)}`, url: `${ MANAGEMENT.PROJECT }/${ escape(id) }`,
watchNamespace: id watchNamespace: id
} }
}; };
const fetchProjects = async () => { const fetchProjects = async() => {
let limit = 30000; let limit = 30000;
const sleep = 100; const sleep = 100;
while (limit > 0 && !rootState.managementReady) { while ( limit > 0 && !rootState.managementReady ) {
await setTimeout(() => { }, sleep); await setTimeout(() => {}, sleep);
limit -= sleep; limit -= sleep;
} }
if (rootGetters['management/schemaFor'](MANAGEMENT.PROJECT)) { if ( rootGetters['management/schemaFor'](MANAGEMENT.PROJECT) ) {
return dispatch('management/findAll', projectArgs, { root: true }); return dispatch('management/findAll', projectArgs, { root: true });
} }
}; };
@ -67,8 +67,8 @@ export default {
if (id !== 'local' && getters['schemaFor'](MANAGEMENT.SETTING)) { // multi-cluster if (id !== 'local' && getters['schemaFor'](MANAGEMENT.SETTING)) { // multi-cluster
const settings = await dispatch('findAll', { const settings = await dispatch('findAll', {
type: MANAGEMENT.SETTING, type: MANAGEMENT.SETTING,
id: SETTING.SYSTEM_NAMESPACES, id: SETTING.SYSTEM_NAMESPACES,
opt: { url: `${virtualBase}/${MANAGEMENT.SETTING}s/`, force: true } opt: { url: `${ virtualBase }/${ MANAGEMENT.SETTING }s/`, force: true }
}); });
const systemNamespaces = settings?.find((x: any) => x.id === SETTING.SYSTEM_NAMESPACES); const systemNamespaces = settings?.find((x: any) => x.id === SETTING.SYSTEM_NAMESPACES);
@ -80,17 +80,21 @@ export default {
} }
} }
const hash: { [key: string]: Promise<any> } = { const hash: { [key: string]: Promise<any>} = {
projects: fetchProjects(), projects: fetchProjects(),
virtualCount: dispatch('findAll', { type: COUNT }), virtualCount: dispatch('findAll', { type: COUNT }),
virtualNamespaces: dispatch('findAll', { type: NAMESPACE }), virtualNamespaces: dispatch('findAll', { type: NAMESPACE }),
settings: dispatch('findAll', { type: HCI.SETTING }), settings: dispatch('findAll', { type: HCI.SETTING }),
clusters: dispatch('management/findAll', { clusters: dispatch('management/findAll', {
type: MANAGEMENT.CLUSTER, type: MANAGEMENT.CLUSTER,
opt: { force: true } opt: { force: true }
}, { root: true }), }, { root: true }),
}; };
if (getters['schemaFor'](HCI.RESOURCE_QUOTA)) {
hash.resourceQuota = dispatch('findAll', { type: HCI.RESOURCE_QUOTA });
}
if (getters['schemaFor'](HCI.UPGRADE)) { if (getters['schemaFor'](HCI.UPGRADE)) {
hash.upgrades = dispatch('findAll', { type: HCI.UPGRADE }); hash.upgrades = dispatch('findAll', { type: HCI.UPGRADE });
} }
@ -101,15 +105,15 @@ export default {
commit('updateNamespaces', { commit('updateNamespaces', {
filters: [], filters: [],
all: getters.filterNamespace(), all: getters.filterNamespace(),
getters getters
}, { root: true }); }, { root: true });
// Solve compatibility with Rancher v2.6.x, fell remove these codes after not support v2.6.x // Solve compatibility with Rancher v2.6.x, fell remove these codes after not support v2.6.x
const definition = { const definition = {
def: false, def: false,
parseJSON: true, parseJSON: true,
inheritFrom: DEV, inheritFrom: DEV,
asUserPreference: true, asUserPreference: true,
}; };
@ -134,9 +138,9 @@ export default {
if (isMultiCluster) { if (isMultiCluster) {
commit('managementChanged', { commit('managementChanged', {
ready: true, ready: true,
isMultiCluster: true, isMultiCluster: true,
isRancher: true, isRancher: true,
}, { root: true }); }, { root: true });
} }
}, },

View File

@ -14,9 +14,9 @@ export default {
divider, divider,
notFilterNamespaces notFilterNamespaces
}: any) => { }: any) => {
const out: { id: string, kind: string, label: string }[] = [{ const out = [{
id: ALL, id: ALL,
kind: NAMESPACE_FILTER_KINDS.SPECIAL, kind: NAMESPACE_FILTER_KINDS.SPECIAL,
label: rootGetters['i18n/t']('nav.ns.all'), label: rootGetters['i18n/t']('nav.ns.all'),
}]; }];
@ -30,11 +30,9 @@ export default {
MANAGEMENT.PROJECT MANAGEMENT.PROJECT
); );
projects = sortBy( projects = sortBy(filterBy(projects, 'spec.clusterName', cluster.id), [
filterBy(projects, 'spec.clusterName', cluster.id), 'nameDisplay',
['nameDisplay'], ]).filter((project: any) => project.nameDisplay !== 'System');
null
).filter((project: any) => project.nameDisplay !== 'System');
const projectsById: any = {}; const projectsById: any = {};
const namespacesByProject: any = {}; const namespacesByProject: any = {};
@ -72,8 +70,8 @@ export default {
} }
out.push({ out.push({
id: `project://${id}`, id: `project://${ id }`,
kind: 'project', kind: 'project',
label: project.nameDisplay, label: project.nameDisplay,
}); });
@ -90,8 +88,8 @@ export default {
} }
out.push({ out.push({
id: ALL_ORPHANS, id: ALL_ORPHANS,
kind: 'project', kind: 'project',
label: rootGetters['i18n/t']('nav.ns.orphan'), label: rootGetters['i18n/t']('nav.ns.orphan'),
}); });

View File

@ -28,12 +28,12 @@ const harvesterFactory = (): CoreStoreSpecifics => {
return steveFactory; return steveFactory;
}; };
const config: CoreStoreConfig = { const config: CoreStoreConfig = {
namespace: PRODUCT_NAME, namespace: PRODUCT_NAME,
isClusterStore: true isClusterStore: true
}; };
export default { export default {
specifics: harvesterFactory(), specifics: harvesterFactory(),
config, config,
init: steveStoreInit init: steveStoreInit
}; };

View File

@ -31,6 +31,7 @@ export const HCI = {
FLOW: 'harvesterhci.io.logging.flow', FLOW: 'harvesterhci.io.logging.flow',
OUTPUT: 'harvesterhci.io.logging.output', OUTPUT: 'harvesterhci.io.logging.output',
STORAGE: 'harvesterhci.io.storage', STORAGE: 'harvesterhci.io.storage',
RESOURCE_QUOTA: 'harvesterhci.io.resourcequota',
KSTUNED: 'node.harvesterhci.io.ksmtuned', KSTUNED: 'node.harvesterhci.io.ksmtuned',
PCI_DEVICE: 'devices.harvesterhci.io.pcidevice', PCI_DEVICE: 'devices.harvesterhci.io.pcidevice',
PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim', PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim',