Compare commits

..
Author SHA1 Message Date
Harvester Bot 9b78eac733 chore: bump version to 1.9.0-rc5
Signed-off-by: Harvester Bot <94133267+harvesterhci-io-github-bot@users.noreply.github.com>
2026-08-12 05:03:54 +00:00
44 changed files with 153 additions and 1743 deletions
@@ -125,13 +125,6 @@ jobs:
node-version-file: '.nvmrc'
cache: 'yarn'
# Tag builds (RC, formal and sprint releases) ship the tag as the package
# version. RC/formal tags already match package.json; sprint tags
# (vX.Y.Z-dev-YYYYMMDD) only share its prefix, so pin it here for the build.
- name: Pin package version to tag
if: ${{ inputs.CI_BUILD_TAG != '' }}
run: ./scripts/bump_version.sh "${{ inputs.CI_BUILD_TAG }}"
- id: ci-build-pkg
name: Build pkg
run: ./scripts/ci-build-pkg.sh harvester
@@ -58,12 +58,6 @@ jobs:
- name: Setup Nodejs with yarn install
uses: ./.github/actions/setup
# The catalog image is tagged with the package.json version. Sprint tags
# (vX.Y.Z-dev-YYYYMMDD) only share its prefix, so pin it to the tag here.
- name: Pin package version to tag
if: ${{ github.ref_type == 'tag' }}
run: ./scripts/bump_version.sh "${{ github.ref_name }}"
- name: Build and push UI image
run: |
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "harvester-ui-extension",
"version": "1.9.1-rc3",
"version": "1.9.0-rc5",
"private": false,
"engines": {
"node": ">=24.0.0"
@@ -39,7 +39,7 @@
"merge": "2.1.1",
"node-forge": "1.4.0",
"nth-check": "2.1.1",
"qs": "6.16.0",
"qs": "6.15.3",
"roarr": "7.21.7",
"semver": "7.8.5",
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.8 KiB

+14 -21
View File
@@ -10,7 +10,6 @@ import { docLink } from '../utils/feature-flags';
const CATEGORY = {
ui: [
'branding',
HCI_SETTING.SERVER_FLAVOR,
'ui-source',
'ui-index',
]
@@ -54,7 +53,7 @@ export default {
};
},
computed: { ...mapGetters({ t: 'i18n/t', isHarvesterPrime: 'harvester-common/isHarvesterPrime' }) },
computed: { ...mapGetters({ t: 'i18n/t' }) },
watch: {
settings: {
@@ -232,21 +231,14 @@ export default {
v-if="setting.customized"
class="modified"
>
{{ t('advancedSettings.tags.modified.label') }}
Modified
</span>
<span
v-if="setting.experimental"
v-clean-tooltip="t('advancedSettings.tags.experimental.description')"
v-clean-tooltip="t('advancedSettings.experimental')"
class="experimental"
>
{{ t('advancedSettings.tags.experimental.label') }}
</span>
<span
v-if="setting.preview"
v-clean-tooltip="t('advancedSettings.tags.preview.description')"
class="preview"
>
{{ t('advancedSettings.tags.preview.label') }}
Experimental
</span>
</h1>
<h2 v-clean-html="t(setting.description, getDocLinkParams(setting) || {}, true)">
@@ -270,8 +262,7 @@ export default {
v-if="!setting.hide"
class="settings-value"
>
<pre v-if="setting.id === 'branding' && isHarvesterPrime">{{ $store.getters['harvester-common/privateLabel'] }}</pre>
<pre v-else-if="setting.kind === 'json'">{{ setting.json }}</pre>
<pre v-if="setting.kind === 'json'">{{ setting.json }}</pre>
<pre v-else-if="setting.kind === 'multiline'">{{ setting.data.value || setting.data.default }}</pre>
<pre v-else-if="setting.kind === 'enum'">{{ t(setting.enum) }}</pre>
<pre v-else-if="setting.kind === 'custom' && setting.custom">{{ setting.custom }}</pre>
@@ -361,19 +352,21 @@ export default {
flex: 1;
}
.modified,
.experimental,
.preview {
.modified {
margin-left: 10px;
border: 1px solid;
border: 1px solid var(--primary);
border-radius: 5px;
padding: 2px 10px;
font-size: 12px;
}
.modified { border-color: var(--primary); }
.experimental { border-color: var(--error); }
.preview { border-color: var(--warning); }
.experimental {
margin-left: 10px;
border: 1px solid var(--error);
border-radius: 5px;
padding: 2px 10px;
font-size: 12px;
}
.no-search-match {
text-align: center;
@@ -15,7 +15,7 @@ import MappingColumn from './MappingColumn.vue';
import StorageDefaultsModal from './StorageDefaultsModal.vue';
const DEFAULT_VOLUME_MODE = VOLUME_MODE.FILE_SYSTEM;
const DEFAULT_ACCESS_MODE = ACCESS_MODE?.READ_WRITE_MANY ?? 'ReadWriteMany';
const DEFAULT_ACCESS_MODES = [ACCESS_MODE.READ_WRITE_MANY];
const props = defineProps({
providerName: { type: String, default: '' },
@@ -268,9 +268,9 @@ const buildStorageEntries = () => {
capacity: 0,
target: '',
volumeMode: DEFAULT_VOLUME_MODE,
accessModes: [DEFAULT_ACCESS_MODE],
accessModes: [...DEFAULT_ACCESS_MODES],
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
inheritedAccessModes: [...DEFAULT_ACCESS_MODES],
inheritedFromProvider: false,
overridden: false,
usedBy: [],
@@ -310,9 +310,9 @@ const buildStorageEntriesFromProvider = (datastoresData) => {
capacity: ds.capacity || 0,
target: '',
volumeMode: DEFAULT_VOLUME_MODE,
accessModes: [DEFAULT_ACCESS_MODE],
accessModes: [...DEFAULT_ACCESS_MODES],
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
inheritedAccessModes: [...DEFAULT_ACCESS_MODES],
inheritedFromProvider: false,
overridden: false,
usedBy: [],
@@ -8,22 +8,16 @@ import { useI18n } from '@shell/composables/useI18n';
import { VOLUME_MODE, ACCESS_MODE } from '../../config/types';
const VOLUME_MODE_OPTIONS = [VOLUME_MODE.FILE_SYSTEM, VOLUME_MODE.BLOCK];
const {
READ_WRITE_ONCE = 'ReadWriteOnce',
READ_ONLY_MANY = 'ReadOnlyMany',
READ_WRITE_MANY = 'ReadWriteMany',
READ_WRITE_ONCE_POD = 'ReadWriteOncePod'
} = ACCESS_MODE || {};
const ACCESS_MODE_OPTIONS = [READ_WRITE_ONCE, READ_WRITE_MANY, READ_ONLY_MANY, READ_WRITE_ONCE_POD];
const ACCESS_MODE_OPTIONS = [ACCESS_MODE.READ_WRITE_ONCE, ACCESS_MODE.READ_WRITE_MANY, ACCESS_MODE.READ_ONLY_MANY];
const props = defineProps({
storageClassName: { type: String, default: '' },
providerName: { type: String, default: '' },
showInherited: { type: Boolean, default: false },
volumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
accessModes: { type: Array, default: () => ['ReadWriteMany'] },
accessModes: { type: Array, default: () => [ACCESS_MODE.READ_WRITE_MANY] },
inheritedVolumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
inheritedAccessModes: { type: Array, default: () => ['ReadWriteMany'] },
inheritedAccessModes: { type: Array, default: () => [ACCESS_MODE.READ_WRITE_MANY] },
});
const emit = defineEmits(['apply', 'close']);
@@ -32,7 +26,7 @@ const store = useStore();
const { t } = useI18n(store);
const localVolumeMode = ref(props.volumeMode || VOLUME_MODE.FILE_SYSTEM);
const localAccessMode = ref(props.accessModes?.[0] || READ_WRITE_MANY);
const localAccessMode = ref(props.accessModes?.[0] || ACCESS_MODE.READ_WRITE_MANY);
const volumeModeOptions = VOLUME_MODE_OPTIONS.map((value) => ({ label: value, value }));
const accessModeOptions = ACCESS_MODE_OPTIONS.map((value) => ({ label: value, value }));
@@ -48,7 +42,7 @@ const apply = () => {
// Repopulate the dropdowns with the provider default values; the user then applies.
const reset = () => {
localVolumeMode.value = props.inheritedVolumeMode || VOLUME_MODE.FILE_SYSTEM;
localAccessMode.value = props.inheritedAccessModes?.[0] || READ_WRITE_MANY;
localAccessMode.value = props.inheritedAccessModes?.[0] || ACCESS_MODE.READ_WRITE_MANY;
};
const cancel = () => {
-5
View File
@@ -77,11 +77,6 @@ const FEATURE_FLAGS = {
'staticIPForVM',
'fsFreezeDeadline',
],
'v1.9.1': [
'lvmDmThinOnly',
'highPerformanceStorage',
],
'v1.10.0': [],
};
const generateFeatureFlags = () => {
+9 -47
View File
@@ -57,8 +57,6 @@ import {
} from './table-headers';
import { ADD_ONS } from './harvester-map';
import { registerAddonSideNav } from '../utils/dynamic-nav';
import { setVendor } from '@shell/config/private-label';
import { createCssVars } from '@shell/utils/color';
const TEMPLATE = HCI.VM_VERSION;
const MONITORING_GROUP = 'Monitoring & Logging::Monitoring';
@@ -111,51 +109,15 @@ export function init($plugin, store) {
}
};
store.watch(
(_state, getters) => JSON.stringify([
!!getters['harvester-common/isHarvesterPrime'],
getters['prefs/theme'] === 'dark',
getters['harvester-common/privateLabel']
]),
(value) => {
const [isHarvesterPrime, isDark, privateLabel] = JSON.parse(value);
const primeLogo = isDark ? require('../assets/images/dark/suse-virtualization.svg') : require('../assets/images/suse-virtualization.svg');
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', {
productName: PRODUCT_NAME,
logo: isHarvesterPrime ? primeLogo : require(`@shell/assets/images/providers/harvester.svg`),
productNameKey: isHarvesterPrime ? primeLabelKey : 'harvester.productLabel',
getVersionInfo: (store) => store.getters[`${ PRODUCT_NAME }/byId`]?.(HCI.SETTING, 'server-version')?.value || 'unknown',
afterLoginRoute: home,
logoRoute: home,
supportCustomLogo: !isHarvesterPrime
});
},
{ immediate: true }
);
store.dispatch('setIsSingleProduct', {
productName: PRODUCT_NAME,
logo: require(`@shell/assets/images/providers/harvester.svg`),
productNameKey: 'harvester.productLabel',
getVersionInfo: (store) => store.getters[`${ PRODUCT_NAME }/byId`]?.(HCI.SETTING, 'server-version')?.value || 'unknown',
afterLoginRoute: home,
logoRoute: home,
supportCustomLogo: true
});
}
product({
-15
View File
@@ -39,21 +39,6 @@ export const VOLUME_TYPE = [{
value: 'cd-rom'
}];
// KubeVirt high-performance disk features
// https://kubevirt.io/user-guide/storage/disks_and_volumes/#high-performance-features
export const DISK_CACHE_MODE = ['', 'none', 'writeback', 'writethrough'];
export const DISK_IO_MODE = ['', 'native', 'threads'];
export const IO_THREADS_POLICY = ['', 'shared', 'auto', 'supplementalPool'];
// "Easy-to-consume" presets layered on top of the raw KubeVirt fields.
export const DISK_PERFORMANCE_PROFILE = {
DEFAULT: 'default',
HIGH: 'highPerformance',
CUSTOM: 'custom',
};
export const VOLUME_HOTPLUG_ACTION = {
INSERT_CDROM_IMAGE: 'INSERT_CDROM_IMAGE',
EJECT_CDROM_IMAGE: 'EJECT_CDROM_IMAGE',
@@ -33,11 +33,6 @@ export const HCI = {
CLONE_BACKEND_STORAGE_STATUS: 'harvesterhci.io/clone-backend-storage-status',
MIGRATION_STATE: 'harvesterhci.io/migrationState',
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
// Default KubeVirt high-performance disk settings, applied when the volume is
// attached to a VM as a disk. See disks_and_volumes.md#high-performance-features.
DISK_CACHE_MODE: 'harvesterhci.io/disk-cache-mode',
DISK_IO_MODE: 'harvesterhci.io/disk-io-mode',
DISK_DEDICATED_IOTHREAD: 'harvesterhci.io/disk-dedicated-iothread',
IMAGE_NAME: 'harvesterhci.io/image-name',
INIT_IP: 'etcd.rke2.cattle.io/node-address',
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
+1 -5
View File
@@ -3,7 +3,6 @@ export const HCI_SETTING = {
CONTAINERD_REGISTRY: 'containerd-registry',
LOG_LEVEL: 'log-level',
SERVER_VERSION: 'server-version',
SERVER_FLAVOR: 'server-flavor',
UI_INDEX: 'ui-index',
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
@@ -69,7 +68,6 @@ export const HCI_ALLOWED_SETTINGS = {
kind: 'json', from: 'import', featureFlag: 'csiOnlineExpandValidation'
},
[HCI_SETTING.SERVER_VERSION]: { readOnly: true },
[HCI_SETTING.SERVER_FLAVOR]: { readOnly: true },
[HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' },
[HCI_SETTING.UPGRADE_CHECKER_URL]: { kind: 'url' },
[HCI_SETTING.HTTP_PROXY]: { kind: 'json', from: 'import' },
@@ -113,17 +111,15 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'number', featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
kind: 'boolean',
preview: true,
experimental: true,
featureFlag: 'longhornV2LVMSupport'
},
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_HUGEPAGE_ENABLED]: {
kind: 'boolean',
preview: true,
featureFlag: 'longhornV2HugepageSettings'
},
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_MEMORY_SIZE]: {
kind: 'number',
preview: true,
featureFlag: 'longhornV2HugepageSettings'
},
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
@@ -214,14 +214,6 @@ export default {
this['diskRows'] = diskRows;
this['networkRows'] = this.getNetworkRows(neu, { fromTemplate: false, init: false });
// getInitConfig() seeds these on created(); refresh them here too so the
// VM-wide performance panel does not go stale when the spec changes.
const domain = neu?.spec?.template?.spec?.domain;
this['blockMultiQueue'] = domain?.devices?.blockMultiQueue || false;
this['ioThreadsPolicy'] = domain?.ioThreadsPolicy || '';
this['ioThreadCount'] = domain?.ioThreads?.supplementalPoolThreadCount || 2;
},
deep: true
}
@@ -262,9 +254,6 @@ export default {
:namespace="value.metadata.namespace"
:vm="value"
:resource-type="value.type"
:block-multi-queue="blockMultiQueue"
:io-threads-policy="ioThreadsPolicy"
:io-thread-count="ioThreadCount"
/>
</Tab>
@@ -401,17 +390,14 @@ export default {
:vmi-resource="vmi"
:vmim-resource="vmim"
/>
<div
<DashboardMetrics
v-if="showVmMetrics && liveMigrationProgressEnabled"
class="migration-metrics-scroll mb-30"
>
<DashboardMetrics
:detail-url="VM_MIGRATION_DETAIL_URL"
graph-height="960px"
:has-summary-and-detail="false"
:vars="graphVars"
/>
</div>
:detail-url="VM_MIGRATION_DETAIL_URL"
graph-height="640px"
:has-summary-and-detail="false"
:vars="graphVars"
class="mb-30"
/>
<Events
v-if="liveMigrationProgressEnabled"
:resource="vmi"
@@ -450,10 +436,3 @@ export default {
</Tabbed>
</div>
</template>
<style lang="scss" scoped>
.migration-metrics-scroll {
max-height: 640px;
overflow-y: auto;
}
</style>
-164
View File
@@ -1,164 +0,0 @@
<script>
import merge from 'lodash/merge';
import jsyaml from 'js-yaml';
import { mapGetters } from 'vuex';
import { Card } from '@components/Card';
import { LabeledInput } from '@components/Form/LabeledInput';
import AsyncButton from '@shell/components/AsyncButton';
import { escapeHtml } from '@shell/utils/string';
const DEFAULT_VALUE = {
rancher: {
hostname: '',
version: '',
bootstrapPassword: '',
},
};
export default {
name: 'HarvesterEnableK3k',
emits: ['close'],
components: {
AsyncButton,
Card,
LabeledInput,
},
props: {
resources: {
type: Array,
required: true
}
},
data() {
const addon = this.resources[0];
let valuesContentJson;
try {
valuesContentJson = merge({}, DEFAULT_VALUE, jsyaml.load(addon.spec.valuesContent));
} catch (e) {
valuesContentJson = merge({}, DEFAULT_VALUE);
}
return { valuesContentJson };
},
computed: {
...mapGetters({ t: 'i18n/t' }),
buttonDisabled() {
const { rancher } = this.valuesContentJson;
return !(rancher?.hostname || '').trim() || !(rancher?.version || '').trim() || !(rancher?.bootstrapPassword || '').trim();
}
},
methods: {
close() {
this.$emit('close');
},
async enable(buttonCb) {
const addon = this.resources[0];
try {
addon.spec.valuesContent = jsyaml.dump(this.valuesContentJson);
addon.spec.enabled = true;
await addon.save();
buttonCb(true);
this.close();
} catch (err) {
addon.spec.enabled = false;
this.$store.dispatch('growl/fromError', {
title: this.t('generic.notification.title.error', { name: escapeHtml(addon.metadata.name) }),
err,
}, { root: true });
buttonCb(false);
}
}
}
};
</script>
<template>
<Card :show-highlight-border="false">
<template #title>
<h4
v-clean-html="t('harvester.addons.rancherK3k.enable.title')"
class="text-default-text"
/>
</template>
<template #body>
<div class="body">
<div class="row mb-15">
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.rancher.hostname"
:required="true"
:label="t('harvester.addons.rancherK3k.hostname')"
placeholder="rancher.$vip.nip.io"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.rancher.version"
:required="true"
:label="t('harvester.addons.rancherK3k.version')"
placeholder="v1.14.0"
/>
</div>
</div>
<div class="row mb-15">
<div class="col span-12">
<LabeledInput
v-model:value="valuesContentJson.rancher.bootstrapPassword"
:required="true"
type="password"
:label="t('harvester.addons.rancherK3k.password')"
:tooltip="t('harvester.addons.rancherK3k.passwordTooltip')"
/>
</div>
</div>
</div>
</template>
<template #actions>
<div class="buttons actions">
<button
class="btn role-secondary mr-10"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="enable"
:disabled="buttonDisabled"
@click="enable"
/>
</div>
</template>
</Card>
</template>
<style lang="scss" scoped>
.body {
display: flex;
flex-direction: column;
min-width: 400px;
}
.actions {
width: 100%;
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
</style>
@@ -1,167 +0,0 @@
<script>
import merge from 'lodash/merge';
import jsyaml from 'js-yaml';
import { LabeledInput } from '@components/Form/LabeledInput';
import { RadioGroup } from '@components/Form/Radio';
const DEFAULT_VALUE = {
certManagerVersion: 'v1.20.2',
rancher: {
hostname: '',
version: '',
bootstrapPassword: '',
},
k3kCluster: {
servers: 1,
version: 'v1.35.4-k3s1',
storageClassName: 'harvester-longhorn',
},
};
export default {
name: 'EditAddonRancherK3k',
components: { LabeledInput, RadioGroup },
inheritAttrs: false,
props: {
value: {
type: Object,
required: true,
},
mode: {
type: String,
required: true
},
registerBeforeHook: {
type: Function,
required: true,
},
},
data() {
let valuesContentJson = {};
try {
valuesContentJson = merge({}, DEFAULT_VALUE, jsyaml.load(this.value.spec.valuesContent));
} catch (err) {
valuesContentJson = merge({}, DEFAULT_VALUE);
this.$store.dispatch('growl/fromError', {
title: this.$store.getters['i18n/t']('generic.notification.title.error'),
err: err.data || err,
}, { root: true });
}
return { valuesContentJson };
},
created() {
if (this.registerBeforeHook) {
this.registerBeforeHook(this.willSave, 'willSave');
}
},
methods: {
willSave() {
const errors = [];
if (!this.value.spec.enabled) {
return Promise.resolve();
}
if (!this.valuesContentJson.rancher.hostname) {
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.hostname') }, true));
}
if (!this.valuesContentJson.rancher.version) {
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.version') }, true));
}
if (!this.valuesContentJson.rancher.bootstrapPassword) {
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.password') }, true));
}
if (errors.length > 0) {
return Promise.reject(errors);
} else {
return Promise.resolve();
}
},
},
watch: {
valuesContentJson: {
handler(neu) {
this.value.spec['valuesContent'] = jsyaml.dump(neu);
},
deep: true,
immediate: true
},
},
};
</script>
<template>
<div>
<div class="row">
<div class="col span-12">
<RadioGroup
v-model:value="value.spec.enabled"
class="mb-20"
name="model"
:mode="mode"
:options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]"
/>
</div>
</div>
<template v-if="value.spec.enabled">
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.rancher.hostname"
label-key="harvester.addons.rancherK3k.hostname"
:required="true"
:mode="mode"
placeholder="rancher.$vip.nip.io"
/>
</div>
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.rancher.version"
label-key="harvester.addons.rancherK3k.version"
:required="true"
:mode="mode"
placeholder="v1.14.0"
/>
</div>
</div>
<div class="row mt-20">
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.rancher.bootstrapPassword"
label-key="harvester.addons.rancherK3k.password"
:mode="mode"
:required="true"
type="password"
:tooltip="t('harvester.addons.rancherK3k.passwordTooltip')"
/>
</div>
</div>
</template>
</div>
</template>
<style lang="scss" scoped>
:deep() .radio-group {
display: flex;
.radio-container {
margin-right: 30px;
}
}
</style>
@@ -7,7 +7,6 @@ import NameNsDescription from '@shell/components/form/NameNsDescription';
import YamlEditor from '@shell/components/YamlEditor';
import CreateEditView from '@shell/mixins/create-edit-view';
import FormValidation from '@shell/mixins/form-validation';
import { HCI } from '@pkg/harvester/config/labels-annotations';
export default {
@@ -24,19 +23,14 @@ export default {
NameNsDescription,
},
mixins: [CreateEditView, FormValidation],
mixins: [CreateEditView],
inheritAttrs: false,
data() {
return {
config: this.value.data?.cloudInit || '',
type: this.value?.metadata?.labels?.[HCI.CLOUD_INIT] || 'user',
fvFormRuleSets: [{
path: 'metadata.name',
rules: ['required'],
translationKey: 'nameNsDescription.name.label',
}],
config: this.value.data?.cloudInit || '',
type: this.value?.metadata?.labels?.[HCI.CLOUD_INIT] || 'user',
};
},
@@ -84,7 +78,6 @@ export default {
:resource="value"
:errors="errors"
:apply-hooks="applyHooks"
:validation-passed="fvFormIsValid"
@finish="save"
@cancel="done"
@error="e=>errors=e"
@@ -93,7 +86,6 @@ export default {
:value="value"
:mode="mode"
:namespaced="true"
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
@update:value="$emit('update:value', $event)"
/>
@@ -7,12 +7,8 @@ import { allHash } from '@shell/utils/promise';
import { clone } from '@shell/utils/object';
import { HCI } from '../../../types';
import { NODE } from '@shell/config/types';
import { _CREATE, _CLONE } from '@shell/config/query-params';
import { LVM_TOPOLOGY_LABEL } from '../index.vue';
const STRIPED_TYPE = 'striped';
const DM_THIN_TYPE = 'dm-thin';
const DEFAULT_PARAMETERS = [
'type',
'vgName'
@@ -58,17 +54,8 @@ export default {
data() {
const node = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).find((t) => t.key === LVM_TOPOLOGY_LABEL)?.values[0];
// striped is dropped once the LVM add-on reaches GA (v1.9.1); dm-thin is the only supported type.
const dmThinOnly = this.$store.getters['harvester-common/getFeatureEnabled']('lvmDmThinOnly');
const volumeGroupTypes = dmThinOnly ? [DM_THIN_TYPE] : [STRIPED_TYPE, DM_THIN_TYPE];
// Default new storage classes to dm-thin, and reset a cloned striped type that is no longer selectable.
if (dmThinOnly && [_CREATE, _CLONE].includes(this.realMode) && !volumeGroupTypes.includes(this.value.parameters.type)) {
this.value.parameters.type = DM_THIN_TYPE;
}
return {
volumeGroupTypes,
volumeGroupTypes: ['striped', 'dm-thin'],
node,
};
},
@@ -308,9 +308,6 @@ export default {
>
<Volume
v-model:value="diskRows"
v-model:block-multi-queue="blockMultiQueue"
v-model:io-threads-policy="ioThreadsPolicy"
v-model:io-thread-count="ioThreadCount"
:mode="mode"
:namespace="value.metadata.namespace"
:existing-volume-disabled="true"
+2 -40
View File
@@ -8,7 +8,6 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import Conditions from '@shell/components/form/Conditions';
import DiskPerformanceOptions from './kubevirt.io.virtualmachine/VirtualMachineVolume/DiskPerformanceOptions';
import { Banner } from '@components/Banner';
import { Checkbox } from '@components/Form/Checkbox';
import jsyaml from 'js-yaml';
@@ -46,8 +45,7 @@ export default {
LabeledSelect,
LabeledInput,
NameNsDescription,
Conditions,
DiskPerformanceOptions
Conditions
},
mixins: [CreateEditView],
@@ -100,15 +98,7 @@ export default {
createWithDataVolume: false,
snapshots: [],
images: [],
GIBIBYTE,
// Default KubeVirt high-performance disk profile, stored as annotations and
// applied when this volume is later attached to a VM as a disk.
perf: {
cache: get(this.value, `metadata.annotations."${ HCI_ANNOTATIONS.DISK_CACHE_MODE }"`) || '',
io: get(this.value, `metadata.annotations."${ HCI_ANNOTATIONS.DISK_IO_MODE }"`) || '',
dedicatedIOThread: get(this.value, `metadata.annotations."${ HCI_ANNOTATIONS.DISK_DEDICATED_IOTHREAD }"`) === 'true',
volumeMode: this.value?.spec?.volumeMode,
},
GIBIBYTE
};
},
@@ -340,7 +330,6 @@ export default {
}
},
'value.spec.volumeMode'(neu) {
this.perf.volumeMode = neu;
if (neu === VOLUME_MODE.FILE_SYSTEM) {
this.setVolumeForVmAnnotation();
} else if (neu === VOLUME_MODE.BLOCK ) {
@@ -455,23 +444,6 @@ export default {
imageAnnotations = { ...this.value.metadata.annotations };
}
// Persist the default high-performance disk profile as annotations.
if (this.perf.cache) {
imageAnnotations[HCI_ANNOTATIONS.DISK_CACHE_MODE] = this.perf.cache;
} else {
delete imageAnnotations[HCI_ANNOTATIONS.DISK_CACHE_MODE];
}
if (this.perf.io) {
imageAnnotations[HCI_ANNOTATIONS.DISK_IO_MODE] = this.perf.io;
} else {
delete imageAnnotations[HCI_ANNOTATIONS.DISK_IO_MODE];
}
if (this.perf.dedicatedIOThread) {
imageAnnotations[HCI_ANNOTATIONS.DISK_DEDICATED_IOTHREAD] = 'true';
} else {
delete imageAnnotations[HCI_ANNOTATIONS.DISK_DEDICATED_IOTHREAD];
}
const spec = {
...this.value.spec,
resources: { requests: { storage: this.storage } },
@@ -636,16 +608,6 @@ export default {
class="mb-20"
@update:value="update"
/>
<Banner
color="info"
:label="t('harvester.volume.performance.pvcTip')"
/>
<DiskPerformanceOptions
:value="perf"
:mode="mode"
@update="update"
/>
</Tab>
<Tab
v-if="!isCreate"
@@ -246,27 +246,4 @@ export default {
.group-actions {
display: inline;
}
// Match the enable/disable passthrough bulk-action buttons height to the .btn (40px) on the right.
:deep(.bulk .rc-button.btn-medium.bulk-action:not(.btn-sm)) {
min-height: 40px;
}
// Make the collapsed "Actions" dropdown button 40px too.
:deep(.bulk .rc-button.btn-medium.bulk-actions-dropdown:not(.btn-sm)) {
min-height: 40px;
}
// Lay the bulk row out with flex so the "N selected" label can wrap below the buttons.
:deep(.bulk) {
display: flex;
flex-wrap: wrap;
align-items: center;
}
:deep(.bulk .action-availability) {
flex-basis: 100%;
margin-left: 0;
margin-top: 6px;
}
</style>
@@ -5,7 +5,6 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import Banner from '@components/Banner/Banner.vue';
import remove from 'lodash/remove';
import { set } from '@shell/utils/object';
import { uniq } from '@shell/utils/array';
import { HCI } from '../../../types';
import DeviceList from './DeviceList';
import CompatibilityMatrix from '../CompatibilityMatrix';
@@ -61,15 +60,7 @@ export default {
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
});
// When off, read from spec; otherwise the spec name is a placeholder ('provisioned'),
// so read the real allocated device names from the deviceAllocationDetails annotation.
const hostDeviceNames = this.vm.isOff ? [
...vmDevices.map(({ name }) => name),
] : [
...Object.values(this.vm?.provisionedHostDevices || {}).reduce((acc, devices) => [...acc, ...devices], []),
];
uniq(hostDeviceNames).forEach((name) => {
vmDevices.forEach(({ name }) => {
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
selectedDevices.push(name);
}
@@ -1,321 +0,0 @@
<script>
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { Checkbox } from '@components/Form/Checkbox';
import { Banner } from '@components/Banner';
import { _VIEW } from '@shell/config/query-params';
import { DISK_CACHE_MODE, DISK_IO_MODE, DISK_PERFORMANCE_PROFILE } from '../../../config/harvester-map';
const { DEFAULT, HIGH, CUSTOM } = DISK_PERFORMANCE_PROFILE;
export default {
name: 'DiskPerformanceOptions',
components: {
LabeledSelect, Checkbox, Banner
},
emits: ['update'],
props: {
// The disk row. This component reads/writes value.cache, value.io,
// value.dedicatedIOThread and (for the "High Performance" preset) value.bus.
value: {
type: Object,
default: () => ({})
},
mode: {
type: String,
default: 'create'
},
},
data() {
const hasPerf = !!(this.value.cache || this.value.io || this.value.dedicatedIOThread);
return {
DISK_PERFORMANCE_PROFILE,
expanded: hasPerf,
profile: this.detectProfile(),
// Bus the disk used before the "High Performance" preset switched it to
// virtio, so returning to "Default" can put it back.
busBeforeHighProfile: null,
};
},
computed: {
isView() {
return this.mode === _VIEW;
},
storagePerformanceEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('highPerformanceStorage');
},
isCdRom() {
return this.value.type === 'cd-rom';
},
isCustom() {
return this.profile === CUSTOM;
},
isNativeIo() {
return this.value.io === 'native';
},
profileOptions() {
return [DEFAULT, HIGH, CUSTOM].map((value) => ({
label: this.t(`harvester.virtualMachine.volume.performance.profile.${ value }`),
value,
}));
},
cacheOptions() {
return DISK_CACHE_MODE.map((value) => ({
label: this.cacheLabel(value),
value,
// Native AIO only works with an uncached (O_DIRECT) disk, so while it is
// selected the only valid cache option is "none" — disable everything
// else, including "Default", to prevent an invalid combination.
disabled: this.isNativeIo && value !== 'none',
}));
},
ioOptions() {
return DISK_IO_MODE.map((value) => ({
label: value === '' ? this.t('harvester.virtualMachine.volume.performance.ioMode.default') : this.t(`harvester.virtualMachine.volume.performance.ioMode.${ value }`),
value,
}));
},
showFilesystemCacheWarning() {
return this.value.cache === 'none' && this.value.volumeMode === 'Filesystem';
},
showBusTip() {
const hasPerf = !!(this.value.cache || this.value.io || this.value.dedicatedIOThread);
return hasPerf && this.value.bus && this.value.bus !== 'virtio';
},
},
watch: {
// Keep the profile selector in sync if the row is repopulated (e.g. editing an existing VM).
'value.cache'() {
this.profile = this.detectProfile();
},
'value.io'() {
this.profile = this.detectProfile();
},
'value.dedicatedIOThread'() {
this.profile = this.detectProfile();
},
},
methods: {
detectProfile() {
const { cache, io, dedicatedIOThread } = this.value;
if (!cache && !io && !dedicatedIOThread) {
return DEFAULT;
}
if (cache === 'none' && io === 'native' && dedicatedIOThread) {
return HIGH;
}
return CUSTOM;
},
cacheLabel(value) {
if (value === '') {
return this.t('harvester.virtualMachine.volume.performance.cacheMode.default');
}
return this.t(`harvester.virtualMachine.volume.performance.cacheMode.${ value }`);
},
onProfileChange(profile) {
const previous = this.profile;
this.profile = profile;
if (profile === DEFAULT) {
this.value.cache = '';
this.value.io = '';
this.value.dedicatedIOThread = false;
// "Default" means no overrides, so also undo the virtio bus that the
// "High Performance" preset applied. Skip it if the user has since
// picked a different bus themselves — that choice is theirs to keep.
if (this.busBeforeHighProfile !== null) {
if (this.value.bus === 'virtio') {
this.value.bus = this.busBeforeHighProfile;
}
this.busBeforeHighProfile = null;
}
} else if (profile === HIGH) {
this.value.cache = 'none';
this.value.io = 'native';
this.value.dedicatedIOThread = true;
if (previous !== HIGH) {
this.busBeforeHighProfile = this.value.bus || '';
}
this.value.bus = 'virtio';
}
// CUSTOM keeps whatever is currently set, including the bus.
this.update();
},
onIoChange(io) {
this.value.io = io;
// Native AIO requires an uncached disk; force cache to "none" to keep the
// combo valid — including when cache is still on its "Default" ('') value,
// which libvirt would otherwise reject at domain start.
if (io === 'native' && this.value.cache !== 'none') {
this.value.cache = 'none';
}
this.update();
},
update() {
this.$emit('update');
},
},
};
</script>
<template>
<div
v-if="!isCdRom && storagePerformanceEnabled"
class="disk-performance"
>
<button
v-if="!isView"
type="button"
class="btn btn-sm role-link expand-toggle"
:aria-expanded="expanded"
:aria-label="expanded ? t('harvester.virtualMachine.volume.performance.collapse') : t('harvester.virtualMachine.volume.performance.expand')"
@click.prevent="expanded = !expanded"
>
<i
class="icon"
:class="expanded ? 'icon-chevron-down' : 'icon-chevron-right'"
/>
{{ t('harvester.virtualMachine.volume.performance.title') }}
</button>
<div
v-if="expanded || isView"
class="perf-body mt-10"
>
<div class="row mb-20">
<div
class="col span-6"
data-testid="input-disk-perf-profile"
>
<LabeledSelect
:value="profile"
:label="t('harvester.virtualMachine.volume.performance.profile.label')"
:tooltip="t('harvester.virtualMachine.volume.performance.profile.tip')"
:options="profileOptions"
:mode="mode"
@update:value="onProfileChange"
/>
</div>
</div>
<Banner
v-if="profile === DISK_PERFORMANCE_PROFILE.HIGH"
color="info"
:label="t('harvester.virtualMachine.volume.performance.highProfileTip')"
/>
<div
v-if="isCustom || isView"
class="row mb-20"
>
<div
class="col span-6"
data-testid="input-disk-cache"
>
<LabeledSelect
v-model:value="value.cache"
:label="t('harvester.virtualMachine.volume.performance.cacheMode.label')"
:tooltip="t('harvester.virtualMachine.volume.performance.cacheMode.tip')"
:options="cacheOptions"
:mode="mode"
@update:value="update"
/>
</div>
<div
class="col span-6"
data-testid="input-disk-io"
>
<LabeledSelect
:value="value.io"
:label="t('harvester.virtualMachine.volume.performance.ioMode.label')"
:tooltip="t('harvester.virtualMachine.volume.performance.ioMode.tip')"
:options="ioOptions"
:mode="mode"
@update:value="onIoChange"
/>
</div>
</div>
<div
v-if="isCustom || isView"
class="row mb-10"
>
<div
class="col span-12"
data-testid="input-disk-dedicated-iothread"
>
<Checkbox
v-model:value="value.dedicatedIOThread"
:label="t('harvester.virtualMachine.volume.performance.dedicatedIOThread.label')"
:tooltip="t('harvester.virtualMachine.volume.performance.dedicatedIOThread.tip')"
:mode="mode"
@update:value="update"
/>
</div>
</div>
<Banner
v-if="isNativeIo"
color="info"
:label="t('harvester.virtualMachine.volume.performance.ioMode.nativeRequiresNoCacheTip')"
/>
<Banner
v-if="showFilesystemCacheWarning"
color="warning"
:label="t('harvester.virtualMachine.volume.performance.cacheMode.filesystemWarning')"
/>
<Banner
v-if="showBusTip"
color="warning"
:label="t('harvester.virtualMachine.volume.performance.busTip')"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
.disk-performance {
border-top: 1px solid var(--border);
margin-top: 10px;
padding-top: 10px;
.expand-toggle {
padding: 0;
font-weight: 600;
.icon {
margin-right: 4px;
}
}
}
</style>
@@ -1,191 +0,0 @@
<script>
import InfoBox from '@shell/components/InfoBox';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { LabeledInput } from '@components/Form/LabeledInput';
import { Checkbox } from '@components/Form/Checkbox';
import { Banner } from '@components/Banner';
import { _VIEW } from '@shell/config/query-params';
import { IO_THREADS_POLICY } from '../../../config/harvester-map';
export default {
name: 'VMPerformanceOptions',
components: {
InfoBox, LabeledSelect, LabeledInput, Checkbox, Banner
},
emits: ['update:blockMultiQueue', 'update:ioThreadsPolicy', 'update:ioThreadCount'],
props: {
blockMultiQueue: {
type: Boolean,
default: false
},
ioThreadsPolicy: {
type: String,
default: ''
},
ioThreadCount: {
type: Number,
default: 2
},
mode: {
type: String,
default: 'create'
},
// Whether the current disk set contains a virtio disk (VM context only).
hasVirtioDisk: {
type: Boolean,
default: true
},
// Whether any disk requests a dedicated I/O thread (drives the auto-enable hint).
hasDedicatedIothread: {
type: Boolean,
default: false
},
},
data() {
// These are specialist tuning options, so keep the panel collapsed unless the
// VM already has something configured.
return { expanded: !!(this.blockMultiQueue || this.ioThreadsPolicy) };
},
computed: {
isView() {
return this.mode === _VIEW;
},
storagePerformanceEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('highPerformanceStorage');
},
ioThreadsPolicyOptions() {
return IO_THREADS_POLICY.map((value) => ({
label: value === '' ? this.t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.default') : this.t(`harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.${ value }`),
value,
}));
},
},
watch: {
// Block Multi-Queue only applies to virtio disks. If the last virtio disk is
// removed while it is enabled, clear it so the VM is not left with an invalid
// setting the disabled checkbox can no longer surface.
hasVirtioDisk(neu) {
if (!neu && this.blockMultiQueue) {
this.$emit('update:blockMultiQueue', false);
}
},
},
};
</script>
<template>
<InfoBox
v-if="storagePerformanceEnabled && (!isView || blockMultiQueue || !!ioThreadsPolicy)"
class="vm-perf mt-10"
>
<button
v-if="!isView"
type="button"
class="btn btn-sm role-link expand-toggle"
:aria-expanded="expanded"
:aria-label="expanded ? t('harvester.virtualMachine.volume.diskPerformance.collapse') : t('harvester.virtualMachine.volume.diskPerformance.expand')"
@click.prevent="expanded = !expanded"
>
<i
class="icon"
:class="expanded ? 'icon-chevron-down' : 'icon-chevron-right'"
/>
{{ t('harvester.virtualMachine.volume.diskPerformance.title') }}
</button>
<h3
v-else
class="mb-5"
>
{{ t('harvester.virtualMachine.volume.diskPerformance.title') }}
</h3>
<template v-if="expanded || isView">
<p class="text-muted mb-15 mt-10">
{{ t('harvester.virtualMachine.volume.diskPerformance.description') }}
</p>
<div class="row mb-10">
<div
class="col span-12"
data-testid="input-vm-block-multi-queue"
>
<Checkbox
:value="blockMultiQueue"
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.label')"
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.tip')"
:mode="mode"
:disabled="!hasVirtioDisk"
@update:value="$emit('update:blockMultiQueue', $event)"
/>
</div>
</div>
<Banner
v-if="!hasVirtioDisk && !isView"
color="info"
:label="t('harvester.virtualMachine.volume.diskPerformance.blockMultiQueue.noVirtioTip')"
/>
<div class="row mb-10">
<div
class="col span-6"
data-testid="input-vm-iothreads-policy"
>
<LabeledSelect
:value="ioThreadsPolicy"
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.label')"
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.tip')"
:options="ioThreadsPolicyOptions"
:mode="mode"
@update:value="$emit('update:ioThreadsPolicy', $event)"
/>
</div>
<div
v-if="ioThreadsPolicy === 'supplementalPool'"
class="col span-6"
data-testid="input-vm-iothread-count"
>
<LabeledInput
type="number"
min="1"
:value="ioThreadCount"
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.label')"
:tooltip="t('harvester.virtualMachine.volume.diskPerformance.ioThreadCount.tip')"
:mode="mode"
@update:value="$emit('update:ioThreadCount', Number($event))"
/>
</div>
</div>
<Banner
v-if="hasDedicatedIothread && !ioThreadsPolicy"
color="info"
:label="t('harvester.virtualMachine.volume.diskPerformance.ioThreadsPolicy.autoEnabledTip')"
/>
</template>
</InfoBox>
</template>
<style lang="scss" scoped>
.vm-perf {
.expand-toggle {
padding: 0;
font-weight: 600;
.icon {
margin-right: 4px;
}
}
}
</style>
@@ -1,125 +0,0 @@
import { mount } from '@vue/test-utils';
import { _EDIT } from '@shell/config/query-params';
import DiskPerformanceOptions from '../DiskPerformanceOptions.vue';
const mountOptions = (value, { featureEnabled = true } = {}) => ({
props: { value, mode: _EDIT },
global: {
mocks: {
$store: {
getters: {
'harvester-common/getFeatureEnabled': () => featureEnabled,
'i18n/t': (key) => key,
'i18n/exists': () => true,
}
},
t: (key) => key,
},
}
});
const hardDisk = (overrides = {}) => ({
type: 'disk', bus: 'virtio', cache: '', io: '', dedicatedIOThread: false, ...overrides
});
describe('component: DiskPerformanceOptions', () => {
it('is hidden when the highPerformanceStorage feature is disabled', () => {
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk(), { featureEnabled: false }));
expect(wrapper.find('.disk-performance').exists()).toBe(false);
});
it('renders for a hard disk when the feature is enabled', () => {
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk()));
expect(wrapper.find('.disk-performance').exists()).toBe(true);
});
it('applies the High Performance preset and emits update', () => {
const value = hardDisk();
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onProfileChange('highPerformance');
expect(value.cache).toBe('none');
expect(value.io).toBe('native');
expect(value.dedicatedIOThread).toBe(true);
expect(value.bus).toBe('virtio');
expect(wrapper.emitted('update')).toHaveLength(1);
});
it('clears all overrides when switching back to Default', () => {
const value = hardDisk({
cache: 'none', io: 'native', dedicatedIOThread: true
});
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onProfileChange('default');
expect(value.cache).toBe('');
expect(value.io).toBe('');
expect(value.dedicatedIOThread).toBe(false);
expect(wrapper.emitted('update')).toHaveLength(1);
});
it('restores the original bus when leaving High Performance for Default', () => {
const value = hardDisk({ bus: 'sata' });
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onProfileChange('highPerformance');
expect(value.bus).toBe('virtio');
wrapper.vm.onProfileChange('default');
expect(value.bus).toBe('sata');
});
it('keeps a bus the user picked themselves after High Performance', () => {
const value = hardDisk({ bus: 'sata' });
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onProfileChange('highPerformance');
value.bus = 'scsi';
wrapper.vm.onProfileChange('default');
expect(value.bus).toBe('scsi');
});
it('leaves the bus alone for a disk that never used the High Performance preset', () => {
const value = hardDisk({
bus: 'sata', cache: 'writeback', io: 'threads'
});
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onProfileChange('default');
expect(value.bus).toBe('sata');
});
it.each([
['a cached state', 'writeback'],
['the Default cache', ''],
])('forces cache to none when Native I/O is selected from %s', (_label, cache) => {
const value = hardDisk({ cache });
const wrapper = mount(DiskPerformanceOptions, mountOptions(value));
wrapper.vm.onIoChange('native');
expect(value.io).toBe('native');
expect(value.cache).toBe('none');
expect(wrapper.emitted('update')).toHaveLength(1);
});
it('disables every cache option except none while Native I/O is active', () => {
const wrapper = mount(DiskPerformanceOptions, mountOptions(hardDisk({ io: 'native' })));
const disabledByValue = wrapper.vm.cacheOptions.reduce((acc, opt) => {
acc[opt.value] = opt.disabled;
return acc;
}, {});
expect(disabledByValue['none']).toBe(false);
expect(disabledByValue['']).toBe(true);
expect(disabledByValue['writeback']).toBe(true);
expect(disabledByValue['writethrough']).toBe(true);
});
});
@@ -7,14 +7,13 @@ import UnitInput from '@shell/components/form/UnitInput';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard';
import VMPerformanceOptions from './VMPerformanceOptions';
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
import { PVC, STORAGE_CLASS } from '@shell/config/types';
import { clone } from '@shell/utils/object';
import { ucFirst, randomStr } from '@shell/utils/string';
import { removeObject } from '@shell/utils/array';
import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
import { HCI } from '../../../types';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
@@ -22,10 +21,10 @@ import { OFF } from '../../../models/kubevirt.io.virtualmachine';
import { EMPTY_IMAGE } from '../../../utils/vm';
export default {
emits: ['update:value', 'update:blockMultiQueue', 'update:ioThreadsPolicy', 'update:ioThreadCount'],
emits: ['update:value'],
components: {
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard, VMPerformanceOptions
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
},
props: {
@@ -76,22 +75,6 @@ export default {
resourceType: {
type: String,
default: ''
},
// KubeVirt high-performance VM-level features
blockMultiQueue: {
type: Boolean,
default: false
},
ioThreadsPolicy: {
type: String,
default: ''
},
ioThreadCount: {
type: Number,
default: 2
}
},
@@ -162,14 +145,6 @@ export default {
isLHV2VolExpansionFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
},
hasVirtioDisk() {
return this.rows.some((R) => R.type === 'disk' && R.bus === 'virtio');
},
hasDedicatedIOThread() {
return this.rows.some((R) => R.dedicatedIOThread);
},
},
watch: {
@@ -531,18 +506,6 @@ export default {
</button>
</div>
<VMPerformanceOptions
:block-multi-queue="blockMultiQueue"
:io-threads-policy="ioThreadsPolicy"
:io-thread-count="ioThreadCount"
:has-virtio-disk="hasVirtioDisk"
:has-dedicated-iothread="hasDedicatedIOThread"
:mode="mode"
@update:block-multi-queue="$emit('update:blockMultiQueue', $event)"
@update:io-threads-policy="$emit('update:ioThreadsPolicy', $event)"
@update:io-thread-count="$emit('update:ioThreadCount', $event)"
/>
<ModalWithCard
v-if="isOpen"
name="deleteTip"
@@ -4,7 +4,6 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import InputOrDisplay from '@shell/components/InputOrDisplay';
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
import { Banner } from '@components/Banner';
import DiskPerformanceOptions from '../DiskPerformanceOptions';
export default {
name: 'HarvesterEditContainer',
@@ -12,7 +11,7 @@ export default {
emits: ['update'],
components: {
LabeledInput, LabeledSelect, InputOrDisplay, Banner, DiskPerformanceOptions
LabeledInput, LabeledSelect, InputOrDisplay, Banner
},
props: {
@@ -154,11 +153,5 @@ export default {
class="mb-20"
:label="value.volumeBackups.error.message"
/>
<DiskPerformanceOptions
:value="value"
:mode="mode"
@update="update"
/>
</div>
</template>
@@ -14,7 +14,6 @@ import { VOLUME_MODE } from '@pkg/harvester/config/types';
import { HCI } from '../../../../types';
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
import { GIBIBYTE } from '../../../../utils/unit';
import DiskPerformanceOptions from '../DiskPerformanceOptions';
export default {
name: 'HarvesterEditExisting',
@@ -22,7 +21,7 @@ export default {
emits: ['update'],
components: {
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner, Checkbox, DiskPerformanceOptions
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner, Checkbox
},
props: {
@@ -173,14 +172,6 @@ export default {
this.value.storageClassName = pvcResource.spec.storageClassName;
this.value.volumeMode = pvcResource.spec.volumeMode;
this.value.shareable = false;
// Adopt the volume's default high-performance profile (if any) as a starting point.
const ann = pvcResource.metadata?.annotations || {};
this.value.cache = ann[HCI_ANNOTATIONS.DISK_CACHE_MODE] || '';
this.value.io = ann[HCI_ANNOTATIONS.DISK_IO_MODE] || '';
this.value.dedicatedIOThread = ann[HCI_ANNOTATIONS.DISK_DEDICATED_IOTHREAD] === 'true';
this.update();
},
@@ -388,11 +379,5 @@ export default {
class="mb-20"
:label="value.volumeBackups.error.message"
/>
<DiskPerformanceOptions
:value="value"
:mode="mode"
@update="update"
/>
</div>
</template>
@@ -15,7 +15,6 @@ import LabelValue from '@shell/components/LabelValue';
import { ucFirst } from '@shell/utils/string';
import { GIBIBYTE } from '../../../../utils/unit';
import { EMPTY_IMAGE } from '../../../../utils/vm';
import DiskPerformanceOptions from '../DiskPerformanceOptions';
export default {
name: 'HarvesterEditVMImage',
@@ -23,7 +22,7 @@ export default {
emits: ['update'],
components: {
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner, DiskPerformanceOptions
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner
},
props: {
@@ -483,11 +482,5 @@ export default {
color="error"
:label="t('harvester.virtualMachine.volume.vmImageVolumeTip', {diskSize: diskSize, imageVirtualSize: imageVirtualSize})"
/>
<DiskPerformanceOptions
:value="value"
:mode="mode"
@update="update"
/>
</div>
</template>
@@ -9,7 +9,6 @@ import { formatSi, parseSi } from '@shell/utils/units';
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
import { _VIEW } from '@shell/config/query-params';
import LabelValue from '@shell/components/LabelValue';
import DiskPerformanceOptions from '../DiskPerformanceOptions';
import { ucFirst } from '@shell/utils/string';
import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass';
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
@@ -23,7 +22,7 @@ export default {
emits: ['update'],
components: {
InputOrDisplay, Loading, LabeledInput, LabeledSelect, UnitInput, LabelValue, DiskPerformanceOptions
InputOrDisplay, Loading, LabeledInput, LabeledSelect, UnitInput, LabelValue
},
props: {
@@ -361,11 +360,6 @@ export default {
</InputOrDisplay>
</div>
</div>
<DiskPerformanceOptions
:value="value"
:mode="mode"
@update="update"
/>
<div class="row mb-20">
<div
v-if="value.volumeEncryptionFeatureEnabled && isView"
@@ -772,9 +772,6 @@ export default {
>
<Volume
v-model:value="diskRows"
v-model:block-multi-queue="blockMultiQueue"
v-model:io-threads-policy="ioThreadsPolicy"
v-model:io-thread-count="ioThreadCount"
:mode="mode"
:custom-volume-mode="customVolumeMode"
:namespace="value.metadata.namespace"
+1 -74
View File
@@ -84,8 +84,6 @@ dialog:
harvester:
branding:
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.'
favicon:
@@ -800,56 +798,6 @@ harvester:
setFirst: Set as root volume
saveVolume: Update Volume
encryption: Encryption
performance:
title: Storage Performance Options
expand: Show high-performance options
collapse: Hide high-performance options
profile:
label: Performance Profile
tip: "\"High Performance\" applies a tuned preset (no host cache, native AIO, a dedicated I/O thread on the virtio bus). Choose \"Custom\" to set each option yourself."
default: Default (Balanced)
highPerformance: High Performance
custom: Custom
highProfileTip: This disk will use the virtio bus with host cache disabled, native AIO and a dedicated I/O thread.
cacheMode:
label: Cache Mode
tip: "How the host caches disk I/O. \"None\" (host cache disabled) is best for high-throughput guests on block storage; \"WriteBack\" favours performance; \"WriteThrough\" favours durability."
default: Default (hypervisor)
none: None (no host cache)
writeback: WriteBack
writethrough: WriteThrough
filesystemWarning: "\"None\" (O_DIRECT) requires storage that supports direct I/O. It is recommended only for Block volume mode; on Filesystem volumes the VM may fail to start."
ioMode:
label: I/O Mode
tip: "How QEMU submits I/O. \"Native\" (Linux AIO) offers the lowest overhead but requires host cache set to \"None\"; \"Threads\" uses a host thread pool."
default: Default (hypervisor)
native: Native (AIO)
threads: Threads
nativeRequiresNoCacheTip: Native I/O mode requires cache mode "None"; it has been set for you.
dedicatedIOThread:
label: Dedicated I/O Thread
tip: Allocate an exclusive libvirt I/O thread to this disk instead of sharing one. Useful for disks with heavy I/O. Enabling this on any disk turns on the VM's I/O threads policy.
busTip: High-performance features work best on the virtio bus.
diskPerformance:
title: High Performance (I/O Threads and Multi-Queue)
description: These settings apply to all volumes in the Virtual Machine and complement the per-volume options above.
expand: Show VM-wide high-performance options
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).
noVirtioTip: Add at least one disk on the virtio bus to enable Block Multi-Queue.
ioThreadsPolicy:
label: I/O Threads Policy
tip: "How libvirt allocates I/O threads. \"Shared\" uses one thread for all disks; \"Auto\" pools threads round-robin (up to ~2x vCPUs); \"Supplemental Pool\" lets you pick a fixed thread count and adds matching CPUs to the pod."
default: "Disabled (default)"
shared: Shared
auto: Auto
supplementalPool: Supplemental Pool
autoEnabledTip: A disk requests a dedicated I/O thread, so the policy is enabled (defaults to "Shared").
ioThreadCount:
label: I/O Thread Count
tip: Number of I/O threads for the supplemental pool. 4-8 is a common range depending on workload.
shareable:
label: Shareable
tip: Allow multiple virtual machines to attach and write to this volume simultaneously.
@@ -1047,8 +995,6 @@ harvester:
createWithDataVolumeTooltip: Create Volume with Kubevirt/Containerized Data Importer way. It can fill accessMode/volumeMode automatically.
showAdvanced: Show Advanced Options
hideAdvanced: Hide Advanced Options
performance:
pvcTip: These high-performance options are saved as defaults on the volume and applied when it is attached to a virtual machine as a disk.
source: Source
kind: Kind
sourceOptions:
@@ -1886,8 +1832,6 @@ harvester:
'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.
'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 add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
'harvester-csi-driver-lvm': harvester-csi-driver-lvm is an add-on allowing users to create PVC through the LVM with local devices.
@@ -2192,14 +2136,6 @@ harvester:
hostname: Hostname
rancherVersion: Rancher Version
password: Bootstrap Password
rancherK3k:
accessRancher: Access the Rancher Dashboard
hostname: Hostname
version: Rancher Version
password: Bootstrap Password
passwordTooltip: The initial password in Rancher for the first time login.
enable:
title: Enable K3k
switchFailed: "{action} {name} failed!"
nvidiaDriverToolkit:
titles:
@@ -2481,15 +2417,7 @@ typeDescription:
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
advancedSettings:
tags:
modified:
label: Modified
experimental:
label: Experimental
description: 'Experimental: The feature is incomplete but its essential functionality is operational. The feature is not recommended for use in production environments.'
preview:
label: Technical Preview
description: 'Technical Preview: The feature is almost complete and its functionality is not expected to change significantly. Explore the feature extensively before using in production environments.'
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
descriptions:
'harv-vlan': Default Network Interface name of the VLAN network.
'harv-backup-target': Custom backup target to store virtual machine backups.
@@ -2499,7 +2427,6 @@ advancedSettings:
'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 <code>upgrade-checker-enabled</code> 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.
+1 -1
View File
@@ -72,7 +72,7 @@ export default {
/>
<a
v-if="['rancher-vcluster', 'rancher-k3k'].includes(scope.row.metadata.name) && scope.row.spec.enabled"
v-if="scope.row.metadata.name === 'rancher-vcluster' && scope.row.spec.enabled"
v-tooltip="t('harvester.addons.rancherVcluster.accessRancher')"
class="ml-5"
rel="nofollow noopener noreferrer"
-12
View File
@@ -38,10 +38,6 @@ export const SSH_EXISTING_TYPE = {
export default {
methods: {
hasCloudConfigComment(userScript) {
if (typeof userScript === 'string' && /^\s*#cloud-config(\s|$)/.test(userScript)) {
return true;
}
// Check that userData contains: #cloud-config
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
const items = userDataDoc?.contents?.items || [];
@@ -56,14 +52,6 @@ export default {
exist = true;
}
if (userDataDoc?.contents?.commentBefore === 'cloud-config' || userDataDoc?.contents?.commentBefore?.includes('cloud-config\n')) {
exist = true;
}
if (userDataDoc?.contents?.comment === 'cloud-config' || userDataDoc?.contents?.comment?.includes('cloud-config\n')) {
exist = true;
}
items.map((item) => {
const key = item.key;
+16 -93
View File
@@ -193,10 +193,6 @@ export default {
cpuPinning: false,
cpuModel: '',
sysprep: { secretName: '', xmlContent: '' },
// KubeVirt high-performance VM-level features
blockMultiQueue: false,
ioThreadsPolicy: '',
ioThreadCount: 2,
};
},
@@ -411,10 +407,6 @@ export default {
const cpuPinning = this.isCpuPinning(spec);
const cpuModel = spec.template.spec.domain.cpu?.model || '';
const blockMultiQueue = spec.template.spec.domain?.devices?.blockMultiQueue || false;
const ioThreadsPolicy = spec.template.spec.domain?.ioThreadsPolicy || '';
const ioThreadCount = spec.template.spec.domain?.ioThreads?.supplementalPoolThreadCount || 2;
const secretRef = this.getSecret(spec);
const accessCredentials = this.getAccessCredentials(spec);
@@ -453,10 +445,6 @@ export default {
this['cpuPinning'] = cpuPinning;
this['cpuModel'] = cpuModel;
this['blockMultiQueue'] = blockMultiQueue;
this['ioThreadsPolicy'] = ioThreadsPolicy;
this['ioThreadCount'] = ioThreadCount;
this['hasCreateVolumes'] = hasCreateVolumes;
this['networkRows'] = networkRows;
this['imageId'] = imageId;
@@ -513,20 +501,17 @@ export default {
}
out.push({
id: randomStr(5),
source: SOURCE_TYPE.IMAGE,
name: 'disk-0',
accessMode: 'ReadWriteMany', // root disk only support LHv1 volume, should be RWX
id: randomStr(5),
source: SOURCE_TYPE.IMAGE,
name: 'disk-0',
accessMode: 'ReadWriteMany', // root disk only support LHv1 volume, should be RWX
bus,
volumeName: '',
volumeName: '',
size,
type,
storageClassName: '',
image: this.imageId,
volumeMode: VOLUME_MODE.BLOCK,
cache: '',
io: '',
dedicatedIOThread: false,
storageClassName: '',
image: this.imageId,
volumeMode: VOLUME_MODE.BLOCK,
isEncrypted,
volumeBackups,
});
@@ -616,25 +601,22 @@ export default {
const volumeBackups = volBackups?.find((vBackup) => vBackup.volumeName === DISK.name) || null;
return {
id: randomStr(5),
id: randomStr(5),
bootOrder,
source,
name: DISK.name,
name: DISK.name,
realName,
bus,
volumeName,
container,
accessMode,
size: `${ formatSize }${ GIBIBYTE }`,
volumeMode: volumeMode || this.customVolumeMode,
size: `${ formatSize }${ GIBIBYTE }`,
volumeMode: volumeMode || this.customVolumeMode,
image,
type,
storageClassName,
hotpluggable,
cache: DISK?.cache || '',
io: DISK?.io || '',
dedicatedIOThread: DISK?.dedicatedIOThread || false,
shareable: DISK.shareable || false,
shareable: DISK.shareable || false,
volumeStatus,
dataSource,
namespace,
@@ -989,25 +971,9 @@ export default {
// `shareable` is an attachment-level opt-in, remove the field
// inherited from the old spec when the disk row no longer requests it
mergedDisks.forEach((disk) => {
const parsed = disks.find((D) => D.name === disk.name);
if (disk.shareable && !parsed?.shareable) {
if (disk.shareable && !disks.find((D) => D.name === disk.name)?.shareable) {
delete disk.shareable;
}
// High-performance fields are only serialized when requested (see
// parseDisk). When editing, mergeDeviceList would otherwise keep the
// previous spec's cache/io/dedicatedIOThread even after the row is
// switched back to "Default" or the dedicated I/O thread is unchecked,
// silently preserving stale values — strip them so the disk matches
// what the row actually requests.
if (parsed) {
['cache', 'io', 'dedicatedIOThread'].forEach((field) => {
if (!parsed[field] && field in disk) {
delete disk[field];
}
});
}
});
let spec = {
@@ -1044,35 +1010,6 @@ export default {
delete spec.template.spec.volumes;
}
// KubeVirt high-performance VM-level features (I/O threads & block multi-queue).
const domainRef = spec.template.spec.domain;
const hasDedicatedIOThread = mergedDisks.some((D) => D.dedicatedIOThread);
// If any disk requests a dedicated I/O thread, KubeVirt requires a policy;
// default it to "shared" so the dedicated thread is honored.
let ioThreadsPolicy = this.ioThreadsPolicy;
if (hasDedicatedIOThread && !ioThreadsPolicy) {
ioThreadsPolicy = 'shared';
}
if (ioThreadsPolicy) {
domainRef.ioThreadsPolicy = ioThreadsPolicy;
} else {
delete domainRef.ioThreadsPolicy;
}
if (ioThreadsPolicy === 'supplementalPool') {
domainRef.ioThreads = { supplementalPoolThreadCount: Number(this.ioThreadCount) || 1 };
} else {
delete domainRef.ioThreads;
}
if (this.blockMultiQueue) {
domainRef.devices.blockMultiQueue = true;
} else {
delete domainRef.devices.blockMultiQueue;
}
if (this.resourceType === HCI.VM) {
if (!this.isSingle) {
spec = this.multiVMScheduler(spec);
@@ -1272,10 +1209,9 @@ export default {
userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
const userDataYaml = userDataDoc.toString();
const userDataValue = userDataDoc.toJSON();
if (userDataValue === null || (typeof userDataValue === 'object' && !Array.isArray(userDataValue) && isEmpty(userDataValue))) {
// Empty userData should not create cloud-init content like `null`.
if (userDataYaml === '{}\n') {
// When the YAML parsed value is '{}\n', it means that the userData is empty, then undefined is returned.
return undefined;
}
@@ -1320,19 +1256,6 @@ export default {
if (R.type === HARD_DISK) {
out.disk = { bus: R.bus };
// KubeVirt high-performance per-disk features. `cache`, `io` and
// `dedicatedIOThread` live at the Disk level (siblings of `disk`), not
// inside the DiskTarget. See disks_and_volumes.md#high-performance-features.
if (R.cache) {
out.cache = R.cache;
}
if (R.io) {
out.io = R.io;
}
if (R.dedicatedIOThread) {
out.dedicatedIOThread = true;
}
} else if (R.type === CD_ROM) {
out.cdrom = { bus: R.bus };
}
@@ -5,7 +5,6 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
import { get } from '@shell/utils/object';
import { NAMESPACE } from '@shell/config/types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
import { FORKLIFT_NAMESPACE } from '@pkg/harvester/config/harvester-map';
import { HCI } from '../../types';
const OBSCURE_NAMESPACE_PREFIX = [
@@ -94,10 +93,6 @@ export default class HciNamespace extends namespace {
}
get isSystem() {
if (this.metadata?.name === FORKLIFT_NAMESPACE) {
return false;
}
const systemNamespaces = ['fleet-default'];
if (systemNamespaces.includes(this.metadata.name)) {
+6 -18
View File
@@ -5,14 +5,12 @@ import HarvesterResource from './harvester';
import { HCI } from '../types';
const HARVESTER_NVIDIA_DRIVER_TOOLKIT = 'harvester-system/nvidia-driver-toolkit';
const RANCHER_VCLUSTER = 'harvester-system/rancher-vcluster';
const RANCHER_K3K_ADDON = 'rancher-k3k/rancher-k3k';
export default class HciAddonConfig extends HarvesterResource {
get availableActions() {
const out = super._availableActions;
if (this.id === RANCHER_VCLUSTER || this.id === RANCHER_K3K_ADDON) {
if (this.id === 'harvester-system/rancher-vcluster') {
const rancherDashboard = {
action: 'goToRancher',
enabled: this.spec.enabled,
@@ -59,15 +57,6 @@ export default class HciAddonConfig extends HarvesterResource {
return;
}
if (!this.spec.enabled && this.id === RANCHER_K3K_ADDON) {
this.$dispatch('promptModal', {
resources: [this],
component: 'HarvesterEnableK3k',
});
return;
}
this.spec.enabled = !this.spec.enabled;
await this.save();
} catch (err) {
@@ -80,19 +69,18 @@ export default class HciAddonConfig extends HarvesterResource {
}
goToRancher() {
const valuesContent = jsyaml.load(this.spec.valuesContent);
window.open(
this.rancherHostname,
`https://${ valuesContent.hostname }`,
'_blank',
);
}
get rancherHostname() {
const valuesContent = jsyaml.load(this.spec.valuesContent) || {};
const valuesContent = jsyaml.load(this.spec.valuesContent);
// rancher-k3k nests the hostname under `rancher`, rancher-vcluster keeps it at the top level
const hostname = this.id === RANCHER_K3K_ADDON ? valuesContent.rancher?.hostname : valuesContent.hostname;
return `https://${ hostname }`;
return `https://${ valuesContent.hostname }`;
}
get stateColor() {
@@ -1315,18 +1315,6 @@ export default class VirtVm extends HarvesterResource {
}
}
get provisionedHostDevices() {
// Rancher-provisioned VMs store hostDevices.name as a placeholder ('provisioned'); the
// real allocated PCI device names live in this annotation, keyed by resourceName.
try {
const deviceAllocationDetails = JSON.parse(this.metadata?.annotations[HCI_ANNOTATIONS.VM_DEVICE_ALLOCATION_DETAILS] || '{}');
return deviceAllocationDetails?.hostdevices || {};
} catch (error) {
return {};
}
}
get schedulingVMBackupFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
}
@@ -1,14 +0,0 @@
import ManagementSetting from '@shell/models/management.cattle.io.setting';
import { HCI_SETTING } from '../../config/settings';
export default class HarvesterManagementSetting extends ManagementSetting {
get _availableActions() {
const actions = super._availableActions;
if (this.$rootGetters['isStandaloneHarvester'] && this.id === HCI_SETTING.UI_PL) {
return actions.filter((action) => !['goToClone', 'promptRemove'].includes(action.action));
}
return actions;
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.9.1-rc3",
"version": "1.9.0-rc5",
"private": false,
"rancher": {
"annotations": {
+2 -14
View File
@@ -63,7 +63,6 @@ export default {
return {
vendor: getVendor(),
uiPLSetting: {},
privateLabelDraft: null,
uiLogoDarkSetting: {},
uiLogoDark: '',
uiLogoLightSetting: {},
@@ -82,17 +81,6 @@ export default {
};
},
computed: {
isHarvesterPrime() {
return !!this.$store.getters['harvester-common/isHarvesterPrime'];
},
privateLabel: {
get() {
return this.privateLabelDraft ?? (this.isHarvesterPrime ? this.$store.getters['harvester-common/privateLabel'] : this.uiPLSetting.value);
},
set(value) {
this.privateLabelDraft = value;
}
},
mode() {
const schema = this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
@@ -124,7 +112,7 @@ export default {
this.errors.push(e);
},
async save(btnCB) {
this.uiPLSetting.value = (this.privateLabel || '').replaceAll(/[\<>&=#()"]/gm, '');
this.uiPLSetting.value = this.uiPLSetting.value.replaceAll(/[\<>&=#()"]/gm, '');
if (this.customizeLogo) {
this.uiLogoLightSetting.value = this.uiLogoLight;
this.uiLogoDarkSetting.value = this.uiLogoDark;
@@ -181,7 +169,7 @@ export default {
<div class="row mb-20">
<div class="col span-6">
<LabeledInput
v-model:value="privateLabel"
v-model:value="uiPLSetting.value"
:label="t('branding.uiPL.label')"
:mode="mode"
:maxlength="100"
-19
View File
@@ -1,6 +1,4 @@
import Parse from 'url-parse';
import { MANAGEMENT } from '@shell/config/types';
import { SETTING } from '@shell/config/settings';
import { HCI } from '../types';
import { PRODUCT_NAME } from '../config/harvester';
import { featureEnabled, getVersion } from '../utils/feature-flags';
@@ -66,23 +64,6 @@ const mutations = {
};
const getters = {
isHarvesterPrime: (_state, _getters, _rootState, rootGetters) => {
const setting = rootGetters['harvester/byId']?.(HCI.SETTING, 'server-flavor');
return (setting?.value || setting?.default) === 'prime';
},
privateLabel: (_state, getters, _rootState, rootGetters) => {
const setting = rootGetters['management/byId']?.(MANAGEMENT.SETTING, SETTING.PL);
const value = setting?.value || '';
if (getters.isHarvesterPrime && value === (setting?.default || 'Harvester')) {
return '';
}
return value;
},
getBundleId(state) {
return state.latestBundleId;
},
+2 -7
View File
@@ -1,5 +1,3 @@
import { FAVORITE_TYPES } from '@shell/store/prefs';
/**
* Dynamically toggles SideNav entries based on the enabled status of a specific Addon.
*
@@ -28,13 +26,10 @@ export function registerAddonSideNav(store, productName, {
// Toggle the trigger a few times so an early kick (fired before the SideNav
// has mounted on first login) is retried once the component is listening.
// Use the prefs/load mutation (in-memory only)
[0, 600, 1500].forEach((delay) => {
setTimeout(() => {
const base = (store.getters['prefs/get'](FAVORITE_TYPES) || []).filter((t) => t !== TRIGGER);
store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base, TRIGGER] });
setTimeout(() => store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base] }), 300);
store.dispatch('type-map/addFavorite', TRIGGER);
setTimeout(() => store.dispatch('type-map/removeFavorite', TRIGGER), 300);
}, delay);
});
};
+67 -163
View File
@@ -524,12 +524,12 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0", "@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d"
integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==
"@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
"@babel/core@^7.1.0", "@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
@@ -550,28 +550,7 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.7"
"@babel/helper-compilation-targets" "^7.29.7"
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helpers" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/template" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.29.0":
"@babel/generator@^7.29.0", "@babel/generator@^7.29.7", "@babel/generator@^7.7.2":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
@@ -582,17 +561,6 @@
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/generator@^7.29.7", "@babel/generator@^7.29.8", "@babel/generator@^7.7.2":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979"
integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
dependencies:
"@babel/parser" "^7.29.8"
"@babel/types" "^7.29.8"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.14.5", "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3", "@babel/helper-annotate-as-pure@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz#c70fe3c6ecbdc3fd2dd1b0f498428b88b82ce47f"
@@ -601,12 +569,12 @@
"@babel/types" "^7.29.7"
"@babel/helper-compilation-targets@^7.12.16", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
version "7.28.6"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25"
integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
dependencies:
"@babel/compat-data" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
"@babel/compat-data" "^7.28.6"
"@babel/helper-validator-option" "^7.27.1"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
@@ -657,22 +625,22 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.27.1", "@babel/helper-module-imports@^7.28.6", "@babel/helper-module-imports@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.27.1", "@babel/helper-module-imports@^7.28.6":
version "7.28.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c"
integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
version "7.28.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e"
integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==
dependencies:
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-module-imports" "^7.28.6"
"@babel/helper-validator-identifier" "^7.28.5"
"@babel/traverse" "^7.28.6"
"@babel/helper-optimise-call-expression@^7.29.7":
version "7.29.7"
@@ -722,10 +690,10 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.27.1", "@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
"@babel/helper-wrap-function@^7.27.1":
version "7.28.6"
@@ -754,14 +722,7 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.0", "@babel/parser@^7.29.0", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17"
integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==
dependencies:
"@babel/types" "^7.29.8"
"@babel/parser@^7.7.0":
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.0", "@babel/parser@^7.29.0", "@babel/parser@^7.29.7", "@babel/parser@^7.7.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
@@ -927,11 +888,11 @@
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.27.1":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz#622c16f9ad63782fe6e83dadc7e40330744b7f1e"
integrity sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==
version "7.28.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee"
integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-plugin-utils" "^7.28.6"
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
version "7.10.4"
@@ -1532,20 +1493,7 @@
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a"
integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.8"
"@babel/helper-globals" "^7.29.7"
"@babel/parser" "^7.29.8"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.8"
debug "^4.3.1"
"@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
@@ -1558,15 +1506,7 @@
"@babel/types" "^7.29.7"
debug "^4.3.1"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863"
integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
dependencies:
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/types@^7.29.0", "@babel/types@^7.7.0":
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
@@ -1739,20 +1679,13 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@eslint-community/eslint-utils@^4.2.0":
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/eslint-utils@^4.4.0":
version "4.10.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.4.0":
version "4.12.2"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
@@ -2810,9 +2743,9 @@
"@types/json-schema" "*"
"@types/estree@*", "@types/estree@^1.0.8":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33":
version "4.19.8"
@@ -3879,12 +3812,7 @@ acorn@^7.1.1, acorn@^7.4.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.0.4, acorn@^8.0.5, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940"
integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==
acorn@^8.2.4, acorn@^8.9.0:
acorn@^8.0.4, acorn@^8.0.5, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.2.4, acorn@^8.9.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
@@ -3933,7 +3861,7 @@ ajv-keywords@^5.1.0:
dependencies:
fast-deep-equal "^3.1.3"
ajv@^6.10.0, ajv@^6.12.4:
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5:
version "6.14.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
@@ -3943,16 +3871,6 @@ ajv@^6.10.0, ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^6.12.2, ajv@^6.12.5:
version "6.15.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492"
integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.9.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
@@ -6203,12 +6121,12 @@ end-of-stream@^1.1.0:
once "^1.4.0"
enhanced-resolve@^5.0.0, enhanced-resolve@^5.20.0:
version "5.24.5"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz#b4dad3255b7545f07ba5535189868e9f85f47573"
integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==
version "5.20.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz#eeeb3966bea62c348c40a0cc9e7912e2557d0be0"
integrity sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.3.3"
tapable "^2.3.0"
enquirer@^2.3.5:
version "2.4.1"
@@ -7275,7 +7193,7 @@ gopd@^1.0.1, gopd@^1.2.0:
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -7382,10 +7300,10 @@ hasha@^5.0.0:
is-stream "^2.0.0"
type-fest "^0.8.0"
hasown@^2.0.2, hasown@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
@@ -7824,14 +7742,7 @@ is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.13.0, is-core-module@^2.16.1:
version "2.16.2"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082"
integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==
dependencies:
hasown "^2.0.3"
is-core-module@^2.3.0:
is-core-module@^2.13.0, is-core-module@^2.16.1, is-core-module@^2.3.0:
version "2.16.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
@@ -8681,7 +8592,7 @@ js-yaml-loader@1.2.2:
loader-utils "^1.2.3"
un-eval "^1.2.0"
js-yaml@4.1.1:
js-yaml@4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
@@ -8696,13 +8607,6 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.1.0, js-yaml@^4.1.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.0.tgz#d1900572a7f7cf0b5f540c83673e60bad3436592"
integrity sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==
dependencies:
argparse "^2.0.1"
jsdom@^16.6.0:
version "16.7.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
@@ -9972,9 +9876,9 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
picomatch@^4.0.3:
version "4.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
pirates@^4.0.4:
version "4.0.7"
@@ -10241,9 +10145,9 @@ postcss-selector-parser@^6.0.15, postcss-selector-parser@^6.0.2, postcss-selecto
util-deprecate "^1.0.2"
postcss-selector-parser@^7.0.0:
version "7.1.5"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz#ee7b090724eb60739b530660e5b402200e694a97"
integrity sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==
version "7.1.1"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f"
integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@@ -10425,10 +10329,10 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qs@6.16.0, qs@^6.12.3, qs@~6.15.1:
version "6.16.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.0.tgz#c22c723a28a920f3aacdce8289fabd43eccb79fd"
integrity sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==
qs@6.15.3, qs@^6.12.3, qs@~6.15.1:
version "6.15.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
dependencies:
es-define-property "^1.0.1"
side-channel "^1.1.1"
@@ -11557,9 +11461,9 @@ tapable@^1.0.0:
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0:
version "2.3.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160"
integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==
version "2.3.2"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.2.tgz#86755feabad08d82a26b891db044808c6ad00f15"
integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==
terminal-link@^2.0.0:
version "2.1.1"
@@ -12672,9 +12576,9 @@ yargs@^16.0.0, yargs@^16.2.0:
yargs-parser "^20.2.2"
yargs@^17.0.0:
version "17.7.3"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.3.tgz#779dffe6bcafec596a7172e983289a588647faaa"
integrity sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"