mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-13 13:11:43 +00:00
Latest changes from harvester/master
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
parent
deeccf3db6
commit
ec3d88aeb7
@ -3,7 +3,7 @@ import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
|
||||
import Tag from '@shell/components/Tag';
|
||||
|
||||
export default {
|
||||
name: 'DiskTags',
|
||||
name: 'Tags',
|
||||
|
||||
components: { Tag },
|
||||
|
||||
|
||||
@ -35,13 +35,23 @@ export default {
|
||||
version: '',
|
||||
enableLogging: true,
|
||||
readyReleaseNote: false,
|
||||
isOpen: false,
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['currentCluster']),
|
||||
|
||||
latestUpgrade() {
|
||||
return this.upgrade?.find(u => u.isLatestUpgrade);
|
||||
},
|
||||
|
||||
isUpgradeInProgress() {
|
||||
return this.latestUpgrade &&
|
||||
!this.latestUpgrade.isUpgradeSucceeded &&
|
||||
!this.latestUpgrade.isUpgradeFailed;
|
||||
},
|
||||
|
||||
versionOptions() {
|
||||
const versions = this.$store.getters['harvester/all'](HCI.VERSION);
|
||||
|
||||
@ -133,7 +143,7 @@ export default {
|
||||
/>
|
||||
</h1>
|
||||
<button
|
||||
v-if="versionOptions.length"
|
||||
v-if="versionOptions.length && !isUpgradeInProgress"
|
||||
type="button"
|
||||
class="btn bg-warning btn-sm"
|
||||
@click="open"
|
||||
|
||||
@ -34,15 +34,7 @@ export default {
|
||||
},
|
||||
|
||||
data() {
|
||||
const categorySettings = this.settings.filter((s) => {
|
||||
if (this.category !== 'advanced') {
|
||||
return (CATEGORY[this.category] || []).find(item => item === s.id);
|
||||
} else if (this.category === 'advanced') {
|
||||
const allCategory = Object.keys(CATEGORY);
|
||||
|
||||
return !allCategory.some(category => (CATEGORY[category] || []).find(item => item === s.id));
|
||||
}
|
||||
}) || [];
|
||||
const categorySettings = this.filterCategorySettings();
|
||||
|
||||
return {
|
||||
HCI_SETTING,
|
||||
@ -52,7 +44,27 @@ export default {
|
||||
|
||||
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
||||
|
||||
watch: {
|
||||
settings: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this['categorySettings'] = this.filterCategorySettings();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
filterCategorySettings() {
|
||||
return this.settings.filter((s) => {
|
||||
if (this.category !== 'advanced') {
|
||||
return (CATEGORY[this.category] || []).find(item => item === s.id);
|
||||
} else if (this.category === 'advanced') {
|
||||
const allCategory = Object.keys(CATEGORY);
|
||||
|
||||
return !allCategory.some(category => (CATEGORY[category] || []).find(item => item === s.id));
|
||||
}
|
||||
}) || [];
|
||||
},
|
||||
showActionMenu(e, setting) {
|
||||
const actionElement = e.srcElement;
|
||||
|
||||
@ -104,7 +116,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(setting, i) in categorySettings" class="advanced-setting mb-20" :key="i" >
|
||||
<div v-for="(setting, i) in categorySettings" :key="i">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<h1>
|
||||
@ -112,6 +124,9 @@ export default {
|
||||
<span v-if="setting.customized" class="modified">
|
||||
Modified
|
||||
</span>
|
||||
<span v-if="setting.technicalPreview" v-clean-tooltip="t('advancedSettings.technicalPreview')" class="technical-preview">
|
||||
Technical Preview
|
||||
</span>
|
||||
</h1>
|
||||
<h2 v-clean-html="t(setting.description, {}, true)">
|
||||
</h2>
|
||||
@ -200,4 +215,12 @@ export default {
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.technical-preview {
|
||||
margin-left: 10px;
|
||||
border: 1px solid var(--warning);
|
||||
border-radius: 5px;
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { Banner } from '@components/Banner';
|
||||
import { DOC_LINKS } from '../config/doc-links';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterUpgradeInfo',
|
||||
@ -16,6 +17,10 @@ export default {
|
||||
computed: {
|
||||
releaseVersion() {
|
||||
return !!this.version ? `https://github.com/harvester/harvester/releases/tag/${ this.version }` : `https://github.com/harvester/harvester/releases`;
|
||||
},
|
||||
|
||||
upgradeLink() {
|
||||
return DOC_LINKS.UPGRADE_URL;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -26,15 +31,14 @@ export default {
|
||||
<Banner color="warning">
|
||||
<div>
|
||||
<strong>{{ t('harvester.upgradePage.upgradeInfo.warning') }}:</strong>
|
||||
<p v-clean-html="t('harvester.upgradePage.upgradeInfo.doc', {}, true)" class="mb-5">
|
||||
</p>
|
||||
<p v-clean-html="t('harvester.upgradePage.upgradeInfo.doc', {url: upgradeLink}, true)" class="mb-5"></p>
|
||||
|
||||
<p class="mb-5">
|
||||
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
|
||||
</p>
|
||||
|
||||
<p class="mb-5">
|
||||
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a :href="releaseVersion" target="_blank">{{ t('generic.moreInfo') }} </a>
|
||||
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a :href="releaseVersion" target="_blank">{{ t('generic.moreInfo') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</Banner>
|
||||
|
||||
@ -61,7 +61,7 @@ export default {
|
||||
window.open(
|
||||
url,
|
||||
'_blank',
|
||||
'toolbars=0,width=900,height=700,left=0,top=0,noreferrer'
|
||||
`toolbars=0,width=${ screen.width - 200 },height=${ screen.height - 200 },left=0,top=0,noreferrer`
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
<script>
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
|
||||
export default {
|
||||
name: 'AdditionalGuestMemoryOverheadRatio',
|
||||
|
||||
components: { LabeledInput },
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
||||
data() {
|
||||
return { ratio: this.value.value || this.value.default };
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.value['value'] = this.ratio;
|
||||
},
|
||||
|
||||
useDefault() {
|
||||
this['ratio'] = this.value.default;
|
||||
this.update();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<LabeledInput
|
||||
v-model:value="ratio"
|
||||
:label="t('harvester.setting.ratio')"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -10,6 +10,7 @@ import Tip from '@shell/components/Tip';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { NODE } from '@shell/config/types';
|
||||
import { HCI } from '../../types';
|
||||
import { DOC_LINKS } from '../../config/doc-links';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditStorageNetwork',
|
||||
@ -85,6 +86,9 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
storageNetworkExampleLink() {
|
||||
return DOC_LINKS.STORAGE_NETWORK_EXAMPLE;
|
||||
},
|
||||
clusterNetworkOptions() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
||||
@ -206,8 +210,8 @@ export default {
|
||||
:placeholder="t('harvester.setting.storageNetwork.range.placeholder')"
|
||||
label-key="harvester.setting.storageNetwork.range.label"
|
||||
/>
|
||||
<Tip class="mb-20" icon="icon icon-info" :text="t('harvester.setting.storageNetwork.tip')">
|
||||
<t k="harvester.setting.storageNetwork.tip" :raw="true" />
|
||||
<Tip class="mb-20" icon="icon icon-info">
|
||||
<t k="harvester.setting.storageNetwork.tip" :raw="true" :url="storageNetworkExampleLink" />
|
||||
</Tip>
|
||||
|
||||
<ArrayList
|
||||
|
||||
12
pkg/harvester/config/doc-links.js
Normal file
12
pkg/harvester/config/doc-links.js
Normal file
@ -0,0 +1,12 @@
|
||||
const pkgJson = require('../package.json');
|
||||
import semver from 'semver';
|
||||
|
||||
const docVersion = `v${ semver.major(pkgJson.version) }.${ semver.minor(pkgJson.version) }`;
|
||||
|
||||
export const DOC_LINKS = {
|
||||
CONSOLE_URL: `https://docs.harvesterhci.io/${ docVersion }/host/`,
|
||||
RANCHER_INTEGRATION_URL: `https://docs.harvesterhci.io/${ docVersion }/rancher/rancher-integration`,
|
||||
STORAGE_NETWORK_EXAMPLE: `https://docs.harvesterhci.io/${ docVersion }/advanced/storagenetwork#configuration-example`,
|
||||
KSMTUNED_MODE: `https://docs.harvesterhci.io/${ docVersion }/host/#ksmtuned-mode`,
|
||||
UPGRADE_URL: `https://docs.harvesterhci.io/${ docVersion }/upgrade/index`
|
||||
};
|
||||
@ -23,7 +23,7 @@ export const InterfaceOption = [{
|
||||
|
||||
export const SOURCE_TYPE = {
|
||||
NEW: 'New',
|
||||
IMAGE: 'VM Image',
|
||||
IMAGE: 'Virtual Machine Image',
|
||||
ATTACH_VOLUME: 'Existing Volume',
|
||||
CONTAINER: 'Container'
|
||||
};
|
||||
@ -41,7 +41,14 @@ export const ACCESS_CREDENTIALS = {
|
||||
INJECT_SSH: 'sshPublicKey'
|
||||
};
|
||||
|
||||
export const RunStrategys = ['Always', 'RerunOnFailure', 'Manual', 'Halted'];
|
||||
export const runStrategies = ['Always', 'RerunOnFailure', 'Manual', 'Halted'];
|
||||
|
||||
export const maintenanceStrategies = [
|
||||
'Migrate',
|
||||
'ShutdownAndRestartAfterEnable',
|
||||
'ShutdownAndRestartAfterDisable',
|
||||
'Shutdown'
|
||||
];
|
||||
|
||||
export const VOLUME_DATA_SOURCE_KIND = {
|
||||
VolumeSnapshot: 'VolumeSnapshot',
|
||||
@ -55,10 +62,10 @@ export const FLOW_TYPE = {
|
||||
};
|
||||
|
||||
export const ADD_ONS = {
|
||||
HARVESTER_SEEDER: 'harvester-seeder',
|
||||
PCI_DEVICE_CONTROLLER: 'pcidevices-controller',
|
||||
RANCHER_LOGGING: 'rancher-logging',
|
||||
RANCHER_MONITORING: 'rancher-monitoring',
|
||||
VM_IMPORT_CONTROLLER: 'vm-import-controller',
|
||||
NVIDIA_DRIVER_TOOLKIT_CONTROLLER: 'nvidia-driver-toolkit'
|
||||
HARVESTER_SEEDER: 'harvester-seeder',
|
||||
PCI_DEVICE_CONTROLLER: 'pcidevices-controller',
|
||||
NVIDIA_DRIVER_TOOLKIT_CONTROLLER: 'nvidia-driver-toolkit',
|
||||
RANCHER_LOGGING: 'rancher-logging',
|
||||
RANCHER_MONITORING: 'rancher-monitoring',
|
||||
VM_IMPORT_CONTROLLER: 'vm-import-controller',
|
||||
};
|
||||
|
||||
@ -1,49 +1,55 @@
|
||||
export const HCI = {
|
||||
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
|
||||
CURRENT_IP: 'rke2.io/internal-ip',
|
||||
OWNED_BY: 'harvesterhci.io/owned-by',
|
||||
IMAGE_ID: 'harvesterhci.io/imageId',
|
||||
SSH_NAMES: 'harvesterhci.io/sshNames',
|
||||
NETWORK_IPS: 'network.harvesterhci.io/ips',
|
||||
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
||||
CREATOR: 'harvesterhci.io/creator',
|
||||
OS: 'harvesterhci.io/os',
|
||||
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
||||
VM_NAME: 'harvesterhci.io/vmName',
|
||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
||||
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
||||
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
|
||||
RESTORE_NAME: 'restore.harvesterhci.io/name',
|
||||
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
|
||||
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
|
||||
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
|
||||
MIGRATION_STATE: 'harvesterhci.io/migrationState',
|
||||
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
|
||||
IMAGE_NAME: 'harvesterhci.io/image-name',
|
||||
INIT_IP: 'etcd.rke2.cattle.io/node-address',
|
||||
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
||||
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
||||
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
||||
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
||||
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
||||
REAY_MESSAGE: 'harvesterhci.io/read-message',
|
||||
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
|
||||
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
|
||||
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
||||
OS_TYPE: 'harvesterhci.io/os-type',
|
||||
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
||||
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
||||
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
||||
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
|
||||
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
|
||||
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
|
||||
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
|
||||
NODE_MANUFACTURER: 'manufacturer',
|
||||
NODE_MODEL: 'model',
|
||||
NODE_SERIAL_NUMBER: 'serialNumber',
|
||||
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
|
||||
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
|
||||
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
|
||||
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
|
||||
CURRENT_IP: 'rke2.io/internal-ip',
|
||||
IMAGE_ID: 'harvesterhci.io/imageId',
|
||||
SSH_NAMES: 'harvesterhci.io/sshNames',
|
||||
NETWORK_IPS: 'network.harvesterhci.io/ips',
|
||||
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
||||
CREATOR: 'harvesterhci.io/creator',
|
||||
OS: 'harvesterhci.io/os',
|
||||
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
||||
VM_NAME: 'harvesterhci.io/vmName',
|
||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
||||
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
||||
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
|
||||
RESTORE_NAME: 'restore.harvesterhci.io/name',
|
||||
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
|
||||
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
|
||||
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
|
||||
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
|
||||
MIGRATION_STATE: 'harvesterhci.io/migrationState',
|
||||
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
|
||||
IMAGE_NAME: 'harvesterhci.io/image-name',
|
||||
INIT_IP: 'etcd.rke2.cattle.io/node-address',
|
||||
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
||||
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
||||
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
|
||||
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
||||
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
||||
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
||||
REAY_MESSAGE: 'harvesterhci.io/read-message',
|
||||
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
|
||||
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
|
||||
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
||||
OS_TYPE: 'harvesterhci.io/os-type',
|
||||
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
|
||||
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
||||
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
||||
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
||||
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
|
||||
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
|
||||
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
|
||||
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
|
||||
NODE_MANUFACTURER: 'manufacturer',
|
||||
NODE_MODEL: 'model',
|
||||
NODE_SERIAL_NUMBER: 'serialNumber',
|
||||
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
|
||||
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
|
||||
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
|
||||
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
|
||||
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
|
||||
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
|
||||
CPU_MANAGER: 'cpumanager'
|
||||
};
|
||||
|
||||
@ -1,33 +1,39 @@
|
||||
export const HCI_SETTING = {
|
||||
BACKUP_TARGET: 'backup-target',
|
||||
CONTAINERD_REGISTRY: 'containerd-registry',
|
||||
LOG_LEVEL: 'log-level',
|
||||
SERVER_VERSION: 'server-version',
|
||||
UI_INDEX: 'ui-index',
|
||||
UI_PLUGIN_INDEX: 'ui-plugin-index',
|
||||
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
|
||||
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
|
||||
VLAN: 'vlan',
|
||||
UI_SOURCE: 'ui-source',
|
||||
UI_PL: 'ui-pl',
|
||||
HTTP_PROXY: 'http-proxy',
|
||||
ADDITIONAL_CA: 'additional-ca',
|
||||
OVERCOMMIT_CONFIG: 'overcommit-config',
|
||||
CLUSTER_REGISTRATION_URL: 'cluster-registration-url',
|
||||
DEFAULT_STORAGE_CLASS: 'default-storage-class',
|
||||
SUPPORT_BUNDLE_TIMEOUT: 'support-bundle-timeout',
|
||||
SUPPORT_BUNDLE_IMAGE: 'support-bundle-image',
|
||||
STORAGE_NETWORK: 'storage-network',
|
||||
VM_FORCE_RESET_POLICY: 'vm-force-reset-policy',
|
||||
SSL_CERTIFICATES: 'ssl-certificates',
|
||||
SSL_PARAMETERS: 'ssl-parameters',
|
||||
SUPPORT_BUNDLE_NAMESPACES: 'support-bundle-namespaces',
|
||||
AUTO_DISK_PROVISION_PATHS: 'auto-disk-provision-paths',
|
||||
RELEASE_DOWNLOAD_URL: 'release-download-url',
|
||||
CCM_CSI_VERSION: 'harvester-csi-ccm-versions',
|
||||
CSI_DRIVER_CONFIG: 'csi-driver-config',
|
||||
VM_TERMINATION_PERIOD: 'default-vm-termination-grace-period-seconds',
|
||||
NTP_SERVERS: 'ntp-servers',
|
||||
BACKUP_TARGET: 'backup-target',
|
||||
CONTAINERD_REGISTRY: 'containerd-registry',
|
||||
LOG_LEVEL: 'log-level',
|
||||
SERVER_VERSION: 'server-version',
|
||||
UI_INDEX: 'ui-index',
|
||||
UI_PLUGIN_INDEX: 'ui-plugin-index',
|
||||
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
|
||||
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
|
||||
VLAN: 'vlan',
|
||||
UI_SOURCE: 'ui-source',
|
||||
UI_PL: 'ui-pl',
|
||||
HTTP_PROXY: 'http-proxy',
|
||||
ADDITIONAL_CA: 'additional-ca',
|
||||
OVERCOMMIT_CONFIG: 'overcommit-config',
|
||||
CLUSTER_REGISTRATION_URL: 'cluster-registration-url',
|
||||
DEFAULT_STORAGE_CLASS: 'default-storage-class',
|
||||
SUPPORT_BUNDLE_TIMEOUT: 'support-bundle-timeout',
|
||||
SUPPORT_BUNDLE_EXPIRATION: 'support-bundle-expiration',
|
||||
SUPPORT_BUNDLE_IMAGE: 'support-bundle-image',
|
||||
SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT: 'support-bundle-node-collection-timeout',
|
||||
STORAGE_NETWORK: 'storage-network',
|
||||
VM_FORCE_RESET_POLICY: 'vm-force-reset-policy',
|
||||
SSL_CERTIFICATES: 'ssl-certificates',
|
||||
SSL_PARAMETERS: 'ssl-parameters',
|
||||
SUPPORT_BUNDLE_NAMESPACES: 'support-bundle-namespaces',
|
||||
AUTO_DISK_PROVISION_PATHS: 'auto-disk-provision-paths',
|
||||
RELEASE_DOWNLOAD_URL: 'release-download-url',
|
||||
CCM_CSI_VERSION: 'harvester-csi-ccm-versions',
|
||||
CSI_DRIVER_CONFIG: 'csi-driver-config',
|
||||
VM_TERMINATION_PERIOD: 'default-vm-termination-grace-period-seconds',
|
||||
NTP_SERVERS: 'ntp-servers',
|
||||
AUTO_ROTATE_RKE2_CERTS: 'auto-rotate-rke2-certs',
|
||||
KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
|
||||
LONGHORN_V2_DATA_ENGINE_ENABLED: 'longhorn-v2-data-engine-enabled',
|
||||
ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO: 'additional-guest-memory-overhead-ratio'
|
||||
};
|
||||
|
||||
export const HCI_ALLOWED_SETTINGS = {
|
||||
@ -41,6 +47,7 @@ export const HCI_ALLOWED_SETTINGS = {
|
||||
[HCI_SETTING.VLAN]: {
|
||||
kind: 'custom', from: 'import', alias: 'vlan'
|
||||
},
|
||||
[HCI_SETTING.AUTO_ROTATE_RKE2_CERTS]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.CSI_DRIVER_CONFIG]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.SERVER_VERSION]: { readOnly: true },
|
||||
[HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' },
|
||||
@ -49,14 +56,16 @@ export const HCI_ALLOWED_SETTINGS = {
|
||||
[HCI_SETTING.ADDITIONAL_CA]: {
|
||||
kind: 'multiline', canReset: true, from: 'import'
|
||||
},
|
||||
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: {},
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
|
||||
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.RANCHER_MANAGER_SUPPORT]: { kind: 'boolean' },
|
||||
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.SSL_PARAMETERS]: {
|
||||
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: {},
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: {},
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
|
||||
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.RANCHER_MANAGER_SUPPORT]: { kind: 'boolean' },
|
||||
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
||||
[HCI_SETTING.SSL_PARAMETERS]: {
|
||||
kind: 'json', from: 'import', canReset: true
|
||||
},
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_NAMESPACES]: { from: 'import', canReset: true },
|
||||
@ -75,6 +84,9 @@ export const HCI_ALLOWED_SETTINGS = {
|
||||
[HCI_SETTING.NTP_SERVERS]: {
|
||||
kind: 'json', from: 'import', canReset: true
|
||||
},
|
||||
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: {},
|
||||
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: { kind: 'boolean', technicalPreview: true },
|
||||
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
|
||||
};
|
||||
|
||||
export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = {
|
||||
|
||||
@ -11,6 +11,14 @@ export const IMAGE_DOWNLOAD_SIZE = {
|
||||
width: 120
|
||||
};
|
||||
|
||||
export const IMAGE_VIRTUAL_SIZE = {
|
||||
name: 'virtualSize',
|
||||
labelKey: 'harvester.tableHeaders.virtualSize',
|
||||
value: 'virtualSize',
|
||||
sort: 'status.virtualSize',
|
||||
width: 120
|
||||
};
|
||||
|
||||
export const IMAGE_PROGRESS = {
|
||||
name: 'Uploaded',
|
||||
labelKey: 'tableHeaders.progress',
|
||||
|
||||
@ -48,6 +48,13 @@ export default {
|
||||
customName() {
|
||||
return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME];
|
||||
},
|
||||
cpuManagerStatus() {
|
||||
if (this.value.isCPUManagerEnableInProgress) {
|
||||
return this.t('generic.loading');
|
||||
}
|
||||
|
||||
return this.t(`generic.${ this.value.isCPUManagerEnabled ? 'enabled' : 'disabled' }`);
|
||||
},
|
||||
|
||||
consoleUrl() {
|
||||
const consoleUrl = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
|
||||
@ -224,6 +231,9 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div v-if="!value.isEtcd" class="col span-6">
|
||||
<LabelValue :name="t('harvester.host.detail.cpuManager')" :value="cpuManagerStatus" />
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<LabelValue :name="t('harvester.host.detail.consoleUrl')" :value="consoleUrl.value">
|
||||
<a slot="value" :href="consoleUrl.value" target="_blank">{{ consoleUrl.display }}</a>
|
||||
@ -268,7 +278,7 @@ export default {
|
||||
<HarvesterStorageUsed
|
||||
:row="value"
|
||||
:resource-name="t('harvester.host.detail.storage')"
|
||||
:show-reserved="true"
|
||||
:show-allocated="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,13 +4,15 @@ import LabelValue from '@shell/components/LabelValue';
|
||||
import { BadgeState } from '@components/BadgeState';
|
||||
import { Banner } from '@components/Banner';
|
||||
import HarvesterDisk from '../../mixins/harvester-disk';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabelValue,
|
||||
BadgeState,
|
||||
Banner,
|
||||
Tag
|
||||
Tag,
|
||||
RadioGroup
|
||||
},
|
||||
|
||||
mixins: [
|
||||
@ -37,6 +39,18 @@ export default {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
targetDisk() {
|
||||
return this.disks.find(disk => disk.name === this.value.name);
|
||||
},
|
||||
schedulableTooltipMessage() {
|
||||
const { name, path } = this.value;
|
||||
|
||||
if (this.targetDisk && !this.targetDisk.allowScheduling && name && path) {
|
||||
return this.t('harvester.host.disk.allowScheduling.tooltip', { name, path });
|
||||
} else {
|
||||
return this.schedulableCondition.message;
|
||||
}
|
||||
},
|
||||
allowSchedulingOptions() {
|
||||
return [{
|
||||
label: this.t('generic.enabled'),
|
||||
@ -117,6 +131,16 @@ export default {
|
||||
</div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-12">
|
||||
<div class="pull-left">
|
||||
<RadioGroup
|
||||
v-model:value="value.allowScheduling"
|
||||
name="diskScheduling"
|
||||
:label="t('harvester.host.disk.allowScheduling.label')"
|
||||
:mode="mode"
|
||||
:options="allowSchedulingOptions"
|
||||
:row="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{ t('harvester.host.disk.conditions') }}:
|
||||
<BadgeState
|
||||
@ -127,9 +151,9 @@ export default {
|
||||
class="mr-10 ml-10 state"
|
||||
/>
|
||||
<BadgeState
|
||||
v-clean-tooltip="schedulableCondition.message"
|
||||
:color="schedulableCondition.status === 'True' ? 'bg-success' : 'bg-error' "
|
||||
:icon="schedulableCondition.status === 'True' ? 'icon-checkmark' : 'icon-warning' "
|
||||
v-clean-tooltip="schedulableTooltipMessage"
|
||||
:color="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'bg-success' : 'bg-error' "
|
||||
:icon="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'icon-checkmark' : 'icon-warning' "
|
||||
label="Schedulable"
|
||||
class="mr-10 state"
|
||||
/>
|
||||
|
||||
@ -104,7 +104,7 @@ export default {
|
||||
key-field="_key"
|
||||
|
||||
>
|
||||
<template cell:state="scope">
|
||||
<template cell:state="scope" class="state-col">
|
||||
<div class="state">
|
||||
<HarvesterVmState class="vmstate" :row="scope.row" :all-cluster-network="allClusterNetwork" />
|
||||
</div>
|
||||
|
||||
@ -311,14 +311,12 @@ export default {
|
||||
};
|
||||
}
|
||||
|
||||
const current = this.ntpSync?.currentNtpServers || '';
|
||||
|
||||
if (status === 'unsynced') {
|
||||
return {
|
||||
status: 'unsynced',
|
||||
warning: {
|
||||
key: 'harvester.host.ntp.ntpSyncStatus.isUnsynced',
|
||||
current
|
||||
key: 'harvester.host.ntp.ntpSyncStatus.isUnsynced',
|
||||
current: this.ntpSync?.currentNtpServers ? `<code>${ this.ntpSync.currentNtpServers }</code>` : '',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@ export default {
|
||||
return this.value?.downSize;
|
||||
},
|
||||
|
||||
virtualSize() {
|
||||
return this.value?.virtualSize;
|
||||
},
|
||||
|
||||
url() {
|
||||
return this.value?.spec?.url || '-';
|
||||
},
|
||||
@ -100,6 +104,12 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<LabelValue :name="t('harvester.image.virtualSize')" :value="virtualSize" class="mb-20" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<LabelValue :name="t('nameNsDescription.description.label')" :value="description" class="mb-20" />
|
||||
|
||||
@ -11,6 +11,7 @@ import NodeScheduling from '@shell/components/form/NodeScheduling';
|
||||
import PodAffinity from '@shell/components/form/PodAffinity';
|
||||
import KeyValue from '@shell/components/form/KeyValue';
|
||||
import Labels from '@shell/components/form/Labels';
|
||||
import LabelValue from '@shell/components/LabelValue';
|
||||
import { HCI } from '../../types';
|
||||
import VM_MIXIN from '../../mixins/harvester-vm';
|
||||
|
||||
@ -22,6 +23,7 @@ import Events from './VirtualMachineTabs/VirtualMachineEvents';
|
||||
import Migration from './VirtualMachineTabs/VirtualMachineMigration';
|
||||
import OverviewBasics from './VirtualMachineTabs/VirtualMachineBasics';
|
||||
import OverviewKeypairs from './VirtualMachineTabs/VirtualMachineKeypairs';
|
||||
import { formatSi } from '@shell/utils/units';
|
||||
|
||||
const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1';
|
||||
|
||||
@ -33,6 +35,7 @@ export default {
|
||||
Tabbed,
|
||||
Events,
|
||||
OverviewBasics,
|
||||
LabelValue,
|
||||
Volume,
|
||||
Network,
|
||||
OverviewKeypairs,
|
||||
@ -57,15 +60,18 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
switchToCloud: false,
|
||||
hasResourceQuotaSchema: false,
|
||||
switchToCloud: false,
|
||||
VM_METRICS_DETAIL_URL,
|
||||
showVmMetrics: false,
|
||||
showVmMetrics: false,
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
this.hasResourceQuotaSchema = !!this.$store.getters[`${ inStore }/schemaFor`](HCI.RESOURCE_QUOTA);
|
||||
|
||||
const hash = {
|
||||
pods: this.$store.dispatch(`${ inStore }/findAll`, { type: POD }),
|
||||
services: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE }),
|
||||
@ -75,6 +81,10 @@ export default {
|
||||
restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }),
|
||||
};
|
||||
|
||||
if (this.hasResourceQuotaSchema) {
|
||||
hash.resourceQuotas = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESOURCE_QUOTA });
|
||||
}
|
||||
|
||||
await allHash(hash);
|
||||
|
||||
setPromiseResult(
|
||||
@ -88,6 +98,22 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['currentCluster']),
|
||||
|
||||
totalSnapshotSize() {
|
||||
if (this.value.snapshotSizeQuota === undefined || this.value.snapshotSizeQuota === null) {
|
||||
return ' - ';
|
||||
}
|
||||
|
||||
if (this.value.snapshotSizeQuota === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return formatSi(this.value.snapshotSizeQuota, {
|
||||
increment: 1024,
|
||||
addSuffix: true,
|
||||
suffix: 'i',
|
||||
});
|
||||
},
|
||||
|
||||
vmi() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
@ -175,10 +201,17 @@ export default {
|
||||
<Network v-model:value="networkRows" mode="view" />
|
||||
</Tab>
|
||||
|
||||
<Tab name="keypairs" :label="t('harvester.virtualMachine.detail.tabs.keypairs')" class="bordered-table" :weight="3">
|
||||
<Tab name="keypairs" :label="t('harvester.virtualMachine.detail.tabs.keypairs')" class="bordered-table" :weight="4">
|
||||
<OverviewKeypairs :value="value" />
|
||||
</Tab>
|
||||
|
||||
<Tab v-if="hasResourceQuotaSchema" name="quotas" :label="t('harvester.tab.quotas')" :weight="3">
|
||||
<LabelValue
|
||||
:name="t('harvester.snapshot.totalSnapshotSize')"
|
||||
:value="totalSnapshotSize"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
v-if="showVmMetrics"
|
||||
name="vm-metrics"
|
||||
|
||||
@ -8,6 +8,7 @@ import { STATE, NAME, AGE } from '@shell/config/table-headers';
|
||||
import { matching } from '@shell/utils/selector';
|
||||
import { NODE } from '@shell/config/types';
|
||||
import { isEmpty } from '@shell/utils/object';
|
||||
import { HCI } from '@pkg/harvester/config/labels-annotations';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -55,10 +56,13 @@ export default {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const nodes = this.$store.getters[`${ inStore }/all`](NODE);
|
||||
const matchedNodes = this.value?.metadata?.annotations?.[HCI.MATCHED_NODES];
|
||||
const selector = this.value?.spec?.nodeSelector;
|
||||
|
||||
if (!isEmpty(selector)) {
|
||||
return matching(nodes, selector);
|
||||
} else if (matchedNodes && matchedNodes.length > 0) {
|
||||
return nodes.filter(node => matchedNodes.includes(node.id));
|
||||
} else {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { Banner } from '@components/Banner';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||
import { BadgeState } from '@components/BadgeState';
|
||||
import { ucFirst } from '@shell/utils/string';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -25,9 +26,9 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
unhealthyVM: '',
|
||||
force: false
|
||||
errors: [],
|
||||
unhealthyVMs: [],
|
||||
force: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -40,13 +41,21 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
ucFirst,
|
||||
|
||||
onInputForce(v) {
|
||||
if (v) {
|
||||
this.unhealthyVMs = [];
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
|
||||
async apply(buttonCb) {
|
||||
this.errors = [];
|
||||
this.unhealthyVM = '';
|
||||
this.unhealthyVMs = [];
|
||||
|
||||
try {
|
||||
const res = await this.actionResource.doAction('maintenancePossible');
|
||||
@ -62,8 +71,8 @@ export default {
|
||||
} else if (res._status === 200 || res._status === 204) {
|
||||
const res = await this.actionResource.doAction('listUnhealthyVM');
|
||||
|
||||
if (res.message) {
|
||||
this.unhealthyVM = res;
|
||||
if (res?.length) {
|
||||
this.unhealthyVMs = res;
|
||||
buttonCb(false);
|
||||
} else {
|
||||
await this.actionResource.doAction('enableMaintenanceMode', { force: 'false' });
|
||||
@ -95,15 +104,20 @@ export default {
|
||||
<Checkbox
|
||||
v-model:value="force"
|
||||
label-key="harvester.host.enableMaintenance.force"
|
||||
@update:value="onInputForce"
|
||||
/>
|
||||
</div>
|
||||
<Banner color="warning" :label="t('harvester.host.enableMaintenance.protip')" class="mb-0" />
|
||||
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
|
||||
|
||||
<div v-if="unhealthyVM">
|
||||
<Banner color="error mb-5">
|
||||
<Banner color="warning" :label="t('harvester.host.enableMaintenance.protip')" />
|
||||
|
||||
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="ucFirst(err)" />
|
||||
|
||||
<Banner v-if="!force" class="mt-0" color="warning" :labelKey="'harvester.host.enableMaintenance.shutDownVMs'" />
|
||||
|
||||
<div v-for="(unhealthyVM, i) in unhealthyVMs" :key="i">
|
||||
<Banner color="error mt-0 mb-5">
|
||||
<p>
|
||||
{{ unhealthyVM.message }}
|
||||
{{ ucFirst(unhealthyVM.message) }}
|
||||
</p>
|
||||
</Banner>
|
||||
|
||||
|
||||
@ -72,8 +72,11 @@ export default {
|
||||
const nodes = this.$store.getters['harvester/all'](NODE);
|
||||
|
||||
return nodes.filter((n) => {
|
||||
// do not allow to migrate to self node
|
||||
return !!this.availableNodes.includes(n.id);
|
||||
const isNotSelfNode = !!this.availableNodes.includes(n.id);
|
||||
const isNotWitnessNode = n.isEtcd !== 'true'; // do not allow to migrate to self node and witness node
|
||||
const matchingCpuManagerConfig = n.isCPUManagerEnabled; // If cpu-pinning is enabled, filter-out non-enabled CPU manager nodes.
|
||||
|
||||
return isNotSelfNode && isNotWitnessNode && matchingCpuManagerConfig;
|
||||
}).map((n) => {
|
||||
let label = n?.metadata?.name;
|
||||
const value = n?.metadata?.name;
|
||||
|
||||
121
pkg/harvester/dialog/HarvesterQuotaDialog.vue
Normal file
121
pkg/harvester/dialog/HarvesterQuotaDialog.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<script>
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import { Card } from '@components/Card';
|
||||
import { Banner } from '@components/Banner';
|
||||
import AsyncButton from '@shell/components/AsyncButton';
|
||||
import UnitInput from '@shell/components/form/UnitInput';
|
||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterVMQuotaDialog',
|
||||
|
||||
components: {
|
||||
AsyncButton,
|
||||
Card,
|
||||
UnitInput,
|
||||
Banner
|
||||
},
|
||||
|
||||
props: {
|
||||
resources: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.totalSnapshotSize = this.modalData.snapshotSizeQuota;
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
totalSnapshotSize: '',
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState('action-menu', ['modalData']),
|
||||
...mapGetters({ t: 'i18n/t' }),
|
||||
|
||||
actionResource() {
|
||||
return this.resources[0];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
close() {
|
||||
this.totalSnapshotSize = '';
|
||||
this.$emit('close');
|
||||
},
|
||||
|
||||
async save(buttonDone) {
|
||||
try {
|
||||
// call delete action if user input 0Gi or empty string
|
||||
if (this.totalSnapshotSize === null || this.totalSnapshotSize === '0Gi' ) {
|
||||
await this.actionResource.doAction('deleteResourceQuota');
|
||||
} else {
|
||||
await this.actionResource.doAction('updateResourceQuota', { totalSnapshotSizeQuota: this.totalSnapshotSize });
|
||||
}
|
||||
this.close();
|
||||
buttonDone(true);
|
||||
} catch (err) {
|
||||
const error = err?.data || err;
|
||||
const message = exceptionToErrorsArray(error);
|
||||
|
||||
this['errors'] = message;
|
||||
buttonDone(false);
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card :show-highlight-border="false">
|
||||
<h4
|
||||
slot="title"
|
||||
v-clean-html="t('harvester.modal.quota.editQuota')"
|
||||
class="text-default-text"
|
||||
/>
|
||||
<template #body>
|
||||
<Banner color="info">
|
||||
{{ t('harvester.modal.quota.bannerMessage') }}
|
||||
</Banner>
|
||||
<UnitInput
|
||||
v-model:value="totalSnapshotSize"
|
||||
v-int-number
|
||||
:label="t('harvester.snapshot.totalSnapshotSize')"
|
||||
:disabled="false"
|
||||
:mode="create"
|
||||
:input-exponent="3"
|
||||
:increment="1024"
|
||||
:output-modifier="true"
|
||||
suffix="GiB"
|
||||
class="mb-20"
|
||||
/>
|
||||
</template>
|
||||
<div slot="actions" class="actions">
|
||||
<div class="buttons">
|
||||
<button class="btn role-secondary mr-10" @click="close">
|
||||
{{ t('generic.cancel') }}
|
||||
</button>
|
||||
<AsyncButton @click="save" />
|
||||
</div>
|
||||
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -149,7 +149,7 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
|
||||
<Banner v-for="(err, i) in errors" :key="i"/>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@ -5,7 +5,7 @@ import { BadgeState } from '@components/BadgeState';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { RadioGroup, RadioButton } from '@components/Form/Radio';
|
||||
import HarvesterDisk from '../../mixins/harvester-disk';
|
||||
import DiskTags from '../../components/DiskTags';
|
||||
import Tags from '../../components/DiskTags';
|
||||
import { HCI } from '../../types';
|
||||
import { LONGHORN_SYSTEM } from './index';
|
||||
|
||||
@ -17,7 +17,7 @@ export default {
|
||||
Banner,
|
||||
RadioGroup,
|
||||
RadioButton,
|
||||
DiskTags,
|
||||
Tags,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
@ -44,6 +44,18 @@ export default {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
targetDisk() {
|
||||
return this.disks.find(disk => disk.name === this.value.name);
|
||||
},
|
||||
schedulableTooltipMessage() {
|
||||
const { name, path } = this.value;
|
||||
|
||||
if (this.targetDisk && !this.targetDisk.allowScheduling && name && path) {
|
||||
return this.t('harvester.host.disk.allowScheduling.tooltip', { name, path });
|
||||
} else {
|
||||
return this.schedulableCondition.message;
|
||||
}
|
||||
},
|
||||
allowSchedulingOptions() {
|
||||
return [{
|
||||
label: this.t('generic.enabled'),
|
||||
@ -171,7 +183,7 @@ export default {
|
||||
<div v-if="!value.isNew">
|
||||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<DiskTags
|
||||
<Tags
|
||||
v-model:value="value.tags"
|
||||
:label="t('harvester.host.disk.tags.label')"
|
||||
:add-label="t('harvester.host.disk.tags.addLabel')"
|
||||
@ -181,6 +193,16 @@ export default {
|
||||
</div>
|
||||
<div class="row mt-10">
|
||||
<div class="col span-12">
|
||||
<div class="pull-left">
|
||||
<RadioGroup
|
||||
v-model:value="value.allowScheduling"
|
||||
name="diskScheduling"
|
||||
:label="t('harvester.host.disk.allowScheduling.label')"
|
||||
:mode="mode"
|
||||
:options="allowSchedulingOptions"
|
||||
:row="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{ t('harvester.host.disk.conditions') }}:
|
||||
<BadgeState
|
||||
@ -191,9 +213,9 @@ export default {
|
||||
class="mr-10 ml-10 state"
|
||||
/>
|
||||
<BadgeState
|
||||
v-clean-tooltip="schedulableCondition.message"
|
||||
:color="schedulableCondition.status === 'True' ? 'bg-success' : 'bg-error' "
|
||||
:icon="schedulableCondition.status === 'True' ? 'icon-checkmark' : 'icon-warning' "
|
||||
v-clean-tooltip="schedulableTooltipMessage"
|
||||
:color="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'bg-success' : 'bg-error' "
|
||||
:icon="schedulableCondition.status === 'True' && targetDisk?.allowScheduling ? 'icon-checkmark' : 'icon-warning' "
|
||||
label="Schedulable"
|
||||
class="mr-10 state"
|
||||
/>
|
||||
|
||||
@ -5,6 +5,7 @@ import UnitInput from '@shell/components/form/UnitInput';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
import { HCI } from '../../types';
|
||||
import { DOC_LINKS } from '../../config/doc-links';
|
||||
|
||||
export const ksmtunedMode = [{
|
||||
value: 'standard',
|
||||
@ -86,6 +87,10 @@ export default {
|
||||
|
||||
showKsmt() {
|
||||
return this.spec.run === 'run';
|
||||
},
|
||||
|
||||
ksmtunedLink() {
|
||||
return DOC_LINKS.KSMTUNED_MODE;
|
||||
}
|
||||
},
|
||||
|
||||
@ -135,7 +140,7 @@ export default {
|
||||
<Checkbox v-model:value="enableMergeAcrossNodes" :mode="mode" class="check mb-20" type="checkbox" :label="t('harvester.host.ksmtuned.enableMergeNodes')" />
|
||||
|
||||
<h3>
|
||||
<t k="harvester.host.ksmtuned.modeLink" :raw="true" />
|
||||
<t k="harvester.host.ksmtuned.modeLink" :raw="true" :url="ksmtunedLink" />
|
||||
</h3>
|
||||
<RadioGroup
|
||||
v-model:value="spec.mode"
|
||||
|
||||
@ -25,10 +25,10 @@ import { _EDIT } from '@shell/config/query-params';
|
||||
import { sortBy } from '@shell/utils/sort';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { HCI } from '../../types';
|
||||
import DiskTags from '../../components/DiskTags';
|
||||
import HarvesterDisk from './HarvesterDisk';
|
||||
import HarvesterSeeder from './HarvesterSeeder';
|
||||
import HarvesterKsmtuned from './HarvesterKsmtuned';
|
||||
import Tags from '../../components/DiskTags';
|
||||
|
||||
export const LONGHORN_SYSTEM = 'longhorn-system';
|
||||
|
||||
@ -46,7 +46,7 @@ export default {
|
||||
ButtonDropdown,
|
||||
KeyValue,
|
||||
Banner,
|
||||
DiskTags,
|
||||
Tags,
|
||||
Loading,
|
||||
HarvesterSeeder,
|
||||
MessageLink,
|
||||
@ -478,10 +478,11 @@ export default {
|
||||
|
||||
const disks = this.longhornNode?.spec?.disks || {};
|
||||
|
||||
// update each disk tags and scheduling
|
||||
this.newDisks.map((disk) => {
|
||||
(disks[disk.name] || {}).tags = disk.tags;
|
||||
(disks[disk.name] || {}).allowScheduling = disk.allowScheduling;
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
|
||||
const retrySave = async() => {
|
||||
@ -505,7 +506,9 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
await retrySave();
|
||||
if (this.longhornNode) {
|
||||
await retrySave();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -547,7 +550,7 @@ export default {
|
||||
class="row mb-20"
|
||||
>
|
||||
<div class="col span-12">
|
||||
<DiskTags
|
||||
<Tags
|
||||
v-model:value="longhornNode.spec.tags"
|
||||
:label="t('harvester.host.tags.label')"
|
||||
:add-label="t('harvester.host.tags.addLabel')"
|
||||
@ -650,7 +653,6 @@ export default {
|
||||
:value="filteredLabels"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:title="t('labels.labels.title')"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@update:value="updateHostLabels"
|
||||
|
||||
@ -143,10 +143,19 @@ export default {
|
||||
},
|
||||
|
||||
input(neu) {
|
||||
const pattern = /^([1-9]|[1-9][0-9]{1,2}|[1-3][0-9]{3}|40[0-9][0-4])$/;
|
||||
if (neu === '') {
|
||||
this.config.vlan = '';
|
||||
|
||||
if (!pattern.test(neu) && neu !== '') {
|
||||
this.config.vlan = neu > 4094 ? 4094 : 1;
|
||||
return;
|
||||
}
|
||||
const newValue = Number(neu);
|
||||
|
||||
if (newValue > 4094) {
|
||||
this.config.vlan = 4094;
|
||||
} else if (newValue < 1) {
|
||||
this.config.vlan = 1;
|
||||
} else {
|
||||
this.config.vlan = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import { STORAGE_CLASS, LONGHORN } from '@shell/config/types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { CSI_DRIVER } from '../../types';
|
||||
import DiskTags from '../../components/DiskTags';
|
||||
import Tags from '../../components/DiskTags';
|
||||
|
||||
const LONGHORN_DRIVER = 'driver.longhorn.io';
|
||||
|
||||
@ -34,7 +34,7 @@ export default {
|
||||
Tab,
|
||||
Tabbed,
|
||||
Loading,
|
||||
DiskTags,
|
||||
Tags,
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
@ -304,7 +304,7 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-8 value">
|
||||
<DiskTags
|
||||
<Tags
|
||||
v-model:value="scope.row.value.values"
|
||||
:add-label="t('generic.add')"
|
||||
:mode="modeOverride"
|
||||
|
||||
@ -162,6 +162,7 @@ export default {
|
||||
buttonCb(false);
|
||||
}
|
||||
} else {
|
||||
this.value.spec.url = this.value.spec.url?.trim() || '';
|
||||
this.save(buttonCb);
|
||||
}
|
||||
},
|
||||
@ -277,6 +278,7 @@ export default {
|
||||
:can-yaml="showEditAsYaml ? true : false"
|
||||
:apply-hooks="applyHooks"
|
||||
@finish="saveImage"
|
||||
@error="e=>errors=e"
|
||||
>
|
||||
<NameNsDescription
|
||||
ref="nd"
|
||||
|
||||
@ -18,7 +18,6 @@ import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import { AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook';
|
||||
import { HCI } from '../types';
|
||||
import { RunStrategys } from '../config/harvester-map';
|
||||
import VM_MIXIN from '../mixins/harvester-vm';
|
||||
import Reserved from './kubevirt.io.virtualmachine/VirtualMachineReserved';
|
||||
import Volume from './kubevirt.io.virtualmachine/VirtualMachineVolume';
|
||||
@ -74,7 +73,6 @@ export default {
|
||||
description: '',
|
||||
defaultVersion: null,
|
||||
isDefaultVersion: false,
|
||||
RunStrategys,
|
||||
};
|
||||
},
|
||||
|
||||
@ -263,7 +261,7 @@ export default {
|
||||
<Tab
|
||||
name="nodeScheduling"
|
||||
:label="t('workload.container.titles.nodeScheduling')"
|
||||
:weight="-89"
|
||||
:weight="-3"
|
||||
>
|
||||
<template #default="{active}">
|
||||
<NodeScheduling
|
||||
@ -274,7 +272,7 @@ export default {
|
||||
</template>
|
||||
</Tab>
|
||||
|
||||
<Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="-90">
|
||||
<Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="-4">
|
||||
<template #default="{active}">
|
||||
<PodAffinity
|
||||
:mode="mode"
|
||||
@ -287,13 +285,42 @@ export default {
|
||||
</template>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
:name="t('generic.labels')"
|
||||
:label="t('harvester.tab.instanceLabel')"
|
||||
:weight="-5"
|
||||
>
|
||||
<Labels
|
||||
:default-container-class="'labels-and-annotations-container'"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:display-side-by-side="false"
|
||||
:show-annotations="false"
|
||||
:show-label-title="false"
|
||||
>
|
||||
<template #labels="{toggler}">
|
||||
<KeyValue
|
||||
key="labels"
|
||||
:value="value.instanceLabels"
|
||||
:protected-keys="value.systemLabels || []"
|
||||
:toggle-filter="toggler"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@input="value.setInstanceLabels($event)"
|
||||
/>
|
||||
</template>
|
||||
</Labels>
|
||||
</Tab>
|
||||
|
||||
<Tab name="advanced" :label="t('harvester.tab.advanced')" :weight="-99">
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="runStrategy"
|
||||
label-key="harvester.virtualMachine.runStrategy"
|
||||
:options="RunStrategys"
|
||||
:options="runStrategies"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
@ -308,6 +335,37 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="maintenanceStrategy"
|
||||
label-key="harvester.virtualMachine.maintenanceStrategy.label"
|
||||
:options="maintenanceStrategies"
|
||||
:get-option-label="getMaintenanceStrategyOptionLabel"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<Reserved
|
||||
:reserved-memory="reservedMemory"
|
||||
:mode="mode"
|
||||
@updateReserved="updateReserved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<Checkbox
|
||||
v-model:value="cpuPinning"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
label-key="harvester.virtualMachine.cpuPinning.label"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<a v-if="showAdvanced" v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
|
||||
<a v-else v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
|
||||
@ -315,13 +373,6 @@ export default {
|
||||
|
||||
<div v-if="showAdvanced">
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<Reserved
|
||||
:reserved-memory="reservedMemory"
|
||||
:mode="mode"
|
||||
@updateReserved="updateReserved"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model:value="terminationGracePeriodSeconds"
|
||||
@ -386,35 +437,6 @@ export default {
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
:name="t('generic.labels')"
|
||||
:label="t('harvester.tab.instanceLabel')"
|
||||
:weight="-99"
|
||||
>
|
||||
<Labels
|
||||
:default-container-class="'labels-and-annotations-container'"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:display-side-by-side="false"
|
||||
:show-annotations="false"
|
||||
:show-label-title="false"
|
||||
>
|
||||
<template #labels="{toggler}">
|
||||
<KeyValue
|
||||
key="labels"
|
||||
:value="value.instanceLabels"
|
||||
:protected-keys="value.systemLabels || []"
|
||||
:toggle-filter="toggler"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@update:value="value.setInstanceLabels($event)"
|
||||
/>
|
||||
</template>
|
||||
</Labels>
|
||||
</Tab>
|
||||
</Tabbed>
|
||||
</CruResource>
|
||||
</template>
|
||||
|
||||
@ -56,6 +56,7 @@ export default {
|
||||
const hash = await allHash(_hash);
|
||||
|
||||
this.snapshots = hash.snapshots;
|
||||
this.images = hash.images;
|
||||
|
||||
const defaultStorage = this.$store.getters[`harvester/all`](STORAGE_CLASS).find( O => O.isDefault);
|
||||
|
||||
@ -77,6 +78,7 @@ export default {
|
||||
storage,
|
||||
imageId,
|
||||
snapshots: [],
|
||||
images: [],
|
||||
};
|
||||
},
|
||||
|
||||
@ -108,10 +110,8 @@ export default {
|
||||
},
|
||||
|
||||
imageOption() {
|
||||
const choices = this.$store.getters['harvester/all'](HCI.IMAGE);
|
||||
|
||||
return sortBy(
|
||||
choices
|
||||
this.images
|
||||
.filter(obj => obj.isReady)
|
||||
.map((obj) => {
|
||||
return {
|
||||
@ -249,7 +249,17 @@ export default {
|
||||
|
||||
this.value['spec'] = spec;
|
||||
},
|
||||
updateImage() {
|
||||
if (this.isVMImage && this.imageId) {
|
||||
const imageResource = this.images?.find(image => this.imageId === image.id);
|
||||
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
|
||||
|
||||
if (imageSize) {
|
||||
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }Gi`;
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
generateYaml() {
|
||||
const out = saferDump(this.value);
|
||||
|
||||
@ -300,7 +310,7 @@ export default {
|
||||
required
|
||||
:mode="mode"
|
||||
class="mb-20"
|
||||
@update:value="update"
|
||||
@update:value="updateImage"
|
||||
/>
|
||||
|
||||
<LabeledSelect
|
||||
|
||||
@ -140,15 +140,24 @@ export default {
|
||||
|
||||
onImageChange() {
|
||||
const imageResource = this.$store.getters['harvester/all'](HCI.IMAGE)?.find( I => this.value.image === I.id);
|
||||
const isIsoImage = /iso$/i.test(imageResource?.imageSuffix);
|
||||
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
|
||||
|
||||
if (this.idx === 0) {
|
||||
if (/iso$/i.test(imageResource?.imageSuffix)) {
|
||||
this.value['type'] = 'cd-rom';
|
||||
this.value['bus'] = 'sata';
|
||||
} else {
|
||||
this.value['type'] = 'disk';
|
||||
this.value['bus'] = 'virtio';
|
||||
if (isIsoImage) {
|
||||
this.value['type'] = 'cd-rom';
|
||||
this.value['bus'] = 'sata';
|
||||
} else {
|
||||
this.value['type'] = 'disk';
|
||||
this.value['bus'] = 'virtio';
|
||||
}
|
||||
|
||||
if (imageSize) {
|
||||
let imageSizeGiB = Math.ceil(imageSize / 1024 / 1024 / 1024);
|
||||
|
||||
if (!isIsoImage) {
|
||||
imageSizeGiB = Math.max(imageSizeGiB, 10);
|
||||
}
|
||||
this.value['size'] = `${ imageSizeGiB }Gi`;
|
||||
}
|
||||
|
||||
this.update();
|
||||
|
||||
@ -28,7 +28,6 @@ import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
|
||||
import { parseVolumeClaimTemplates } from '@pkg/utils/vm';
|
||||
import VM_MIXIN from '../../mixins/harvester-vm';
|
||||
import { RunStrategys } from '../../config/harvester-map';
|
||||
import { HCI } from '../../types';
|
||||
import RestartVMDialog from '../../dialog/RestartVMDialog';
|
||||
import VirtualMachineVGpuDevices from './VirtualMachineVGpuDevices/index';
|
||||
@ -40,6 +39,8 @@ import Network from './VirtualMachineNetwork';
|
||||
import Volume from './VirtualMachineVolume';
|
||||
import SSHKey from './VirtualMachineSSHKey';
|
||||
import Reserved from './VirtualMachineReserved';
|
||||
import { Banner } from '@components/Banner';
|
||||
import MessageLink from '@shell/components/MessageLink';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterEditVM',
|
||||
@ -68,6 +69,8 @@ export default {
|
||||
UnitInput,
|
||||
VirtualMachineVGpuDevices,
|
||||
KeyValue,
|
||||
Banner,
|
||||
MessageLink
|
||||
},
|
||||
|
||||
mixins: [CreateEditView, VM_MIXIN],
|
||||
@ -96,13 +99,19 @@ export default {
|
||||
isOpen: false,
|
||||
hostname,
|
||||
isRestartImmediately,
|
||||
RunStrategys,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({ t: 'i18n/t' }),
|
||||
|
||||
to() {
|
||||
return {
|
||||
name: 'harvester-c-cluster-resource',
|
||||
params: { cluster: this.$store.getters['clusterId'], resource: HCI.HOST },
|
||||
};
|
||||
},
|
||||
|
||||
machineTypeOptions() {
|
||||
return [{
|
||||
label: 'None',
|
||||
@ -173,6 +182,26 @@ export default {
|
||||
hasStartAction() {
|
||||
return this.value.hasAction('start');
|
||||
},
|
||||
|
||||
enableCpuPinningCheckbox() {
|
||||
if (this.mode === 'create') {
|
||||
return this.nodes.some(node => node.isCPUManagerEnabled); // any one of nodes has label cpuManager=true
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
showCpuPinningBanner() {
|
||||
if (this.mode === 'edit') {
|
||||
return this.cpuPinning !== !!this.cloneVM.spec.template.spec.domain.cpu.dedicatedCpuPlacement;
|
||||
}
|
||||
|
||||
if (this.mode === 'create') {
|
||||
return this.nodes.every(node => !node.isCPUManagerEnabled); // no node enabled CPU manager
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -273,6 +302,15 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
cancelAction() {
|
||||
const { fromPage = HCI.VM } = this.$route?.query; // default back to VM list page
|
||||
const cancelOverride = {
|
||||
name: this.doneRoute,
|
||||
params: { resource: fromPage }
|
||||
};
|
||||
|
||||
this.$router.replace(cancelOverride);
|
||||
},
|
||||
saveVM(buttonCb) {
|
||||
clear(this.errors);
|
||||
|
||||
@ -437,12 +475,14 @@ export default {
|
||||
id="vm"
|
||||
:done-route="doneRoute"
|
||||
:resource="value"
|
||||
:cancelEvent="true"
|
||||
:mode="mode"
|
||||
:can-yaml="isSingle ? true : false"
|
||||
:errors="errors"
|
||||
:generate-yaml="generateYaml"
|
||||
:apply-hooks="applyHooks"
|
||||
@finish="saveVM"
|
||||
@cancel="cancelAction"
|
||||
>
|
||||
<RadioGroup
|
||||
v-if="isCreate"
|
||||
@ -606,44 +646,54 @@ export default {
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
v-if="enabledSriovgpu"
|
||||
:label="t('harvester.tab.vGpuDevices')"
|
||||
name="vGpuDevices"
|
||||
:weight="-6"
|
||||
>
|
||||
<VirtualMachineVGpuDevices
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
:vm="value"
|
||||
/>
|
||||
<Tab v-if="enabledSriovgpu" :label="t('harvester.tab.vGpuDevices')" name="vGpuDevices" :weight="-6">
|
||||
<VirtualMachineVGpuDevices :mode="mode" :value="spec.template.spec" :vm="value" />
|
||||
</Tab>
|
||||
|
||||
<Tab v-if="isEdit" :label="t('harvester.tab.accessCredentials')" name="accessCredentials" :weight="-7">
|
||||
<AccessCredentials v-model:value="accessCredentials" :mode="mode" :resource="value" :is-qemu-installed="isQemuInstalled" />
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
v-if="isEdit"
|
||||
:label="t('harvester.tab.accessCredentials')"
|
||||
name="accessCredentials"
|
||||
:weight="-7"
|
||||
name="instanceLabel"
|
||||
:label="t('harvester.tab.instanceLabel')"
|
||||
:weight="-8"
|
||||
>
|
||||
<AccessCredentials
|
||||
v-model:value="accessCredentials"
|
||||
<Labels
|
||||
:default-container-class="'labels-and-annotations-container'"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:resource="value"
|
||||
:is-qemu-installed="isQemuInstalled"
|
||||
/>
|
||||
:display-side-by-side="false"
|
||||
:show-annotations="false"
|
||||
:show-label-title="false"
|
||||
>
|
||||
<template #labels="{toggler}">
|
||||
<KeyValue
|
||||
key="labels"
|
||||
:value="value.instanceLabels"
|
||||
:protected-keys="value.systemLabels || []"
|
||||
:toggle-filter="toggler"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@update:value="value.setInstanceLabels($event)"
|
||||
/>
|
||||
</template>
|
||||
</Labels>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
name="advanced"
|
||||
:label="t('harvester.tab.advanced')"
|
||||
:weight="-8"
|
||||
:weight="-9"
|
||||
>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="runStrategy"
|
||||
label-key="harvester.virtualMachine.runStrategy"
|
||||
:options="RunStrategys"
|
||||
:options="runStrategies"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
@ -659,24 +709,30 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<a
|
||||
v-if="showAdvanced"
|
||||
v-t="'harvester.generic.showMore'"
|
||||
role="button"
|
||||
@click="toggleAdvanced"
|
||||
/>
|
||||
<a
|
||||
v-else
|
||||
v-t="'harvester.generic.showMore'"
|
||||
role="button"
|
||||
@click="toggleAdvanced"
|
||||
/>
|
||||
<div class="col span-6">
|
||||
<LabeledSelect
|
||||
v-model:value="maintenanceStrategy"
|
||||
label-key="harvester.virtualMachine.maintenanceStrategy.label"
|
||||
:options="maintenanceStrategies"
|
||||
:get-option-label="getMaintenanceStrategyOptionLabel"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<Reserved
|
||||
:reserved-memory="reservedMemory"
|
||||
:mode="mode"
|
||||
@updateReserved="updateReserved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showAdvanced"
|
||||
class="mb-20"
|
||||
>
|
||||
<div class="row mb-20">
|
||||
<a v-if="showAdvanced" v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
|
||||
<a v-else v-t="'harvester.generic.showMore'" role="button" @click="toggleAdvanced" />
|
||||
</div>
|
||||
|
||||
<div v-if="showAdvanced" class="mb-20">
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<LabeledInput
|
||||
@ -698,13 +754,6 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="row mb-20">
|
||||
<div class="col span-6">
|
||||
<Reserved
|
||||
:reserved-memory="reservedMemory"
|
||||
:mode="mode"
|
||||
@updateReserved="updateReserved"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-6">
|
||||
<UnitInput
|
||||
v-model:value="terminationGracePeriodSeconds"
|
||||
@ -730,6 +779,16 @@ export default {
|
||||
@updateDataTemplateId="updateDataTemplateId"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
v-model:value="cpuPinning"
|
||||
:disabled="!enableCpuPinningCheckbox"
|
||||
class="check"
|
||||
type="checkbox"
|
||||
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
|
||||
label-key="harvester.virtualMachine.cpuPinning.label"
|
||||
:mode="mode"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
v-model:value="installUSBTablet"
|
||||
class="check mt-20"
|
||||
@ -773,35 +832,21 @@ export default {
|
||||
:label="t('harvester.virtualMachine.secureBoot')"
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
name="instanceLabel"
|
||||
:label="t('harvester.tab.instanceLabel')"
|
||||
:weight="-99"
|
||||
>
|
||||
<Labels
|
||||
:default-container-class="'labels-and-annotations-container'"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:display-side-by-side="false"
|
||||
:show-annotations="false"
|
||||
:show-label-title="false"
|
||||
<Banner
|
||||
v-if="showCpuPinningBanner"
|
||||
color="warning"
|
||||
>
|
||||
<template #labels="{toggler}">
|
||||
<KeyValue
|
||||
key="labels"
|
||||
:value="value.instanceLabels"
|
||||
:protected-keys="value.systemLabels || []"
|
||||
:toggle-filter="toggler"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@update:value="value.setInstanceLabels($event)"
|
||||
/>
|
||||
</template>
|
||||
</Labels>
|
||||
<MessageLink
|
||||
v-if="mode === 'create'"
|
||||
:to="to"
|
||||
prefix-label="harvester.virtualMachine.advancedOptions.cpuManager.prefix"
|
||||
middle-label="harvester.virtualMachine.advancedOptions.cpuManager.middle"
|
||||
suffix-label="harvester.virtualMachine.advancedOptions.cpuManager.suffix"
|
||||
/>
|
||||
<span v-if="mode==='edit'">
|
||||
{{ t('harvester.virtualMachine.cpuPinning.restartVMMessage') }}
|
||||
</span>
|
||||
</Banner>
|
||||
</Tab>
|
||||
</Tabbed>
|
||||
|
||||
|
||||
@ -314,12 +314,21 @@ export default {
|
||||
} else if (selector[HOSTNAME] && Object.keys(selector).length === 1) {
|
||||
const matchNode = allNodes.find(n => n.id === selector[HOSTNAME]);
|
||||
|
||||
this.matchingNodes = {
|
||||
matched: 1,
|
||||
total: allNodes.length,
|
||||
none: false,
|
||||
sample: matchNode ? matchNode.nameDisplay : selector[HOSTNAME],
|
||||
};
|
||||
if (matchNode) {
|
||||
this.matchingNodes = {
|
||||
matched: 1,
|
||||
total: allNodes.length,
|
||||
none: false,
|
||||
sample: matchNode.nameDisplay,
|
||||
};
|
||||
} else {
|
||||
this.matchingNodes = {
|
||||
matched: 0,
|
||||
total: 0,
|
||||
none: true,
|
||||
sample: null,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const match = matching(allNodes, selector);
|
||||
|
||||
|
||||
44
pkg/harvester/formatters/HarvesterCPUPinning.vue
Normal file
44
pkg/harvester/formatters/HarvesterCPUPinning.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'HarvesterCPUPinningFormatter',
|
||||
props: {
|
||||
value: {
|
||||
type: String, // id
|
||||
default: '',
|
||||
},
|
||||
rows: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
row() {
|
||||
return this.rows.find(r => r.id === this.value);
|
||||
},
|
||||
cpuManagerStatus() {
|
||||
if (this.row?.isCPUManagerEnableInProgress) {
|
||||
return this.t('generic.loading');
|
||||
}
|
||||
|
||||
return this.row?.isCPUManagerEnabled ? this.t('generic.enabled') : this.t('generic.disabled');
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="row?.isCPUManagerEnableInProgress" v-clean-tooltip="cpuManagerStatus">
|
||||
<i class="icon icon-spinner icon-spin" />
|
||||
</span>
|
||||
<span v-else-if="row?.isCPUManagerEnabled" v-clean-tooltip="cpuManagerStatus">
|
||||
<i class="icon icon-checkmark" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
v-clean-tooltip="cpuManagerStatus"
|
||||
class="text-muted"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
</template>
|
||||
@ -23,77 +23,76 @@ export default {
|
||||
default: ''
|
||||
},
|
||||
|
||||
showReserved: {
|
||||
showAllocated: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
this.longhornSettings = await this.$store.dispatch(`${ inStore }/findAll`, { type: LONGHORN.SETTINGS });
|
||||
},
|
||||
|
||||
data() {
|
||||
return {};
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornSettings = this.$store.getters[`${ inStore }/all`](LONGHORN.SETTINGS) || [];
|
||||
|
||||
return { longhornSettings };
|
||||
},
|
||||
|
||||
computed: {
|
||||
usage() {
|
||||
storageStats() {
|
||||
const stats = {
|
||||
used: 0,
|
||||
scheduled: 0,
|
||||
maximum: 0,
|
||||
reserved: 0,
|
||||
total: 0
|
||||
};
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`) || {};
|
||||
const node = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`) || {};
|
||||
const storageOverProvisioningPercentageSetting = this.longhornSettings.find(s => s.id === 'longhorn-system/storage-over-provisioning-percentage');
|
||||
const disks = node?.spec?.disks || {};
|
||||
const diskStatus = node?.status?.diskStatus || {};
|
||||
|
||||
return longhornNode?.used || 0;
|
||||
},
|
||||
stats.used += node?.spec?.allowScheduling ? node.used : 0;
|
||||
|
||||
reserved() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`);
|
||||
let reserved = 0;
|
||||
|
||||
const disks = longhornNode?.spec?.disks || {};
|
||||
|
||||
Object.values(disks).map((disk) => {
|
||||
if (disk.allowScheduling) {
|
||||
reserved += disk.storageReserved;
|
||||
}
|
||||
Object.keys(disks).map((key) => {
|
||||
stats.scheduled += node?.spec?.allowScheduling ? (diskStatus[key]?.storageScheduled || 0) : 0;
|
||||
stats.reserved += disks[key]?.storageReserved || 0;
|
||||
});
|
||||
Object.values(diskStatus).map((diskStat) => {
|
||||
stats.maximum += diskStat?.storageMaximum || 0;
|
||||
});
|
||||
|
||||
return reserved;
|
||||
},
|
||||
stats.total = ((stats.maximum - stats.reserved) * Number(storageOverProvisioningPercentageSetting?.value ?? 0)) / 100;
|
||||
|
||||
total() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.row.id }`);
|
||||
let out = 0;
|
||||
|
||||
const diskStatus = longhornNode?.status?.diskStatus || {};
|
||||
|
||||
Object.values(diskStatus).map((disk) => {
|
||||
if (disk?.storageMaximum) {
|
||||
out += disk.storageMaximum;
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
return stats;
|
||||
},
|
||||
|
||||
units() {
|
||||
const exponent = exponentNeeded(this.total, 1024);
|
||||
const exponent = exponentNeeded(this.storageStats.maximum, 1024);
|
||||
|
||||
return `${ UNITS[exponent] }iB`;
|
||||
},
|
||||
|
||||
used() {
|
||||
let out = this.formatter(this.usage || 0);
|
||||
let out = this.formatter(this.storageStats.used);
|
||||
|
||||
if (!Number.parseFloat(out) > 0) {
|
||||
out = this.formatter(this.usage || 0, { canRoundToZero: false });
|
||||
out = this.formatter(this.storageStats.used, { canRoundToZero: false });
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
formatReserved() {
|
||||
let out = this.formatter(this.reserved || 0);
|
||||
formatAllocated() {
|
||||
let out = this.formatter(this.storageStats.scheduled);
|
||||
|
||||
if (!Number.parseFloat(out) > 0) {
|
||||
out = this.formatter(this.reserved || 0, { canRoundToZero: false });
|
||||
out = this.formatter(this.storageStats.scheduled, { canRoundToZero: false });
|
||||
}
|
||||
|
||||
return out;
|
||||
@ -102,15 +101,15 @@ export default {
|
||||
usedAmountTemplateValues() {
|
||||
return {
|
||||
used: this.used,
|
||||
total: this.formatter(this.total || 0),
|
||||
total: this.formatter(this.storageStats.maximum),
|
||||
unit: this.units,
|
||||
};
|
||||
},
|
||||
|
||||
reservedAmountTemplateValues() {
|
||||
allocatedAmountTemplateValues() {
|
||||
return {
|
||||
used: this.formatReserved,
|
||||
total: this.formatter(this.total || 0),
|
||||
used: this.formatAllocated,
|
||||
total: this.formatter(this.storageStats.total),
|
||||
unit: this.units,
|
||||
};
|
||||
},
|
||||
@ -118,7 +117,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
formatter(value, format) {
|
||||
const minExponent = exponentNeeded(this.total, 1024);
|
||||
const minExponent = exponentNeeded(this.storageStats.maximum, 1024);
|
||||
const formatOptions = {
|
||||
addSuffix: false,
|
||||
increment: 1024,
|
||||
@ -137,21 +136,21 @@ export default {
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="showReserved"
|
||||
v-if="showAllocated"
|
||||
>
|
||||
<ConsumptionGauge
|
||||
:capacity="total"
|
||||
:used="reserved"
|
||||
:capacity="storageStats.total"
|
||||
:used="storageStats.scheduled"
|
||||
:units="units"
|
||||
:number-formatter="formatter"
|
||||
:resource-name="resourceName"
|
||||
>
|
||||
<template #title="{formattedPercentage}">
|
||||
<span>
|
||||
{{ t('clusterIndexPage.hardwareResourceGauge.reserved') }}
|
||||
{{ t('clusterIndexPage.hardwareResourceGauge.allocated') }}
|
||||
</span>
|
||||
<span class="precent-data">
|
||||
{{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }}
|
||||
{{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }}
|
||||
<span class="ml-10 percentage">
|
||||
/ {{ formattedPercentage }}
|
||||
</span>
|
||||
@ -160,13 +159,13 @@ export default {
|
||||
</ConsumptionGauge>
|
||||
</div>
|
||||
<ConsumptionGauge
|
||||
:capacity="total"
|
||||
:used="usage"
|
||||
:capacity="storageStats.maximum"
|
||||
:used="storageStats.used"
|
||||
:units="units"
|
||||
:number-formatter="formatter"
|
||||
:resource-name="showReserved ? '' : resourceName"
|
||||
:resource-name="showAllocated ? '' : resourceName"
|
||||
:class="{
|
||||
'mt-10': showReserved,
|
||||
'mt-10': showAllocated,
|
||||
}"
|
||||
>
|
||||
<template #title="{formattedPercentage}">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -134,15 +134,16 @@ export default {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const hash = {
|
||||
vms: this.fetchClusterResources(HCI.VM),
|
||||
nodes: this.fetchClusterResources(NODE),
|
||||
events: this.fetchClusterResources(EVENT),
|
||||
metricNodes: this.fetchClusterResources(METRIC.NODE),
|
||||
settings: this.fetchClusterResources(HCI.SETTING),
|
||||
services: this.fetchClusterResources(SERVICE),
|
||||
metric: this.fetchClusterResources(METRIC.NODE),
|
||||
longhornNode: this.fetchClusterResources(LONGHORN.NODES) || [],
|
||||
_pods: this.$store.dispatch('harvester/findAll', { type: POD }),
|
||||
vms: this.fetchClusterResources(HCI.VM),
|
||||
nodes: this.fetchClusterResources(NODE),
|
||||
events: this.fetchClusterResources(EVENT),
|
||||
metricNodes: this.fetchClusterResources(METRIC.NODE),
|
||||
settings: this.fetchClusterResources(HCI.SETTING),
|
||||
services: this.fetchClusterResources(SERVICE),
|
||||
metric: this.fetchClusterResources(METRIC.NODE),
|
||||
longhornNodes: this.fetchClusterResources(LONGHORN.NODES),
|
||||
longhornSettings: this.fetchClusterResources(LONGHORN.SETTINGS),
|
||||
_pods: this.$store.dispatch('harvester/findAll', { type: POD }),
|
||||
};
|
||||
|
||||
(this.accessibleResources || []).map((a) => {
|
||||
@ -155,6 +156,10 @@ export default {
|
||||
hash.addons = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS });
|
||||
}
|
||||
|
||||
if (this.$store.getters[`${ inStore }/schemaFor`](LONGHORN.NODES)) {
|
||||
this.hasLonghornSchema = true;
|
||||
}
|
||||
|
||||
const res = await allHash(hash);
|
||||
|
||||
for ( const k in res ) {
|
||||
@ -226,6 +231,7 @@ export default {
|
||||
showClusterMetrics: false,
|
||||
showVmMetrics: false,
|
||||
enabledMonitoringAddon: false,
|
||||
hasLonghornSchema: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -305,8 +311,7 @@ export default {
|
||||
|
||||
currentVersion() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const settings = this.$store.getters[`${ inStore }/all`](HCI.SETTING);
|
||||
const setting = settings.find( S => S.id === 'server-version');
|
||||
const setting = this.$store.getters[`${ inStore }/byId`](HCI.SETTING, 'server-version');
|
||||
|
||||
return setting?.value || setting?.default;
|
||||
},
|
||||
@ -364,53 +369,46 @@ export default {
|
||||
return out;
|
||||
},
|
||||
|
||||
storageUsage() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
const longhornNodes = this.$store.getters[`${ inStore }/all`](LONGHORN.NODES) || [];
|
||||
|
||||
return longhornNodes.filter(node => node.spec?.allowScheduling).reduce((total, node) => {
|
||||
return total + node.used;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
storageReservedTotal() {
|
||||
let out = 0;
|
||||
|
||||
(this.longhornNode || []).filter(node => node.spec?.allowScheduling).forEach((node) => {
|
||||
storageStats() {
|
||||
const storageOverProvisioningPercentageSetting = this.longhornSettings.find(s => s.id === 'longhorn-system/storage-over-provisioning-percentage');
|
||||
const stats = this.longhornNodes.reduce((total, node) => {
|
||||
const disks = node?.spec?.disks || {};
|
||||
|
||||
Object.values(disks).map((disk) => {
|
||||
if (disk.allowScheduling) {
|
||||
out += disk.storageReserved;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
storageTotal() {
|
||||
let out = 0;
|
||||
|
||||
(this.longhornNode || []).forEach((node) => {
|
||||
const diskStatus = node?.status?.diskStatus || {};
|
||||
|
||||
Object.values(diskStatus).map((disk) => {
|
||||
if (disk?.storageMaximum) {
|
||||
out += disk.storageMaximum;
|
||||
}
|
||||
total.used += node?.spec?.allowScheduling ? node.used : 0;
|
||||
|
||||
Object.keys(disks).map((key) => {
|
||||
total.scheduled += node?.spec?.allowScheduling ? (diskStatus[key]?.storageScheduled || 0) : 0;
|
||||
total.reserved += disks[key]?.storageReserved || 0;
|
||||
});
|
||||
Object.values(diskStatus).map((diskStat) => {
|
||||
total.maximum += diskStat?.storageMaximum || 0;
|
||||
});
|
||||
|
||||
return total;
|
||||
}, {
|
||||
used: 0,
|
||||
scheduled: 0,
|
||||
maximum: 0,
|
||||
reserved: 0,
|
||||
total: 0
|
||||
});
|
||||
|
||||
return out;
|
||||
stats.total = ((stats.maximum - stats.reserved) * Number(storageOverProvisioningPercentageSetting?.value ?? 0)) / 100;
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
storageUsed() {
|
||||
return this.createMemoryValues(this.storageTotal, this.storageUsage);
|
||||
const stats = this.storageStats;
|
||||
|
||||
return this.createMemoryValues(stats.maximum, stats.used);
|
||||
},
|
||||
|
||||
storageReserved() {
|
||||
return this.createMemoryValues(this.storageTotal, this.storageReservedTotal);
|
||||
storageAllocated() {
|
||||
const stats = this.storageStats;
|
||||
|
||||
return this.createMemoryValues(stats.total, stats.scheduled);
|
||||
},
|
||||
|
||||
vmEvents() {
|
||||
@ -644,7 +642,7 @@ export default {
|
||||
<div
|
||||
class="hardware-resource-gauges"
|
||||
:class="{
|
||||
live: !storageTotal,
|
||||
live: !hasLonghornSchema,
|
||||
}"
|
||||
>
|
||||
<HardwareResourceGauge
|
||||
@ -658,10 +656,11 @@ export default {
|
||||
:used="ramUsed"
|
||||
/>
|
||||
<HardwareResourceGauge
|
||||
v-if="storageTotal"
|
||||
v-if="hasLonghornSchema"
|
||||
:name="t('harvester.dashboard.hardwareResourceGauge.storage')"
|
||||
:used="storageUsed"
|
||||
:reserved="storageReserved"
|
||||
:reserved="storageAllocated"
|
||||
:reserved-title="t('clusterIndexPage.hardwareResourceGauge.allocated')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import metricPoller from '@shell/mixins/metric-poller';
|
||||
import { HCI } from '../types';
|
||||
import { DOC_LINKS } from '../config/doc-links';
|
||||
|
||||
const schema = {
|
||||
id: HCI.HOST,
|
||||
@ -91,6 +92,7 @@ export default {
|
||||
value: 'internalIp',
|
||||
formatter: 'CopyToClipboard',
|
||||
sort: ['internalIp'],
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
@ -98,7 +100,7 @@ export default {
|
||||
const metricCol = [
|
||||
{
|
||||
name: 'cpu',
|
||||
labelKey: 'tableHeaders.cpu',
|
||||
labelKey: 'node.detail.glance.consumptionGauge.cpu',
|
||||
value: 'id',
|
||||
formatter: 'HarvesterCPUUsed',
|
||||
formatterOpts: { showUsed: true },
|
||||
@ -121,12 +123,23 @@ export default {
|
||||
labelKey: 'tableHeaders.storage',
|
||||
value: 'id',
|
||||
formatter: 'HarvesterStorageUsed',
|
||||
formatterOpts: { showReserved: true },
|
||||
formatterOpts: { showAllocated: true },
|
||||
};
|
||||
|
||||
out.splice(-1, 0, storageHeader);
|
||||
}
|
||||
|
||||
out.push({
|
||||
name: 'cpuManager',
|
||||
labelKey: 'harvester.tableHeaders.cpuManager',
|
||||
value: 'id',
|
||||
formatter: 'HarvesterCPUPinning',
|
||||
formatterOpts: { rows: this.rows },
|
||||
width: 150,
|
||||
align: 'center',
|
||||
|
||||
});
|
||||
|
||||
if (this.hasLonghornSchema) {
|
||||
out.push({
|
||||
name: 'diskState',
|
||||
@ -143,7 +156,7 @@ export default {
|
||||
name: 'console',
|
||||
label: ' ',
|
||||
align: 'right',
|
||||
width: 65,
|
||||
width: 80,
|
||||
});
|
||||
|
||||
return out;
|
||||
@ -151,6 +164,10 @@ export default {
|
||||
|
||||
schema() {
|
||||
return schema;
|
||||
},
|
||||
|
||||
consoleDocLink() {
|
||||
return DOC_LINKS.CONSOLE_URL;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -169,7 +186,15 @@ export default {
|
||||
|
||||
goto(row) {
|
||||
window.open(row.consoleUrl, '_blank');
|
||||
}
|
||||
},
|
||||
|
||||
consoleTooltip(row) {
|
||||
if (!row.consoleUrl) {
|
||||
return this.t('harvester.host.noConsoleUrl');
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
|
||||
typeDisplay() {
|
||||
@ -199,10 +224,19 @@ export default {
|
||||
|
||||
>
|
||||
<template #cell:console="{row}">
|
||||
<button type="button" class="btn btn-sm role-primary" :disabled="!row.consoleUrl" @click="goto(row)">
|
||||
{{ t('harvester.host.console') }}
|
||||
</button>
|
||||
<div class="console-button">
|
||||
<button v-clean-tooltip="consoleTooltip(row)" type="button" class="mr-5 btn btn-sm role-primary" :disabled="!row.consoleUrl" @click="goto(row)">
|
||||
{{ t('harvester.host.console') }}
|
||||
</button>
|
||||
<a v-if="!row.consoleUrl" :href="consoleDocLink" target="_blank"><i class="icon icon-info" /></a>
|
||||
</div>
|
||||
</template>
|
||||
</ResourceTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.console-button {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -103,7 +103,6 @@ export default {
|
||||
:schema="schema"
|
||||
:rows="rows"
|
||||
key-field="_key"
|
||||
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -56,8 +56,6 @@ export default {
|
||||
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
|
||||
}
|
||||
|
||||
console.log('ppppppppppp', hash);
|
||||
|
||||
this.rows = hash.pvcs;
|
||||
},
|
||||
|
||||
@ -151,23 +149,23 @@ export default {
|
||||
|
||||
>
|
||||
<template
|
||||
cell:state="scope"
|
||||
>
|
||||
cell:state="scope"
|
||||
>
|
||||
<div class="state">
|
||||
<HarvesterVolumeState
|
||||
class="vmstate"
|
||||
:row="scope.row"
|
||||
/>
|
||||
class="vmstate"
|
||||
:row="scope.row"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
cell:AttachedVM="scope"
|
||||
>
|
||||
cell:AttachedVM="scope"
|
||||
>
|
||||
<div>
|
||||
<router-link
|
||||
v-if="getVMName(scope.row)"
|
||||
:to="goTo(scope.row)"
|
||||
>
|
||||
v-if="getVMName(scope.row)"
|
||||
:to="goTo(scope.row)"
|
||||
>
|
||||
{{ getVMName(scope.row) }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
@ -81,6 +81,10 @@ export default {
|
||||
backups: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.BACKUP }),
|
||||
};
|
||||
|
||||
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.RESOURCE_QUOTA)) {
|
||||
_hash.resourceQuotas = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESOURCE_QUOTA });
|
||||
}
|
||||
|
||||
if (this.$store.getters[`${ inStore }/schemaFor`](NODE)) {
|
||||
_hash.nodes = this.$store.dispatch(`${ inStore }/findAll`, { type: NODE });
|
||||
this.hasNode = true;
|
||||
@ -116,7 +120,7 @@ export default {
|
||||
headers() {
|
||||
const restoreCol = {
|
||||
name: 'restoreProgress',
|
||||
labelKey: 'tableHeaders.restore',
|
||||
labelKey: 'harvester.tableHeaders.restore',
|
||||
value: 'restoreProgress',
|
||||
align: 'left',
|
||||
formatter: 'HarvesterBackupProgressBar',
|
||||
@ -128,7 +132,7 @@ export default {
|
||||
value: 'nodeName',
|
||||
sort: ['realAttachNodeName'],
|
||||
formatter: 'HarvesterHost',
|
||||
labelKey: 'tableHeaders.node'
|
||||
labelKey: 'harvester.tableHeaders.vm.node'
|
||||
};
|
||||
|
||||
const cols = clone(VM_HEADERS);
|
||||
@ -137,7 +141,7 @@ export default {
|
||||
cols.splice(-1, 0, nodeCol);
|
||||
}
|
||||
|
||||
if (this.hasRestoredVMs) {
|
||||
if (this.hasBackUpRestoreInProgress) {
|
||||
cols.splice(-1, 0, restoreCol);
|
||||
}
|
||||
|
||||
@ -150,8 +154,11 @@ export default {
|
||||
return [...this.allVMs, ...matchVMIs];
|
||||
},
|
||||
|
||||
hasRestoredVMs() {
|
||||
return !!this.rows.find(r => !!r.restoreResource);
|
||||
/**
|
||||
* We want to show the progress bar only for Backup's restore; snapshot's restore is immediate.
|
||||
*/
|
||||
hasBackUpRestoreInProgress() {
|
||||
return !!this.rows.find(r => r.restoreResource && !r.restoreResource.fromSnapshot && !r.restoreResource.isComplete);
|
||||
}
|
||||
},
|
||||
|
||||
@ -179,7 +186,7 @@ export default {
|
||||
key-field="_key"
|
||||
|
||||
>
|
||||
<template cell:state="scope">
|
||||
<template cell:state="scope" class="state-col">
|
||||
<div class="state">
|
||||
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetworks" :all-cluster-network="allClusterNetworks" />
|
||||
</div>
|
||||
|
||||
@ -144,6 +144,10 @@ export default {
|
||||
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.secureBoot;
|
||||
},
|
||||
|
||||
isCpuPinning(spec) {
|
||||
return !!spec?.template?.spec?.domain?.cpu?.dedicatedCpuPlacement;
|
||||
},
|
||||
|
||||
getCloudInitNoCloud(spec) {
|
||||
const secret = this.getSecret(spec);
|
||||
let userData = secret?.decodedData?.userdata;
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
import { HOSTNAME } from '@shell/config/labels-annotations';
|
||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||
import { uniq } from '@shell/utils/array';
|
||||
import { ADD_ONS, SOURCE_TYPE, ACCESS_CREDENTIALS } from '../../config/harvester-map';
|
||||
import { ADD_ONS, SOURCE_TYPE, ACCESS_CREDENTIALS, maintenanceStrategies, runStrategies } from '../../config/harvester-map';
|
||||
import { HCI_SETTING } from '../../config/settings';
|
||||
import { HCI } from '../../types';
|
||||
import { parseVolumeClaimTemplates } from '../../utils/vm';
|
||||
@ -136,6 +136,9 @@ export default {
|
||||
spec: null,
|
||||
osType: 'linux',
|
||||
sshKey: [],
|
||||
maintenanceStrategies,
|
||||
maintenanceStrategy: 'Migrate',
|
||||
runStrategies,
|
||||
runStrategy: 'RerunOnFailure',
|
||||
installAgent: true,
|
||||
hasCreateVolumes: [],
|
||||
@ -164,6 +167,7 @@ export default {
|
||||
enabledSriovgpu: false,
|
||||
immutableMode: this.realMode === _CREATE ? _CREATE : _VIEW,
|
||||
terminationGracePeriodSeconds: '',
|
||||
cpuPinning: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -316,6 +320,11 @@ export default {
|
||||
};
|
||||
}
|
||||
|
||||
if (!vm.metadata.labels) {
|
||||
vm.metadata.labels = {};
|
||||
}
|
||||
const maintenanceStrategy = vm.metadata.labels?.[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] || 'Migrate';
|
||||
|
||||
const runStrategy = spec.runStrategy || 'RerunOnFailure';
|
||||
const machineType = value.machineType;
|
||||
const cpu = spec.template.spec.domain?.cpu?.cores;
|
||||
@ -352,6 +361,7 @@ export default {
|
||||
const efiEnabled = this.isEfiEnabled(spec);
|
||||
const tpmEnabled = this.isTpmEnabled(spec);
|
||||
const secureBoot = this.isSecureBoot(spec);
|
||||
const cpuPinning = this.isCpuPinning(spec);
|
||||
|
||||
const secretRef = this.getSecret(spec);
|
||||
const accessCredentials = this.getAccessCredentials(spec);
|
||||
@ -362,6 +372,7 @@ export default {
|
||||
}
|
||||
|
||||
this['spec'] = spec;
|
||||
this['maintenanceStrategy'] = maintenanceStrategy;
|
||||
this['runStrategy'] = runStrategy;
|
||||
this['secretRef'] = secretRef;
|
||||
this['accessCredentials'] = accessCredentials;
|
||||
@ -382,6 +393,7 @@ export default {
|
||||
this['efiEnabled'] = efiEnabled;
|
||||
this['tpmEnabled'] = tpmEnabled;
|
||||
this['secureBoot'] = secureBoot;
|
||||
this['cpuPinning'] = cpuPinning;
|
||||
|
||||
this['hasCreateVolumes'] = hasCreateVolumes;
|
||||
this['networkRows'] = networkRows;
|
||||
@ -401,15 +413,37 @@ export default {
|
||||
let out = [];
|
||||
|
||||
if (_disks.length === 0) {
|
||||
let bus = 'virtio';
|
||||
let type = HARD_DISK;
|
||||
let size = '10Gi';
|
||||
|
||||
const imageResource = this.images.find( I => this.imageId === I.id);
|
||||
const isIsoImage = /iso$/i.test(imageResource?.imageSuffix);
|
||||
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
|
||||
|
||||
if (isIsoImage) {
|
||||
bus = 'sata';
|
||||
type = CD_ROM;
|
||||
}
|
||||
|
||||
if (imageSize) {
|
||||
let imageSizeGiB = Math.ceil(imageSize / 1024 / 1024 / 1024);
|
||||
|
||||
if (!isIsoImage) {
|
||||
imageSizeGiB = Math.max(imageSizeGiB, 10);
|
||||
}
|
||||
size = `${ imageSizeGiB }Gi`;
|
||||
}
|
||||
|
||||
out.push({
|
||||
id: randomStr(5),
|
||||
source: SOURCE_TYPE.IMAGE,
|
||||
name: 'disk-0',
|
||||
accessMode: 'ReadWriteMany',
|
||||
bus: 'virtio',
|
||||
bus,
|
||||
volumeName: '',
|
||||
size: '10Gi',
|
||||
type: HARD_DISK,
|
||||
size,
|
||||
type,
|
||||
storageClassName: '',
|
||||
image: this.imageId,
|
||||
volumeMode: 'Block',
|
||||
@ -574,6 +608,12 @@ export default {
|
||||
} else {
|
||||
vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY] = this.reservedMemory;
|
||||
}
|
||||
|
||||
if (this.maintenanceStrategy === 'Migrate') {
|
||||
delete vm.metadata.labels[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY];
|
||||
} else {
|
||||
vm.metadata.labels[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] = this.maintenanceStrategy;
|
||||
}
|
||||
},
|
||||
|
||||
parseDiskRows(disk) {
|
||||
@ -681,27 +721,24 @@ export default {
|
||||
spec = this.multiVMScheduler(spec);
|
||||
}
|
||||
|
||||
this.value.metadata['annotations'] = {
|
||||
...this.value.metadata.annotations,
|
||||
this.value.metadata['annotations'] = {...this.value.metadata.annotations,
|
||||
[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
|
||||
[HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps)
|
||||
};
|
||||
[HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps)};
|
||||
|
||||
this.value.metadata['labels'] = {
|
||||
...this.value.metadata.labels,
|
||||
this.value.metadata['labels'] = {...this.value.metadata.labels,
|
||||
[HCI_ANNOTATIONS.CREATOR]: 'harvester',
|
||||
[HCI_ANNOTATIONS.OS]: this.osType
|
||||
};
|
||||
[HCI_ANNOTATIONS.OS]: this.osType};
|
||||
|
||||
this.value['spec'] = spec;
|
||||
this['spec'] = spec;
|
||||
} else if (this.resource === HCI.VM_VERSION) {
|
||||
this.value.spec.vm['spec'] = spec;
|
||||
this.value.spec.vm.metadata['annotations'] = { ...this.value.spec.vm.metadata.annotations, [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates) };
|
||||
this.value.spec.vm.metadata['labels'] = {
|
||||
...this.value.spec.vm.metadata.labels,
|
||||
[HCI_ANNOTATIONS.OS]: this.osType
|
||||
this.value.spec.vm.metadata['annotations'] = {
|
||||
...this.value.spec.vm.metadata.annotations,
|
||||
[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
|
||||
};
|
||||
this.value.spec.vm.metadata['labels'] = {...this.value.spec.vm.metadata.labels,
|
||||
[HCI_ANNOTATIONS.OS]: this.osType,};
|
||||
this['spec'] = spec;
|
||||
}
|
||||
},
|
||||
@ -819,6 +856,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getMaintenanceStrategyOptionLabel(opt) {
|
||||
return this.t(`harvester.virtualMachine.maintenanceStrategy.options.${ opt.label || opt }`);
|
||||
},
|
||||
|
||||
getInitUserData(config) {
|
||||
const _QGA_JSON = this.getMatchQGA(config.osType);
|
||||
|
||||
@ -1339,6 +1380,14 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
setCpuPinning(value) {
|
||||
if (value) {
|
||||
set(this.spec.template.spec.domain.cpu, 'dedicatedCpuPlacement', true);
|
||||
} else {
|
||||
delete this.spec.template.spec.domain.cpu['dedicatedCpuPlacement'];
|
||||
}
|
||||
},
|
||||
|
||||
setTPM(tpmEnabled) {
|
||||
if (tpmEnabled) {
|
||||
set(this.spec.template.spec.domain.devices, 'tpm', {});
|
||||
@ -1462,6 +1511,10 @@ export default {
|
||||
this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
|
||||
},
|
||||
|
||||
cpuPinning(value) {
|
||||
this.setCpuPinning(value);
|
||||
},
|
||||
|
||||
tpmEnabled(val) {
|
||||
this.setTPM(val);
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
|
||||
import { get } from '@shell/utils/object';
|
||||
import { NAMESPACE } from '@shell/config/types';
|
||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
|
||||
import { HCI } from '../../types';
|
||||
|
||||
const OBSCURE_NAMESPACE_PREFIX = [
|
||||
'c-', // cluster namespace
|
||||
@ -37,15 +38,32 @@ export default class HciNamespace extends namespace {
|
||||
weight: -10,
|
||||
};
|
||||
|
||||
const editQuotaAction = {
|
||||
action: 'editNSQuota',
|
||||
label: this.t('harvester.modal.quota.editQuota'),
|
||||
icon: 'icon icon-storage',
|
||||
enabled: !!this?.actions?.updateResourceQuota && !!this?.actions?.deleteResourceQuota,
|
||||
weight: -11,
|
||||
};
|
||||
|
||||
if (remove > -1) {
|
||||
out.splice(remove, 1);
|
||||
}
|
||||
|
||||
insertAt(out, out.length - 1, promptRemove);
|
||||
insertAt(out, out.length - 5, editQuotaAction);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
editNSQuota(resources = this) {
|
||||
this.$dispatch('promptModal', {
|
||||
resources,
|
||||
snapshotSizeQuota: this.snapshotSizeQuota,
|
||||
component: 'HarvesterQuotaDialog'
|
||||
});
|
||||
}
|
||||
|
||||
promptRemove(resources = this) {
|
||||
this.$dispatch('promptModal', {
|
||||
resources,
|
||||
@ -54,6 +72,17 @@ export default class HciNamespace extends namespace {
|
||||
});
|
||||
}
|
||||
|
||||
get nsResourceQuota() {
|
||||
const inStore = this.$rootGetters['currentProduct'].inStore;
|
||||
const allResQuotas = this.$rootGetters[`${ inStore }/all`](HCI.RESOURCE_QUOTA);
|
||||
|
||||
return allResQuotas.find( RQ => RQ.metadata.namespace === this.id);
|
||||
}
|
||||
|
||||
get snapshotSizeQuota() {
|
||||
return this.nsResourceQuota?.spec?.snapshotLimit?.namespaceTotalSnapshotSizeQuota;
|
||||
}
|
||||
|
||||
get isSystem() {
|
||||
const systemNamespaces = ['fleet-default'];
|
||||
|
||||
|
||||
@ -59,6 +59,22 @@ export default class HciNode extends HarvesterResource {
|
||||
total: 1
|
||||
};
|
||||
|
||||
const enableCPUManager = {
|
||||
action: 'enableCPUManager',
|
||||
enabled: this.hasAction('enableCPUManager') && !this.isCPUManagerEnableInProgress && !this.isCPUManagerEnabled && !this.isEtcd, // witness node doesn't have CPU manager
|
||||
icon: 'icon icon-fw icon-os-management',
|
||||
label: this.t('harvester.action.enableCPUManager'),
|
||||
total: 1
|
||||
};
|
||||
|
||||
const disableCPUManager = {
|
||||
action: 'disableCPUManager',
|
||||
enabled: this.hasAction('disableCPUManager') && !this.isCPUManagerEnableInProgress && this.isCPUManagerEnabled && !this.isEtcd,
|
||||
icon: 'icon icon-fw icon-os-management',
|
||||
label: this.t('harvester.action.disableCPUManager'),
|
||||
total: 1
|
||||
};
|
||||
|
||||
const shutDown = {
|
||||
action: 'shutDown',
|
||||
enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && !this.isStopped && !!this.inventory,
|
||||
@ -88,6 +104,8 @@ export default class HciNode extends HarvesterResource {
|
||||
uncordon,
|
||||
enableMaintenance,
|
||||
disableMaintenance,
|
||||
enableCPUManager,
|
||||
disableCPUManager,
|
||||
shutDown,
|
||||
powerOn,
|
||||
reboot,
|
||||
@ -134,15 +152,12 @@ export default class HciNode extends HarvesterResource {
|
||||
|
||||
get consoleUrl() {
|
||||
const url = this.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
|
||||
const validator = /^[a-z]+:\/\//;
|
||||
|
||||
if (!url) {
|
||||
if (!url?.match(validator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
return `http://${ url }`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
@ -308,6 +323,14 @@ export default class HciNode extends HarvesterResource {
|
||||
this.doAction('disableMaintenanceMode', {});
|
||||
}
|
||||
|
||||
enableCPUManager() {
|
||||
this.doActionGrowl('enableCPUManager', {});
|
||||
}
|
||||
|
||||
disableCPUManager() {
|
||||
this.doActionGrowl('disableCPUManager', {});
|
||||
}
|
||||
|
||||
get isUnSchedulable() {
|
||||
return (
|
||||
this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_SCHEDULABLE] === 'false' ||
|
||||
@ -347,6 +370,28 @@ export default class HciNode extends HarvesterResource {
|
||||
);
|
||||
}
|
||||
|
||||
get isCPUManagerEnabled() {
|
||||
return this.metadata?.labels?.[HCI_ANNOTATIONS.CPU_MANAGER] === 'true';
|
||||
}
|
||||
|
||||
get isCPUManagerEnableInProgress() {
|
||||
return this.cpuManagerUpdateStatus === 'requested' || this.cpuManagerUpdateStatus === 'running';
|
||||
}
|
||||
|
||||
get isCPUManagerEnableFailed() {
|
||||
return this.cpuManagerUpdateStatus === 'failed';
|
||||
}
|
||||
|
||||
get cpuManagerUpdateStatus() {
|
||||
try {
|
||||
const cpuManagerUpdate = JSON.parse(this.metadata.annotations[HCI_ANNOTATIONS.NODE_CPU_MANAGER_UPDATE_STATUS] || '{}');
|
||||
|
||||
return cpuManagerUpdate.status || '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
get longhornDisks() {
|
||||
const inStore = this.$rootGetters['currentProduct'].inStore;
|
||||
const longhornNode = this.$rootGetters[`${ inStore }/byId`](
|
||||
|
||||
@ -20,12 +20,10 @@ export default class HciPv extends HarvesterResource {
|
||||
const storageClassName =
|
||||
realMode === _CLONE ? this.spec.storageClassName : '';
|
||||
|
||||
this['spec'] = {
|
||||
accessModes,
|
||||
this['spec'] = {accessModes,
|
||||
storageClassName,
|
||||
volumeName: '',
|
||||
resources: { requests: { storage } }
|
||||
};
|
||||
resources: { requests: { storage } }};
|
||||
}
|
||||
|
||||
get availableActions() {
|
||||
@ -98,7 +96,6 @@ export default class HciPv extends HarvesterResource {
|
||||
}
|
||||
|
||||
get stateDisplay() {
|
||||
const ownedBy = this?.metadata?.annotations?.[HCI_ANNOTATIONS.OWNED_BY];
|
||||
const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR];
|
||||
const degradedVolume = volumeError === DEGRADED_ERROR;
|
||||
const status = this?.status?.phase === 'Bound' && !volumeError && this.isLonghornVolumeReady ? 'Ready' : 'Not Ready';
|
||||
@ -107,7 +104,7 @@ export default class HciPv extends HarvesterResource {
|
||||
|
||||
if (findBy(conditions, 'type', 'Resizing')?.status === 'True') {
|
||||
return 'Resizing';
|
||||
} else if (ownedBy && !volumeError) {
|
||||
} else if (!!this.attachVM && !volumeError) {
|
||||
return 'In-use';
|
||||
} else if (degradedVolume) {
|
||||
return 'Degraded';
|
||||
@ -179,17 +176,19 @@ export default class HciPv extends HarvesterResource {
|
||||
}
|
||||
|
||||
get attachVM() {
|
||||
const allVMs = this.$rootGetters['harvester/all'](HCI.VM);
|
||||
const ownedBy =
|
||||
get(this, `metadata.annotations."${ HCI_ANNOTATIONS.OWNED_BY }"`) || '';
|
||||
const allVMs = this.$rootGetters['harvester/all'](HCI.VM) || [];
|
||||
|
||||
const findAttachVM = (vm) => {
|
||||
const attachVolumes = vm.spec.template?.spec?.volumes || [];
|
||||
|
||||
if (vm.namespace === this.namespace && attachVolumes.length > 0) {
|
||||
return attachVolumes.find(vol => vol.persistentVolumeClaim?.claimName === this.name);
|
||||
}
|
||||
|
||||
if (!ownedBy) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const ownedId = JSON.parse(ownedBy)[0]?.refs?.[0];
|
||||
|
||||
return allVMs.find(D => D.id === ownedId);
|
||||
return allVMs.find(findAttachVM);
|
||||
}
|
||||
|
||||
get isAvailable() {
|
||||
|
||||
@ -9,6 +9,10 @@ export default class HciUpgrade extends HarvesterResource {
|
||||
return this?.metadata?.labels?.[HCI.LATEST_UPGRADE] === 'true';
|
||||
}
|
||||
|
||||
get isUpgradeFailed() {
|
||||
return this?.metadata?.labels?.[HCI.UPGRADE_STATE] === 'Failed';
|
||||
}
|
||||
|
||||
get isUpgradeSucceeded() {
|
||||
return this?.metadata?.labels?.[HCI.UPGRADE_STATE] === 'Succeeded';
|
||||
}
|
||||
@ -117,10 +121,10 @@ export default class HciUpgrade extends HarvesterResource {
|
||||
get upgradeImageMessage() {
|
||||
const conditions = this?.status?.conditions || [];
|
||||
const imageReady = conditions.find( cond => cond.type === 'ImageReady');
|
||||
const hasError = imageReady?.status === 'False';
|
||||
const success = imageReady?.status === 'True';
|
||||
const message = imageReady?.message || imageReady?.reason;
|
||||
|
||||
return hasError ? message : '';
|
||||
return success ? '' : message;
|
||||
}
|
||||
|
||||
get nodeUpgradeMessage() {
|
||||
|
||||
@ -47,7 +47,7 @@ export default class HciVmImage extends HarvesterResource {
|
||||
{
|
||||
action: 'createFromImage',
|
||||
enabled: canCreateVM,
|
||||
icon: 'icon icon-fw icon-spinner',
|
||||
icon: 'icon icon-circle-plus',
|
||||
label: this.t('harvester.action.createVM'),
|
||||
disabled: !this.isReady,
|
||||
},
|
||||
@ -74,7 +74,7 @@ export default class HciVmImage extends HarvesterResource {
|
||||
router.push({
|
||||
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-create`,
|
||||
params: { resource: HCI.VM },
|
||||
query: { image: this.id }
|
||||
query: { image: this.id, fromPage: HCI.IMAGE }
|
||||
});
|
||||
}
|
||||
|
||||
@ -101,6 +101,10 @@ export default class HciVmImage extends HarvesterResource {
|
||||
const imported = this.getStatusConditionOfType('Imported');
|
||||
|
||||
if (imported?.status === 'Unknown') {
|
||||
if (this.spec.sourceType === 'restore') {
|
||||
return 'Restoring';
|
||||
}
|
||||
|
||||
if (this.spec.sourceType === 'download') {
|
||||
return 'Downloading';
|
||||
}
|
||||
@ -131,7 +135,8 @@ export default class HciVmImage extends HarvesterResource {
|
||||
const conditions = this?.status?.conditions || [];
|
||||
const initialized = conditions.find( cond => cond.type === 'Initialized');
|
||||
const imported = conditions.find( cond => cond.type === 'Imported');
|
||||
const message = initialized?.message || imported?.message;
|
||||
const retryLimitExceeded = conditions.find( cond => cond.type === 'RetryLimitExceeded');
|
||||
const message = initialized?.message || imported?.message || retryLimitExceeded?.message;
|
||||
|
||||
return ucFirst(message);
|
||||
}
|
||||
@ -167,6 +172,21 @@ export default class HciVmImage extends HarvesterResource {
|
||||
});
|
||||
}
|
||||
|
||||
get virtualSize() {
|
||||
const virtualSize = this.status?.virtualSize;
|
||||
|
||||
if (!virtualSize) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return formatSi(virtualSize, {
|
||||
increment: 1024,
|
||||
maxPrecision: 2,
|
||||
suffix: 'B',
|
||||
firstSuffix: 'B',
|
||||
});
|
||||
}
|
||||
|
||||
getStatusConditionOfType(type, defaultValue = []) {
|
||||
const conditions = Array.isArray(get(this, 'status.conditions')) ? this.status.conditions : defaultValue;
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ export default class VirtVm extends HarvesterResource {
|
||||
{
|
||||
action: 'softrebootVM',
|
||||
enabled: !!this.actions?.softreboot,
|
||||
icon: 'icon icon-refresh',
|
||||
icon: 'icon icon-pipeline',
|
||||
label: this.t('harvester.action.softreboot')
|
||||
},
|
||||
{
|
||||
@ -152,9 +152,15 @@ export default class VirtVm extends HarvesterResource {
|
||||
{
|
||||
action: 'takeVMSnapshot',
|
||||
enabled: !!this.actions?.backup,
|
||||
icon: 'icon icon-backup',
|
||||
icon: 'icon icon-snapshot',
|
||||
label: this.t('harvester.action.vmSnapshot')
|
||||
},
|
||||
{
|
||||
action: 'editVMQuota',
|
||||
enabled: !!this.actions?.updateResourceQuota && !!this.actions.deleteResourceQuota,
|
||||
icon: 'icon icon-storage',
|
||||
label: this.t('harvester.action.editVMQuota')
|
||||
},
|
||||
{
|
||||
action: 'restoreVM',
|
||||
enabled: !!this.actions?.restore,
|
||||
@ -331,6 +337,14 @@ export default class VirtVm extends HarvesterResource {
|
||||
});
|
||||
}
|
||||
|
||||
editVMQuota(resources = this) {
|
||||
this.$dispatch('promptModal', {
|
||||
resources,
|
||||
snapshotSizeQuota: this.snapshotSizeQuota,
|
||||
component: 'HarvesterQuotaDialog'
|
||||
});
|
||||
}
|
||||
|
||||
unplugVolume(diskName) {
|
||||
const resources = this;
|
||||
|
||||
@ -541,6 +555,17 @@ export default class VirtVm extends HarvesterResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
get nsResourceQuota() {
|
||||
const inStore = this.productInStore;
|
||||
const allResQuotas = this.$rootGetters[`${ inStore }/all`](HCI.RESOURCE_QUOTA);
|
||||
|
||||
return allResQuotas.find( RQ => RQ.namespace === this.metadata.namespace);
|
||||
}
|
||||
|
||||
get snapshotSizeQuota() {
|
||||
return this.nsResourceQuota?.spec?.snapshotLimit?.vmTotalSnapshotSizeQuota?.[this.metadata.name];
|
||||
}
|
||||
|
||||
get vmi() {
|
||||
const inStore = this.productInStore;
|
||||
|
||||
@ -646,6 +671,10 @@ export default class VirtVm extends HarvesterResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
get isTerminating() {
|
||||
return !!this?.metadata?.deletionTimestamp;
|
||||
}
|
||||
|
||||
get otherState() {
|
||||
const state = (this.vmi &&
|
||||
[VMIPhase.Scheduling, VMIPhase.Scheduled].includes(
|
||||
@ -685,18 +714,21 @@ export default class VirtVm extends HarvesterResource {
|
||||
|
||||
const allRestore = this.$rootGetters[`${ inStore }/all`](HCI.RESTORE);
|
||||
|
||||
return allRestore.find(O => O.id === id);
|
||||
const res = allRestore.find(O => O.id === id);
|
||||
|
||||
if (res) {
|
||||
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
|
||||
|
||||
res.fromSnapshot = !!allBackups
|
||||
.filter(b => b.spec?.type !== BACKUP_TYPE.BACKUP)
|
||||
.find(s => s.id === `${ res.spec?.virtualMachineBackupNamespace }/${ res.spec?.virtualMachineBackupName }`);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
get restoreProgress() {
|
||||
const inStore = this.productInStore;
|
||||
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
|
||||
|
||||
const isSnapshotRestore = !!allBackups
|
||||
.filter(b => b.spec?.type !== BACKUP_TYPE.BACKUP)
|
||||
.find(s => s.id === `${ this.restoreResource?.spec?.virtualMachineBackupNamespace }/${ this.restoreResource?.spec?.virtualMachineBackupName }`);
|
||||
|
||||
if (isSnapshotRestore) {
|
||||
if (this.isVMError || this.isTerminating) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -726,7 +758,7 @@ export default class VirtVm extends HarvesterResource {
|
||||
return 'Restoring';
|
||||
}
|
||||
|
||||
if (this?.metadata?.deletionTimestamp) {
|
||||
if (this.isTerminating) {
|
||||
return 'Terminating';
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import CommunityLinks from '@shell/components/CommunityLinks';
|
||||
import { SCHEMA } from '@shell/config/types';
|
||||
import HarvesterSupportBundle from '../../../../dialog/HarvesterSupportBundle';
|
||||
import { HCI } from '../../../../types';
|
||||
import { DOC_LINKS } from '../../../../config/doc-links';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -71,13 +72,18 @@ export default {
|
||||
const { host, params } = this.internalPrefix;
|
||||
|
||||
return `https://${ host }/k8s/clusters/${ params.cluster }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/#/dashboard`;
|
||||
}
|
||||
},
|
||||
|
||||
rancherIntegrationLink() {
|
||||
return DOC_LINKS.RANCHER_INTEGRATION_URL;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.$store.commit('harvester-common/toggleBundleModal', true);
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -89,10 +95,7 @@ export default {
|
||||
<IndentedPanel>
|
||||
<div class="content mt-20">
|
||||
<div class="promo">
|
||||
<div
|
||||
v-if="showSupportBundle"
|
||||
class="box mb-20 box-primary"
|
||||
>
|
||||
<div v-if="showSupportBundle" class="box mb-20 box-primary">
|
||||
<h2>
|
||||
{{ t('harvester.modal.bundle.title') }}
|
||||
</h2>
|
||||
@ -109,10 +112,7 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box box-primary"
|
||||
:class="{'mb-20': dev }"
|
||||
>
|
||||
<div class="box box-primary" :class="{'mb-20': dev }">
|
||||
<h2>
|
||||
{{ t('harvester.support.kubeconfig.title') }}
|
||||
</h2>
|
||||
@ -129,41 +129,24 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="dev"
|
||||
class="row"
|
||||
>
|
||||
<div v-if="dev" class="row">
|
||||
<div class="col span-6 box box-primary">
|
||||
<h2>
|
||||
<a
|
||||
rel="nofollow noopener noreferrer"
|
||||
target="_blank"
|
||||
:href="rancherLink"
|
||||
>{{ t('harvester.support.internal.rancher.title') }} <i class="icon icon-external-link" /></a>
|
||||
<a rel="nofollow noopener noreferrer" target="_blank" :href="rancherLink">{{ t('harvester.support.internal.rancher.title') }} <i class="icon icon-external-link" /></a>
|
||||
</h2>
|
||||
<div>
|
||||
<p class="warning">
|
||||
<t
|
||||
k="harvester.support.internal.rancher.titleDescription"
|
||||
:raw="true"
|
||||
/>
|
||||
<t k="harvester.support.internal.rancher.titleDescription" :raw="true" :url="rancherIntegrationLink" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col span-6 box box-primary">
|
||||
<h2>
|
||||
<a
|
||||
rel="nofollow noopener noreferrer"
|
||||
target="_blank"
|
||||
:href="longhornLink"
|
||||
>{{ t('harvester.support.internal.longhorn.title') }} <i class="icon icon-external-link" /></a>
|
||||
<a rel="nofollow noopener noreferrer" target="_blank" :href="longhornLink">{{ t('harvester.support.internal.longhorn.title') }} <i class="icon icon-external-link" /></a>
|
||||
</h2>
|
||||
<div>
|
||||
<p class="warning">
|
||||
<t
|
||||
k="harvester.support.internal.longhorn.titleDescription"
|
||||
:raw="true"
|
||||
/>
|
||||
<t k="harvester.support.internal.longhorn.titleDescription" :raw="true" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -173,17 +156,9 @@ export default {
|
||||
<CommunityLinks :link-options="options" />
|
||||
</div>
|
||||
<div class="external">
|
||||
<a
|
||||
href="https://www.suse.com/suse-harvester/support-matrix/all-supported-versions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>{{ t('harvester.support.community.learnMore') }} <i class="icon icon-external-link" /></a>
|
||||
<a href="https://www.suse.com/suse-harvester/support-matrix/all-supported-versions" target="_blank" rel="noopener noreferrer nofollow">{{ t('harvester.support.community.learnMore') }} <i class="icon icon-external-link" /></a>
|
||||
or
|
||||
<a
|
||||
href="https://www.suse.com/products/harvester/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>{{ t('harvester.support.community.pricing') }} <i class="icon icon-external-link" /></a>
|
||||
<a href="https://www.suse.com/products/harvester/" target="_blank" rel="noopener noreferrer nofollow">{{ t('harvester.support.community.pricing') }} <i class="icon icon-external-link" /></a>
|
||||
</div>
|
||||
</div>
|
||||
</IndentedPanel>
|
||||
|
||||
@ -18,16 +18,16 @@ export default {
|
||||
const cluster = await dispatch('management/find', {
|
||||
type: MANAGEMENT.CLUSTER,
|
||||
id,
|
||||
opt: { url: `${MANAGEMENT.CLUSTER}s/${escape(id)}` }
|
||||
opt: { url: `${ MANAGEMENT.CLUSTER }s/${ escape(id) }` }
|
||||
}, { root: true });
|
||||
|
||||
let virtualBase = `/k8s/clusters/${escape(id)}/v1/harvester`;
|
||||
let virtualBase = `/k8s/clusters/${ escape(id) }/v1/harvester`;
|
||||
|
||||
if (id === 'local') {
|
||||
virtualBase = `/v1/harvester`;
|
||||
}
|
||||
|
||||
if (!cluster) {
|
||||
if ( !cluster ) {
|
||||
commit('clusterId', null, { root: true });
|
||||
commit('applyConfig', { baseUrl: null });
|
||||
throw new ClusterNotFoundError(id);
|
||||
@ -44,22 +44,22 @@ export default {
|
||||
|
||||
const projectArgs = {
|
||||
type: MANAGEMENT.PROJECT,
|
||||
opt: {
|
||||
url: `${MANAGEMENT.PROJECT}/${escape(id)}`,
|
||||
opt: {
|
||||
url: `${ MANAGEMENT.PROJECT }/${ escape(id) }`,
|
||||
watchNamespace: id
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProjects = async () => {
|
||||
const fetchProjects = async() => {
|
||||
let limit = 30000;
|
||||
const sleep = 100;
|
||||
|
||||
while (limit > 0 && !rootState.managementReady) {
|
||||
await setTimeout(() => { }, sleep);
|
||||
while ( limit > 0 && !rootState.managementReady ) {
|
||||
await setTimeout(() => {}, sleep);
|
||||
limit -= sleep;
|
||||
}
|
||||
|
||||
if (rootGetters['management/schemaFor'](MANAGEMENT.PROJECT)) {
|
||||
if ( rootGetters['management/schemaFor'](MANAGEMENT.PROJECT) ) {
|
||||
return dispatch('management/findAll', projectArgs, { root: true });
|
||||
}
|
||||
};
|
||||
@ -67,8 +67,8 @@ export default {
|
||||
if (id !== 'local' && getters['schemaFor'](MANAGEMENT.SETTING)) { // multi-cluster
|
||||
const settings = await dispatch('findAll', {
|
||||
type: MANAGEMENT.SETTING,
|
||||
id: SETTING.SYSTEM_NAMESPACES,
|
||||
opt: { url: `${virtualBase}/${MANAGEMENT.SETTING}s/`, force: true }
|
||||
id: SETTING.SYSTEM_NAMESPACES,
|
||||
opt: { url: `${ virtualBase }/${ MANAGEMENT.SETTING }s/`, force: true }
|
||||
});
|
||||
|
||||
const systemNamespaces = settings?.find((x: any) => x.id === SETTING.SYSTEM_NAMESPACES);
|
||||
@ -80,17 +80,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const hash: { [key: string]: Promise<any> } = {
|
||||
projects: fetchProjects(),
|
||||
virtualCount: dispatch('findAll', { type: COUNT }),
|
||||
const hash: { [key: string]: Promise<any>} = {
|
||||
projects: fetchProjects(),
|
||||
virtualCount: dispatch('findAll', { type: COUNT }),
|
||||
virtualNamespaces: dispatch('findAll', { type: NAMESPACE }),
|
||||
settings: dispatch('findAll', { type: HCI.SETTING }),
|
||||
clusters: dispatch('management/findAll', {
|
||||
settings: dispatch('findAll', { type: HCI.SETTING }),
|
||||
clusters: dispatch('management/findAll', {
|
||||
type: MANAGEMENT.CLUSTER,
|
||||
opt: { force: true }
|
||||
opt: { force: true }
|
||||
}, { root: true }),
|
||||
};
|
||||
|
||||
if (getters['schemaFor'](HCI.RESOURCE_QUOTA)) {
|
||||
hash.resourceQuota = dispatch('findAll', { type: HCI.RESOURCE_QUOTA });
|
||||
}
|
||||
|
||||
if (getters['schemaFor'](HCI.UPGRADE)) {
|
||||
hash.upgrades = dispatch('findAll', { type: HCI.UPGRADE });
|
||||
}
|
||||
@ -101,15 +105,15 @@ export default {
|
||||
|
||||
commit('updateNamespaces', {
|
||||
filters: [],
|
||||
all: getters.filterNamespace(),
|
||||
all: getters.filterNamespace(),
|
||||
getters
|
||||
}, { root: true });
|
||||
|
||||
// Solve compatibility with Rancher v2.6.x, fell remove these codes after not support v2.6.x
|
||||
const definition = {
|
||||
def: false,
|
||||
parseJSON: true,
|
||||
inheritFrom: DEV,
|
||||
def: false,
|
||||
parseJSON: true,
|
||||
inheritFrom: DEV,
|
||||
asUserPreference: true,
|
||||
};
|
||||
|
||||
@ -134,9 +138,9 @@ export default {
|
||||
|
||||
if (isMultiCluster) {
|
||||
commit('managementChanged', {
|
||||
ready: true,
|
||||
ready: true,
|
||||
isMultiCluster: true,
|
||||
isRancher: true,
|
||||
isRancher: true,
|
||||
}, { root: true });
|
||||
}
|
||||
},
|
||||
|
||||
@ -14,9 +14,9 @@ export default {
|
||||
divider,
|
||||
notFilterNamespaces
|
||||
}: any) => {
|
||||
const out: { id: string, kind: string, label: string }[] = [{
|
||||
id: ALL,
|
||||
kind: NAMESPACE_FILTER_KINDS.SPECIAL,
|
||||
const out = [{
|
||||
id: ALL,
|
||||
kind: NAMESPACE_FILTER_KINDS.SPECIAL,
|
||||
label: rootGetters['i18n/t']('nav.ns.all'),
|
||||
}];
|
||||
|
||||
@ -30,11 +30,9 @@ export default {
|
||||
MANAGEMENT.PROJECT
|
||||
);
|
||||
|
||||
projects = sortBy(
|
||||
filterBy(projects, 'spec.clusterName', cluster.id),
|
||||
['nameDisplay'],
|
||||
null
|
||||
).filter((project: any) => project.nameDisplay !== 'System');
|
||||
projects = sortBy(filterBy(projects, 'spec.clusterName', cluster.id), [
|
||||
'nameDisplay',
|
||||
]).filter((project: any) => project.nameDisplay !== 'System');
|
||||
|
||||
const projectsById: any = {};
|
||||
const namespacesByProject: any = {};
|
||||
@ -72,8 +70,8 @@ export default {
|
||||
}
|
||||
|
||||
out.push({
|
||||
id: `project://${id}`,
|
||||
kind: 'project',
|
||||
id: `project://${ id }`,
|
||||
kind: 'project',
|
||||
label: project.nameDisplay,
|
||||
});
|
||||
|
||||
@ -90,8 +88,8 @@ export default {
|
||||
}
|
||||
|
||||
out.push({
|
||||
id: ALL_ORPHANS,
|
||||
kind: 'project',
|
||||
id: ALL_ORPHANS,
|
||||
kind: 'project',
|
||||
label: rootGetters['i18n/t']('nav.ns.orphan'),
|
||||
});
|
||||
|
||||
|
||||
@ -28,12 +28,12 @@ const harvesterFactory = (): CoreStoreSpecifics => {
|
||||
return steveFactory;
|
||||
};
|
||||
const config: CoreStoreConfig = {
|
||||
namespace: PRODUCT_NAME,
|
||||
namespace: PRODUCT_NAME,
|
||||
isClusterStore: true
|
||||
};
|
||||
|
||||
export default {
|
||||
specifics: harvesterFactory(),
|
||||
config,
|
||||
init: steveStoreInit
|
||||
init: steveStoreInit
|
||||
};
|
||||
|
||||
@ -31,6 +31,7 @@ export const HCI = {
|
||||
FLOW: 'harvesterhci.io.logging.flow',
|
||||
OUTPUT: 'harvesterhci.io.logging.output',
|
||||
STORAGE: 'harvesterhci.io.storage',
|
||||
RESOURCE_QUOTA: 'harvesterhci.io.resourcequota',
|
||||
KSTUNED: 'node.harvesterhci.io.ksmtuned',
|
||||
PCI_DEVICE: 'devices.harvesterhci.io.pcidevice',
|
||||
PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user