diff --git a/pkg/harvester/config/feature-flags.js b/pkg/harvester/config/feature-flags.js
new file mode 100644
index 00000000..06d1e3c3
--- /dev/null
+++ b/pkg/harvester/config/feature-flags.js
@@ -0,0 +1,54 @@
+
+// https://github.com/harvester/harvester/wiki/Roadmap
+const FEATURES = {
+ cpuPinning: false,
+ usbPassthrough: false,
+ volumeEncryption: false,
+ schedulingVMBackup: false,
+ vmSnapshotQuota: false,
+ longhornV2LVMSupport: false,
+ improveMaintainMode: false,
+ autoRotateRke2CertsSetting: false,
+ kubeconfigDefaultTokenTTLMinutesSetting: false,
+ supportBundleNodeCollectionTimeoutSetting: false
+};
+
+// https://github.com/harvester/dashboard/releases/tag/v1.3.0
+const releaseV130 = { ...FEATURES };
+
+// https://github.com/harvester/dashboard/releases/tag/v1.3.1
+const releaseV131 = {
+ ...releaseV130,
+ autoRotateRke2CertsSetting: true,
+ supportBundleNodeCollectionTimeoutSetting: true
+};
+
+// https://github.com/harvester/dashboard/releases/tag/v1.3.2
+const releaseV132 = {
+ ...releaseV131,
+ kubeconfigDefaultTokenTTLMinutesSetting: true,
+};
+
+// TODO: change to https://github.com/harvester/dashboard/releases/tag/v1.4.0 after v1.4.0 release
+// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc5
+// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc4
+// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc3
+// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc2
+// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc1
+const releaseV140 = {
+ ...releaseV132,
+ cpuPinning: true,
+ usbPassthrough: true,
+ volumeEncryption: true,
+ schedulingVMBackup: true,
+ vmSnapshotQuota: true,
+ longhornV2LVMSupport: true,
+ improveMaintainMode: true,
+};
+
+export const RELEASE_FEATURES = {
+ 'v1.4.0': releaseV140,
+ 'v1.3.2': releaseV132,
+ 'v1.3.1': releaseV132,
+ 'v1.3.0': releaseV130,
+};
diff --git a/pkg/harvester/detail/harvesterhci.io.host/HarvesterHostBasic.vue b/pkg/harvester/detail/harvesterhci.io.host/HarvesterHostBasic.vue
index d5a0da9b..d35c49cb 100644
--- a/pkg/harvester/detail/harvesterhci.io.host/HarvesterHostBasic.vue
+++ b/pkg/harvester/detail/harvesterhci.io.host/HarvesterHostBasic.vue
@@ -247,7 +247,7 @@ export default {
node.cpuPinningFeatureEnabled)) {
+ out.push({
+ name: 'cpuManager',
+ labelKey: 'harvester.tableHeaders.cpuManager',
+ value: 'id',
+ formatter: 'HarvesterCPUPinning',
+ formatterOpts: { rows: this.rows },
+ width: 150,
+ align: 'center',
+ });
+ }
if (this.hasLonghornSchema) {
out.push({
name: 'diskState',
diff --git a/pkg/harvester/models/harvester/node.js b/pkg/harvester/models/harvester/node.js
index b07e7a34..ddcfa958 100644
--- a/pkg/harvester/models/harvester/node.js
+++ b/pkg/harvester/models/harvester/node.js
@@ -14,8 +14,8 @@ import { findBy, isArray } from '@shell/utils/array';
import { ucFirst } from '@shell/utils/string';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
-
import { HCI } from '../../types';
+import { featureEnabled } from '../../utils/server';
const ALLOW_SYSTEM_LABEL_KEYS = [
'topology.kubernetes.io/zone',
@@ -61,7 +61,7 @@ export default class HciNode extends HarvesterResource {
const enableCPUManager = {
action: 'enableCPUManager',
- enabled: this.hasAction('enableCPUManager') && !this.isCPUManagerEnableInProgress && !this.isCPUManagerEnabled && !this.isEtcd, // witness node doesn't have CPU manager
+ enabled: this.cpuPinningFeatureEnabled && 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
@@ -69,7 +69,7 @@ export default class HciNode extends HarvesterResource {
const disableCPUManager = {
action: 'disableCPUManager',
- enabled: this.hasAction('disableCPUManager') && !this.isCPUManagerEnableInProgress && this.isCPUManagerEnabled && !this.isEtcd,
+ enabled: this.cpuPinningFeatureEnabled && this.hasAction('disableCPUManager') && !this.isCPUManagerEnableInProgress && this.isCPUManagerEnabled && !this.isEtcd,
icon: 'icon icon-fw icon-os-management',
label: this.t('harvester.action.disableCPUManager'),
total: 1
@@ -370,6 +370,10 @@ export default class HciNode extends HarvesterResource {
);
}
+ get cpuPinningFeatureEnabled() {
+ return featureEnabled(this.$rootGetters, 'cpuPinning');
+ }
+
get isCPUManagerEnabled() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.CPU_MANAGER] === 'true';
}
diff --git a/pkg/harvester/models/kubevirt.io.virtualmachine.js b/pkg/harvester/models/kubevirt.io.virtualmachine.js
index b37f2f52..40801a5f 100644
--- a/pkg/harvester/models/kubevirt.io.virtualmachine.js
+++ b/pkg/harvester/models/kubevirt.io.virtualmachine.js
@@ -14,6 +14,7 @@ import { BACKUP_TYPE } from '../config/types';
import { HCI } from '../types';
import HarvesterResource from './harvester';
import { LVM_DRIVER } from './harvester/storage.k8s.io.storageclass';
+import { featureEnabled } from '../utils/server';
export const OFF = 'Off';
@@ -480,6 +481,10 @@ export default class VirtVm extends HarvesterResource {
return null;
}
+ get cpuPinningFeatureEnabled() {
+ return featureEnabled(this.$rootGetters, 'cpuPinning');
+ }
+
get isCpuPinning() {
return this.spec?.template?.spec?.domain?.cpu?.dedicatedCpuPlacement === true;
}
diff --git a/pkg/harvester/utils/server.js b/pkg/harvester/utils/server.js
new file mode 100644
index 00000000..3284cf36
--- /dev/null
+++ b/pkg/harvester/utils/server.js
@@ -0,0 +1,24 @@
+import semver from 'semver';
+import { HCI } from '../types';
+import { RELEASE_FEATURES } from '../config/feature-flags';
+
+export function serverVersion(getters) {
+ // e.g v1.4.0
+ if (process.env.VUE_APP_SERVER_VERSION) {
+ return process.env.VUE_APP_SERVER_VERSION;
+ }
+
+ try {
+ const v = getters['harvester/byId'](HCI.SETTING, 'server-version')?.value;
+
+ return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
+ } catch (error) {}
+
+ return '';
+}
+
+export const featureEnabled = (getters, featureKey) => {
+ const version = serverVersion(getters);
+
+ return !!RELEASE_FEATURES[version]?.[featureKey] || false;
+};