From 6e0e0237efe0f172d306661088e84a5e6ab1097e Mon Sep 17 00:00:00 2001 From: marcelofukumoto Date: Mon, 26 Oct 2026 16:28:50 +0000 Subject: [PATCH] feat: use the SUSE Virtualization product name on Prime (#1202) * feat: switch the product name with Rancher Prime Use a [[Harvester]] token for the product name in the translations, so Rancher 2.16 fills it from the l10n global that harvester-manager registers: SUSE Virtualization on Rancher Prime, Harvester otherwise. Rancher before 2.16 shows the tokens as written, so on those hosts (and standalone) the extension resolves them to Harvester itself. Signed-off-by: Marcelo Fukumoto * feat: switch the standalone product name with the server flavor Standalone has no Rancher Prime, so register the [[Harvester]] l10n global from the server-flavor setting instead: SUSE Virtualization on prime, Harvester otherwise. Signed-off-by: Marcelo Fukumoto * fix: point the network policy banner to the documentation Signed-off-by: Marcelo Fukumoto * feat: use the SUSE theme in standalone Prime Standalone Prime kept the harvester brand and only overrode the primary colour, so the rest of the palette and the fonts didn't match Rancher Prime. Switch the ui-brand setting to suse in the store when the server flavor is prime, which makes the shell load the SUSE theme. Signed-off-by: Marcelo Fukumoto * fix: keep the SUSE theme after a brand setting change event The watcher also reads the setting's value, so a change from the server loaded into the same object switches the brand back to suse. Signed-off-by: Marcelo Fukumoto --------- Signed-off-by: Marcelo Fukumoto --- pkg/harvester/config/harvester-cluster.js | 37 +++--- pkg/harvester/index.ts | 12 ++ pkg/harvester/l10n/en-us.yaml | 112 +++++++++--------- .../utils/__tests__/l10n-globals.test.js | 101 ++++++++++++++++ .../utils/__tests__/prime-brand.test.js | 54 +++++++++ pkg/harvester/utils/l10n-globals.js | 49 ++++++++ pkg/harvester/utils/prime-brand.js | 26 ++++ 7 files changed, 315 insertions(+), 76 deletions(-) create mode 100644 pkg/harvester/utils/__tests__/l10n-globals.test.js create mode 100644 pkg/harvester/utils/__tests__/prime-brand.test.js create mode 100644 pkg/harvester/utils/l10n-globals.js create mode 100644 pkg/harvester/utils/prime-brand.js diff --git a/pkg/harvester/config/harvester-cluster.js b/pkg/harvester/config/harvester-cluster.js index 19c40ec0..b127a9f4 100644 --- a/pkg/harvester/config/harvester-cluster.js +++ b/pkg/harvester/config/harvester-cluster.js @@ -57,10 +57,12 @@ import { } from './table-headers'; import { ADD_ONS } from './harvester-map'; import { registerAddonSideNav } from '../utils/dynamic-nav'; +import { setStandalonePrime } from '../utils/l10n-globals'; import { setVendor } from '@shell/config/private-label'; -import { createCssVars } from '@shell/utils/color'; import { CONFIGURABLE_TABLES } from '@shell/store/features'; import { tableViewsOffFeature } from '../utils/table-views'; +import { SETTING } from '@shell/config/settings'; +import { primeBrandUpdater } from '../utils/prime-brand'; const TEMPLATE = HCI.VM_VERSION; const MONITORING_GROUP = 'Monitoring & Logging::Monitoring'; @@ -123,27 +125,9 @@ export function init($plugin, store) { const [isHarvesterPrime, isDark, privateLabel] = JSON.parse(value); const primeLogo = isDark ? require('../assets/images/dark/suse-virtualization.svg') : require('../assets/images/suse-virtualization.svg'); + setStandalonePrime(isHarvesterPrime); setVendor(privateLabel || ''); - let primaryColorStyle = document.getElementById('harvester-prime-primary-color'); - - // set harvester prime primary color style to #4dd192 (Cyan Green) - if (isHarvesterPrime) { - if (!primaryColorStyle) { - primaryColorStyle = document.createElement('style'); - primaryColorStyle.id = 'harvester-prime-primary-color'; - document.head.appendChild(primaryColorStyle); - primaryColorStyle.sheet.insertRule('body.theme-light, body.theme-dark {}', 0); - } - - const colors = createCssVars('#4dd192', isDark ? 'dark' : 'light'); - const rule = primaryColorStyle.sheet.cssRules[0]; - - Object.entries(colors).forEach(([property, color]) => rule.style.setProperty(property.trim(), color)); - } else { - primaryColorStyle?.remove(); - } - const primeLabelKey = privateLabel ? 'harvester.branding.primeLabel' : 'harvester.branding.primeEmptyLabel'; store.dispatch('setIsSingleProduct', { @@ -169,6 +153,19 @@ export function init($plugin, store) { }, { immediate: true } ); + + const updateBrand = primeBrandUpdater(); + + // The value is read too: a change event from the server is loaded into the same setting object + store.watch( + (_state, getters) => { + const brandSetting = getters['management/byId'](MANAGEMENT.SETTING, SETTING.BRAND); + + return [!!getters['harvester-common/isHarvesterPrime'], brandSetting, brandSetting?.value]; + }, + ([isHarvesterPrime, brandSetting]) => updateBrand(brandSetting, isHarvesterPrime), + { immediate: true } + ); } product({ diff --git a/pkg/harvester/index.ts b/pkg/harvester/index.ts index 8acd4354..8270d904 100644 --- a/pkg/harvester/index.ts +++ b/pkg/harvester/index.ts @@ -6,6 +6,9 @@ import harvesterCommonStore from './store/harvester-common'; import harvesterStore from './store/harvester-store'; import customValidators from './validators'; import { PRODUCT_NAME } from './config/harvester'; +import { + hostResolvesL10nGlobals, resolveL10nGlobals, standaloneProductName, PRODUCT_NAME_GLOBAL +} from './utils/l10n-globals'; import { defineAsyncComponent } from 'vue'; import './styles/vue-flow.scss'; @@ -17,6 +20,15 @@ export default function (plugin: IPlugin) { // Auto-import model, detail, edit from the folders importTypes(plugin); + if (isSingleVirtualCluster) { + // Standalone: the server-flavor setting picks the product name, see config/harvester-cluster + plugin.register('l10n-global', PRODUCT_NAME_GLOBAL, standaloneProductName); + } else if (!hostResolvesL10nGlobals(plugin.environment?.version)) { + // Hosts older than Rancher 2.16 would show `[[Harvester]]` literally, so resolve the tokens here. + // Mutating the module in place also updates the copy that importTypes registered. + resolveL10nGlobals(require('./l10n/en-us.yaml')); + } + // Provide plugin metadata from package.json plugin.metadata = require('./package.json'); diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index 6c7d04a0..2fce5798 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -25,8 +25,8 @@ generic: unsupported: serverVersion: 'Current version: {serverVersion}' - description: Harvester ui only supports Harvester cluster version greater or equal to 1.3.0. - learnMore: Learn more about Harvester from the Harvester Web Site or read the Harvester Docs + description: '[[Harvester]] ui only supports [[Harvester]] cluster version greater or equal to 1.3.0.' + learnMore: Learn more about [[Harvester]] from the [[Harvester]] Web Site or read the [[Harvester]] Docs nav: group: @@ -87,10 +87,10 @@ harvester: primeLabel: '{vendor}' primeEmptyLabel: '' logos: - tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.' + tip: 'Upload a logo to replace the [[Harvester]] logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.' favicon: - tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB' - productLabel: 'Harvester' + tip: 'Upload an icon to replace the [[Harvester]] favicon in the browser tab. Max file size is 20KB' + productLabel: '[[Harvester]]' modal: backup: success: 'Backup { backUpName } has been initiated.' @@ -182,7 +182,7 @@ harvester: delete: Delete bundle: title: Generate a Support Bundle - titleDescription: Collect system-related logs in Harvester to assist with troubleshooting and support. + titleDescription: Collect system-related logs in [[Harvester]] to assist with troubleshooting and support. tip: Each field below specifies an aspect of the support bundle. For detailed explanations, please refer to the documentation. url: Issue URL description: Description @@ -336,7 +336,7 @@ harvester: attachedVM: Attached Virtual Machine cpuManager: CPU
Manager diskState: Disk - routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to. + routeConnectivityTooltip: Connectivity between the VM network and the management network, which the [[Harvester]] nodes are connected to. fingerprint: Fingerprint value: Value actions: Actions @@ -501,7 +501,7 @@ harvester: aclEmptyError: The fields in subnet access control list rule can not be empty. dashboard: label: Dashboard - header: "Harvester Cluster: {cluster}" + header: "[[Harvester]] Cluster: {cluster}" hardwareResourceGauge: cpu: CPU memory: Memory @@ -839,7 +839,7 @@ harvester: collapse: Hide VM-wide high-performance options blockMultiQueue: label: Virtio Block Multi-Queue - tip: Map disk I/O to multiple queues so it is processed across multiple CPUs. Applies to virtio disks and requires a fixed CPU allocation (Harvester always sets one). + tip: Map disk I/O to multiple queues so it is processed across multiple CPUs. Applies to virtio disks and requires a fixed CPU allocation ([[Harvester]] always sets one). noVirtioTip: Add at least one disk on the virtio bus to enable Block Multi-Queue. ioThreadsPolicy: label: I/O Threads Policy @@ -939,7 +939,7 @@ harvester: displayNamePlaceholder: Virtual machine alias name displayName: Display Name filesystem: - description: Harvester supports filesystem volumes for VM via virtiofs. + description: '[[Harvester]] supports filesystem volumes for VM via virtiofs.' type: Filesystem Type volume: Volume resource: Resource @@ -1193,8 +1193,8 @@ harvester: selectExisting: Select Existing Image createRepository: Creating Upgrade Repository succeeded: Succeeded - releaseTip: Please read the upgrade documentation carefully. You can view details on the Harvester Release Notes. - checkReady: I have read and understood the upgrade instructions related to this Harvester version. + releaseTip: Please read the upgrade documentation carefully. You can view details on the [[Harvester]] Release Notes. + checkReady: I have read and understood the upgrade instructions related to this [[Harvester]] version. pending: Pending upload: duplicatedFile: The file you are trying to upload already exists. @@ -1203,7 +1203,7 @@ harvester: os: OS kubernetes: Kubernetes monitoringChart: Monitoring Chart - harvesterChart: Harvester Chart + harvesterChart: '[[Harvester]] Chart' success: Success fail: Fail ongoing: On-going @@ -1243,7 +1243,7 @@ harvester: maxFailure: label: Max Failure count: Count - tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, Harvester controller will suspend the schedule job. This value should less than retain count + tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, [[Harvester]] controller will suspend the schedule job. This value should less than retain count virtualMachine: title: Virtual Machine Name placeholder: Select a virtual machine @@ -1257,7 +1257,7 @@ harvester: title: Restore Virtual Machine refreshInterval: label: Refresh Interval - tip: The interval for the backup target to sync data to harvester cluster. Set to 0 to disable refresh mechanism. + tip: The interval for the backup target to sync data to [[Harvester]] cluster. Set to 0 to disable refresh mechanism. backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported. message: noSetting: @@ -1276,7 +1276,7 @@ harvester: actionLabel: Test connection waitingLabel: Testing connection... successLabel: Connection tested - successMessage: 'Harvester can successfully connect to the endpoint {endpoint}' + successMessage: '[[Harvester]] can successfully connect to the endpoint {endpoint}' restore: backup: Backup deletePreviousVolumes: Delete Previous Volumes @@ -1487,7 +1487,7 @@ harvester: placeholder: Select a provider network networkPolicy: label: Policies - banner: The network policies must be used for VMs attached to overlay networks. Please read the harvester document how the network policy works. + banner: The network policies must be used for VMs attached to overlay networks. Please read the [[Harvester]] documentation on how network policies work. network: label: Virtual Machine Networks tabs: @@ -1596,7 +1596,7 @@ harvester: autoRotateRKE2Certs: expiringInHours: Expiring in httpProxy: - warning: 'WARNING:
If you configure "http-proxy" and "https-proxy", you must also put Harvester node's CIDR into "no-proxy", otherwise the Harvester cluster will be broken.' + warning: 'WARNING:
If you configure "http-proxy" and "https-proxy", you must also put [[Harvester]] node's CIDR into "no-proxy", otherwise the [[Harvester]] cluster will be broken.' supportBundleImage: imagePullPolicy: Pull Policy repo: Repository @@ -1648,7 +1648,7 @@ harvester: strategyType: Strategy Type concurrency: Concurrency harvesterMonitoring: - label: Harvester Monitoring + label: '[[Harvester]] Monitoring' section: prometheus: Prometheus prometheusNodeExporter: Prometheus Node Exporter @@ -1663,10 +1663,10 @@ harvester: url: URL insecureSkipTLSVerify: Insecure Skip TLS Verify tip: - prefix: Harvester secures cluster registration via TLS by default. If opt out "Insecure Skip TLS Verify", you must provide custom CA certificates using the + prefix: '[[Harvester]] secures cluster registration via TLS by default. If opt out "Insecure Skip TLS Verify", you must provide custom CA certificates using the' middle: 'additional-ca' suffix: setting. - message: To completely unset the imported Harvester cluster, please also remove it on the Rancher Dashboard UI via the Virtualization Management page. + message: To completely unset the imported [[Harvester]] cluster, please also remove it on the Rancher Dashboard UI via the Virtualization Management page. ntpServers: 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. @@ -1696,19 +1696,19 @@ harvester: networkData: Network Data support: - title: Harvester Support + title: '[[Harvester]] Support' kubeconfig: title: Download KubeConfig titleDescription: Download kubeconfig for debugging. internal: rancher: title: Access Embedded Rancher UI - 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 documentation. + 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 documentation. longhorn: title: Access Embedded Longhorn UI titleDescription: We only support to use the embedded Longhorn UI for debugging and validation purpose. community: - learnMore: Find out more about SUSE Harvester support + learnMore: Find out more about [[Harvester]] support pricing: Contact us for pricing namespace: @@ -1928,16 +1928,16 @@ harvester: addons: descriptions: - '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 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 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/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 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 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 virtual cluster (vcluster) with Rancher installed. - '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 add-on to help migrate virtual machine workloads from other source clusters to an existing Harvester cluster. - '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 add-on to enable vGPU devices and assign them to Harvester virtual machines.' - '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 add-on to collect Harvester cluster and virtual machine 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 add-on to help migrate virtual machine workloads from other source clusters to an existing [[Harvester]] cluster. + '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 add-on to enable vGPU devices and assign them to [[Harvester]] virtual machines.' + '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 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 virtual cluster (vcluster) with Rancher installed. 'k3k': k3k is an add-on to deploy the k3k controller. 'rancher-k3k': rancher-k3k deploys a k3k virtual cluster with Rancher installed. @@ -2006,7 +2006,7 @@ harvester: providerSelect: Provider createNew: Create new provider requirementsTitle: Requirements - requirementsText: "To migrate virtual machines from VMware, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from Harvester Node." + requirementsText: "To migrate virtual machines from VMware, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from [[Harvester]] Node." name: Name urlLabel: vCenter/ESXi URL urlPlaceholder: "https://vcenter.example.com/sdk" @@ -2059,7 +2059,7 @@ harvester: datastore: Datastore dashboard: title: Virtual Machine Migration Overview - description: "Migrate virtual machines from VMware vSphere to Harvester using cold migration for data consistency. Create a migration plan to select VMs, map networks and storage, and prepare a migration to Harvester." + description: "Migrate virtual machines from VMware vSphere to [[Harvester]] using cold migration for data consistency. Create a migration plan to select VMs, map networks and storage, and prepare a migration to [[Harvester]]." createPlan: Create Migration tableTitle: Migrations columns: @@ -2079,22 +2079,22 @@ harvester: checkMigratedVm: Click to check migrated VM details configureMappings: title: Set Mappings - description: Map VMware networks and datastores to Harvester and Longhorn target resources + description: Map VMware networks and datastores to [[Harvester]] and Longhorn target resources save: Save Mappings and Continue noTemplate: Start from scratch removeMap: Remove mapping networkMapping: title: Network Mapping - description: Map VMware port groups to Harvester networks - placeholder: Choose a Harvester network + description: Map VMware port groups to [[Harvester]] networks + placeholder: Choose a [[Harvester]] network template: Use existing network mapping as template options: podNetworking: Management Network ignored: Ignored (No NIC will be created) storageMapping: title: Storage Mapping - description: Map VMware datastores to Harvester storage classes - placeholder: Choose a Harvester Storage Class + description: Map VMware datastores to [[Harvester]] storage classes + placeholder: Choose a [[Harvester]] Storage Class template: Use existing storage mapping as template storageDefaults: title: 'Storage defaults — {name}' @@ -2413,7 +2413,7 @@ harvester: vGpuDevices: vGPU Devices showMore: Show More parentSriov: Filter By Parent SR-IOV GPU - noPermission: Please contact your system administrator to add Harvester add-ons first. + noPermission: Please contact your system administrator to add [[Harvester]] add-ons first. goSetting: prefix: The nvidia-driver-toolkit add-on is not enabled, click middle: here @@ -2449,7 +2449,7 @@ harvester: vgpu: label: vGPU Devices - noPermission: Please contact system administrator to add Harvester add-ons first. + noPermission: Please contact system administrator to add [[Harvester]] add-ons first. detachWarning: title: Cannot Disable vGPU message: Please detach the device from the VM and save it first before disabling this vGPU device. @@ -2470,7 +2470,7 @@ harvester: usb: label: USB Devices - noPermission: Please contact system admin to add Harvester add-ons first + noPermission: Please contact system admin to add [[Harvester]] add-ons first goSetting: prefix: The pcidevices-controller add-on is not enabled, click middle: here @@ -2531,7 +2531,7 @@ typeDescription: # Should fit on one line. # If you link to anything external, it MUST have # target="_blank" rel="noopener noreferrer nofollow" - harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme." + harvester: "Branding allows administrators to globally re-brand the UI by customizing the [[Harvester]] product name, logos and color scheme." advancedSettings: tags: @@ -2546,19 +2546,19 @@ advancedSettings: descriptions: 'harv-vlan': Default Network Interface name of the VLAN network. '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-online-expand-validation': Allow online volume expansion for specific CSI drivers. 'harv-containerd-registry': Containerd Registry Configuration to connect private registries. - 'harv-log-level': Configure Harvester server log level. Defaults to Info. - 'harv-server-version': Harvester server version. - 'harv-server-flavor': "Indicates the Harvester edition: Prime or Community." - '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 upgrade-checker-enabled is equal to True. - 'harv-ui-source': Configure how to load the UI source. The `External` option is used to load the UI from a ui-index URL, while the `Bundled` option loads the UI from the local Harvester server. + 'harv-log-level': Configure [[Harvester]] server log level. Defaults to Info. + 'harv-server-version': '[[Harvester]] server version.' + 'harv-server-flavor': "Indicates the [[Harvester]] edition: Prime or Community." + '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 upgrade-checker-enabled is equal to True. + 'harv-ui-source': Configure how to load the UI source. The `External` option is used to load the UI from a ui-index URL, while the `Bundled` option loads the UI from the local [[Harvester]] server. 'harv-ui-index': 'HTML index location for the UI.' '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-overcommit-config': 'Resource overcommit configuration.' 'harv-support-bundle-timeout': 'Support bundle timeout configuration in minutes, use 0 to disable the timeout.' @@ -2570,13 +2570,13 @@ advancedSettings: 'harv-storage-network': 'Longhorn storage-network setting.' 'harv-rwx-network': 'Configure RWX network behavior for shared or dedicated storage network usage.' 'harv-support-bundle-namespaces': Select additional namespaces to include in the support bundle. - 'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as virtual machine 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 rancher/support-bundle-kit. - 'harv-release-download-url': This setting allows you to configure the upgrade release download URL address. Harvester will get the ISO URL and checksum value from the ($URL/$VERSION/version.yaml) file hosted by the configured URL. + 'harv-release-download-url': This setting allows you to configure the upgrade release download URL address. [[Harvester]] will get the ISO URL and checksum value from the ($URL/$VERSION/version.yaml) file hosted by the configured URL. '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-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-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.
  • Changing this setting will restart RKE2 on all nodes. This will not affect running VM workloads.
  • 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.
' 'harv-longhorn-v2-data-engine-hugepage-enabled': 'Enable hugepages when using the Longhorn V2 data engine. Default is true. Disabling hugepages reduces memory pressure on low-spec nodes and increases deployment flexibility. However, performance may be lower compared to running with hugepages.' 'harv-longhorn-v2-data-engine-memory-size': 'Configure the amount of memory allocated to the SPDK target daemon when using the Longhorn V2 data engine. Default is 2048 MiB.' diff --git a/pkg/harvester/utils/__tests__/l10n-globals.test.js b/pkg/harvester/utils/__tests__/l10n-globals.test.js new file mode 100644 index 00000000..38e3932e --- /dev/null +++ b/pkg/harvester/utils/__tests__/l10n-globals.test.js @@ -0,0 +1,101 @@ +import fs from 'fs'; +import path from 'path'; +import { load } from 'js-yaml'; +import { hostResolvesL10nGlobals, resolveL10nGlobals, setStandalonePrime, standaloneProductName } from '../l10n-globals'; + +describe('hostResolvesL10nGlobals', () => { + it('should be true from Rancher 2.16', () => { + expect(hostResolvesL10nGlobals('v2.16.0')).toBe(true); + expect(hostResolvesL10nGlobals('v2.17.1')).toBe(true); + }); + + it('should be true for 2.16 head builds', () => { + expect(hostResolvesL10nGlobals('v2.16.0-68ddd75-head')).toBe(true); + expect(hostResolvesL10nGlobals('v2.16-136b11a0062247c1b28f107d82283bdb29bc07ae-head')).toBe(true); + }); + + it('should be false before Rancher 2.16', () => { + expect(hostResolvesL10nGlobals('v2.15.1')).toBe(false); + expect(hostResolvesL10nGlobals('v2.14.6')).toBe(false); + }); + + it('should be false when the Rancher version is missing or unknown', () => { + expect(hostResolvesL10nGlobals('')).toBe(false); + expect(hostResolvesL10nGlobals(undefined)).toBe(false); + expect(hostResolvesL10nGlobals('dev')).toBe(false); + }); +}); + +describe('standaloneProductName', () => { + it('should be Harvester when the server flavor is not prime', () => { + setStandalonePrime(false); + + expect(standaloneProductName()).toBe('Harvester'); + }); + + it('should be SUSE Virtualization when the server flavor is prime', () => { + setStandalonePrime(true); + + expect(standaloneProductName()).toBe('SUSE Virtualization'); + setStandalonePrime(false); + }); + + it('should go back to Harvester when the server flavor changes from prime', () => { + setStandalonePrime(true); + setStandalonePrime(false); + + expect(standaloneProductName()).toBe('Harvester'); + }); +}); + +describe('resolveL10nGlobals', () => { + it('should replace tokens with their name when no value is given', () => { + const translations = { support: { title: '[[Harvester]] Support' } }; + + resolveL10nGlobals(translations); + + expect(translations).toStrictEqual({ support: { title: 'Harvester Support' } }); + }); + + it('should replace every token in a string with the given value', () => { + const translations = { message: 'The [[Harvester]] UI only supports [[Harvester]] v1.3.0 and later' }; + + resolveL10nGlobals(translations, { Harvester: 'SUSE Virtualization' }); + + expect(translations.message).toBe('The SUSE Virtualization UI only supports SUSE Virtualization v1.3.0 and later'); + }); + + it('should keep an escaped token as a literal', () => { + const translations = { message: 'Use \\[[Harvester]] in a string' }; + + resolveL10nGlobals(translations); + + expect(translations.message).toBe('Use [[Harvester]] in a string'); + }); + + it('should leave strings without tokens and non-string values untouched', () => { + const translations = { + plain: 'harvester-seeder is an add-on', + count: 3, + nested: { link: 'site' }, + }; + + resolveL10nGlobals(translations); + + expect(translations).toStrictEqual({ + plain: 'harvester-seeder is an add-on', + count: 3, + nested: { link: 'site' }, + }); + }); + + it('should leave no token in the extension translations', () => { + const translations = load(fs.readFileSync(path.join(__dirname, '../../l10n/en-us.yaml'), 'utf8')); + + expect(JSON.stringify(translations)).toContain('[[Harvester]]'); + + resolveL10nGlobals(translations); + + expect(JSON.stringify(translations)).not.toContain('[['); + }); +}); diff --git a/pkg/harvester/utils/__tests__/prime-brand.test.js b/pkg/harvester/utils/__tests__/prime-brand.test.js new file mode 100644 index 00000000..a9c7d1de --- /dev/null +++ b/pkg/harvester/utils/__tests__/prime-brand.test.js @@ -0,0 +1,54 @@ +import { primeBrandUpdater } from '../prime-brand'; + +describe('primeBrandUpdater', () => { + it('should switch the brand to suse on Prime', () => { + const setting = { value: 'harvester' }; + + primeBrandUpdater()(setting, true); + + expect(setting.value).toBe('suse'); + }); + + it('should keep the brand of the server when not on Prime', () => { + const setting = { value: 'harvester' }; + + primeBrandUpdater()(setting, false); + + expect(setting.value).toBe('harvester'); + }); + + it('should put the brand of the server back when the flavor stops being Prime', () => { + const setting = { value: 'harvester' }; + const update = primeBrandUpdater(); + + update(setting, true); + update(setting, false); + + expect(setting.value).toBe('harvester'); + }); + + it('should switch the brand to suse again when the server sends its brand back on Prime', () => { + const setting = { value: 'harvester' }; + const update = primeBrandUpdater(); + + update(setting, true); + setting.value = 'harvester'; + update(setting, true); + + expect(setting.value).toBe('suse'); + }); + + it('should keep an empty brand of the server when not on Prime', () => { + const setting = { value: '' }; + const update = primeBrandUpdater(); + + update(setting, true); + update(setting, false); + + expect(setting.value).toBe(''); + }); + + it('should do nothing before the setting is loaded', () => { + expect(() => primeBrandUpdater()(undefined, true)).not.toThrow(); + }); +}); diff --git a/pkg/harvester/utils/l10n-globals.js b/pkg/harvester/utils/l10n-globals.js new file mode 100644 index 00000000..9182bcd7 --- /dev/null +++ b/pkg/harvester/utils/l10n-globals.js @@ -0,0 +1,49 @@ +import semver from 'semver'; + +// Rancher 2.16 resolves `[[name]]` tokens in translations from values registered as 'l10n-global' +export const L10N_GLOBALS_MIN_RANCHER_VERSION = '2.16.0'; + +// Name of the product name token, `[[Harvester]]`, and its value on Prime +export const PRODUCT_NAME_GLOBAL = 'Harvester'; +export const PRIME_PRODUCT_NAME = 'SUSE Virtualization'; + +const TOKEN = /(\\?)\[\[([\w-]+)\]\]/g; + +// Standalone has no Rancher Prime, the server-flavor setting decides instead +let standalonePrime = false; + +export function setStandalonePrime(isPrime) { + standalonePrime = !!isPrime; +} + +export function standaloneProductName() { + return standalonePrime ? PRIME_PRODUCT_NAME : PRODUCT_NAME_GLOBAL; +} + +/** + * Whether the host Rancher resolves `[[name]]` tokens itself. + * A missing or unknown version counts as an older host. + */ +export function hostResolvesL10nGlobals(version) { + const v = semver.coerce(version); + + return !!v && semver.gte(v, L10N_GLOBALS_MIN_RANCHER_VERSION); +} + +/** + * Replace `[[name]]` tokens in every string of `translations`, in place, the same way Rancher 2.16 does: + * a known name gets its value, an unknown one falls back to the name and `\[[name]]` stays a literal `[[name]]` + */ +export function resolveL10nGlobals(translations, values = {}) { + Object.keys(translations || {}).forEach((key) => { + const value = translations[key]; + + if (typeof value === 'string') { + translations[key] = value.replace(TOKEN, (_, escaped, name) => (escaped ? `[[${ name }]]` : (values[name] ?? name))); + } else if (value && typeof value === 'object') { + resolveL10nGlobals(value, values); + } + }); + + return translations; +} diff --git a/pkg/harvester/utils/prime-brand.js b/pkg/harvester/utils/prime-brand.js new file mode 100644 index 00000000..555d5f0f --- /dev/null +++ b/pkg/harvester/utils/prime-brand.js @@ -0,0 +1,26 @@ +import { BRAND } from '@shell/config/types'; + +/** + * Standalone Prime takes the SUSE theme of Rancher Prime: the `ui-brand` setting decides the stylesheet the shell + * loads, and only the `suse` brand has one. Returns an updater that switches the copy of the setting in the store, + * the server keeps its value, and puts the server's brand back when the flavor stops being Prime. + */ +export function primeBrandUpdater() { + let serverBrand; + + return (brandSetting, isPrime) => { + if (!brandSetting) { + return; + } + + if (brandSetting.value !== BRAND.SUSE) { + serverBrand = brandSetting.value; + } + + const brand = isPrime ? BRAND.SUSE : (serverBrand ?? brandSetting.value); + + if (brandSetting.value !== brand) { + brandSetting.value = brand; + } + }; +}