mergify[bot] 4599159bff
Refactor: the promptRemove.confirmRelatedResource message needs to be refactored (backport #293) (#343)
* refactor: the promptRemove.confirmRelatedResource message needs to be refactored (#293)

* feat: change dialog wordings

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

* refactor: update protip wording

Co-authored-by: Jillian Maroket <67180770+jillian-maroket@users.noreply.github.com>
Signed-off-by: Yiya Chen <yiya.chen@suse.com>

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
Signed-off-by: Yiya Chen <yiya.chen@suse.com>
Co-authored-by: Jillian Maroket <67180770+jillian-maroket@users.noreply.github.com>
(cherry picked from commit 258476876a24ee7c8dd22241ddde5c69f0527897)

# Conflicts:
#	pkg/harvester/dialog/ConfirmRelatedToRemoveDialog.vue

* Resolve conflict

Signed-off-by: Andy Lee <andy.lee@suse.com>

* Fix conflict

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-20 10:42:33 +08:00

662 lines
18 KiB
JavaScript

import pickBy from 'lodash/pickBy';
import { CAPI, LONGHORN, POD, NODE } from '@shell/config/types';
import { CAPI as CAPI_ANNOTATIONS } from '@shell/config/labels-annotations.js';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { clone } from '@shell/utils/object';
import findLast from 'lodash/findLast';
import {
colorForState,
stateDisplay,
STATES_ENUM,
} from '@shell/plugins/dashboard-store/resource-class';
import { parseSi } from '@shell/utils/units';
import { findBy, isArray } from '@shell/utils/array';
import { ucFirst } from '@shell/utils/string';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { HCI } from '../../types';
const ALLOW_SYSTEM_LABEL_KEYS = [
'topology.kubernetes.io/zone',
'topology.kubernetes.io/region',
];
const HEALTHY = 'healthy';
const WARNING = 'warning';
export default class HciNode extends HarvesterResource {
get _availableActions() {
const cordon = {
action: 'cordon',
enabled: this.hasAction('cordon') && !this.isCordoned,
icon: 'icon icon-fw icon-pause',
label: this.t('harvester.action.cordon'),
total: 1
};
const uncordon = {
action: 'uncordon',
enabled: this.hasAction('uncordon'),
icon: 'icon icon-fw icon-play',
label: this.t('harvester.action.uncordon'),
total: 1
};
const enableMaintenance = {
action: 'enableMaintenanceMode',
enabled: this.hasAction('enableMaintenanceMode'),
icon: 'icon icon-fw icon-unlock',
label: this.t('harvester.action.enableMaintenance'),
total: 1
};
const disableMaintenance = {
action: 'disableMaintenanceMode',
enabled: this.hasAction('disableMaintenanceMode'),
icon: 'icon icon-fw icon-lock',
label: this.t('harvester.action.disableMaintenance'),
total: 1
};
const enableCPUManager = {
action: 'enableCPUManager',
enabled: this.cpuPinningFeatureEnabled && this.hasAction('enableCPUManager') && !this.isCPUManagerEnableInProgress && !this.isCPUManagerEnabled && !this.isEtcd, // witness node doesn't have CPU manager
icon: 'icon icon-fw icon-os-management',
label: this.t('harvester.action.enableCPUManager'),
total: 1
};
const disableCPUManager = {
action: 'disableCPUManager',
enabled: this.cpuPinningFeatureEnabled && this.hasAction('disableCPUManager') && !this.isCPUManagerEnableInProgress && this.isCPUManagerEnabled && !this.isEtcd,
icon: 'icon icon-fw icon-os-management',
label: this.t('harvester.action.disableCPUManager'),
total: 1
};
const shutDown = {
action: 'shutDown',
enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && !this.isStopped && !!this.inventory,
icon: 'icon icon-fw icon-dot',
label: this.t('harvester.action.shutdown'),
total: 1
};
const powerOn = {
action: 'powerOn',
enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && this.isStopped && !!this.inventory,
icon: 'icon icon-fw icon-play',
label: this.t('harvester.action.powerOn'),
total: 1
};
const reboot = {
action: 'reboot',
enabled: this.hasAction('powerActionPossible') && this.hasAction('powerAction') && !this.isStopped && !!this.inventory,
icon: 'icon icon-fw icon-refresh',
label: this.t('harvester.action.reboot'),
total: 1
};
return [
cordon,
uncordon,
enableMaintenance,
disableMaintenance,
enableCPUManager,
disableCPUManager,
shutDown,
powerOn,
reboot,
...super._availableActions
];
}
promptRemove(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'ConfirmRelatedToRemoveDialog'
});
}
remove(resources = this) {
const nodes = Array.isArray(resources) ? resources : [resources];
nodes.forEach((node) => {
if (node.capiMachine) {
node.capiMachine.remove();
} else {
node.remove();
}
});
}
get capiMachine() {
const namespace = this.annotations?.[CAPI_ANNOTATIONS.CLUSTER_NAMESPACE];
const name = this.annotations?.[CAPI_ANNOTATIONS.MACHINE_NAME];
if (namespace && name) {
const inStore = this.$rootGetters['currentProduct'].inStore;
return this.$rootGetters[`${ inStore }/byId`](CAPI.MACHINE, `${ namespace }/${ name }`);
}
return null;
}
get confirmRemove() {
return true;
}
get consoleUrl() {
const url = this.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
const validator = /^[a-z]+:\/\//;
if (!url?.match(validator)) {
return false;
}
return url;
}
get filteredSystemLabels() {
const reg = /(k3s|kubernetes|kubevirt|harvesterhci|k3os)+\.io/;
const labels = pickBy(this.labels, (value, key) => {
return !reg.test(key);
});
ALLOW_SYSTEM_LABEL_KEYS.map((key) => {
const value = this?.metadata?.labels?.[key];
if (value) {
labels[key] = value;
}
});
return labels;
}
get nameDisplay() {
return (
this.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME] ||
this.name
);
}
get isKVMDisable() {
// Arm based
const isARMMachine = this.metadata.labels?.[HCI_ANNOTATIONS.K8S_ARCH]?.includes('arm');
if (isARMMachine) {
return this.status.capacity['devices.kubevirt.io/kvm'] && this.status.capacity['devices.kubevirt.io/kvm'] === '0';
}
const allNotExist = !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] && !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU];
return allNotExist || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] === 'false' || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU] === 'false';
}
get stateDisplay() {
if (this.isEnteringMaintenance) {
return 'Entering maintenance mode';
}
if (this.isStopping) {
return ucFirst(STATES_ENUM.STOPPING);
}
if (this.isStarting) {
return ucFirst(STATES_ENUM.STARTING);
}
if (this.isStopped) {
return ucFirst(STATES_ENUM.OFF);
}
if (this.isRebooting) {
return 'Rebooting';
}
if (this.isMaintenance) {
return 'Maintenance';
}
if (this.isCordoned) {
return 'Cordoned';
}
return stateDisplay(this.state);
}
get stateBackground() {
if (this.isStopped || this.isStopping || this.isStarting || this.isRebooting) {
return colorForState(
this.stateDisplay,
false,
this.stateObj?.transitioning
).replace('text-', 'bg-');
}
return colorForState(
this.stateDisplay,
this.stateObj?.error,
this.stateObj?.transitioning
).replace('text-', 'bg-');
}
get stateDescription() {
const currentIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.CURRENT_IP];
const initIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.INIT_IP];
if (initIP && currentIP && currentIP !== initIP) {
return this.t('harvester.host.inconsistentIP', { currentIP, initIP });
}
return super.stateDescription;
}
get stateObj() {
const currentIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.CURRENT_IP];
const initIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.INIT_IP];
if (initIP && currentIP && currentIP !== initIP) {
this.metadata.state.error = true;
}
return this.metadata?.state;
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.HOST;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.HOST;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.HOST }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get internalIp() {
const addresses = this.status?.addresses || [];
return findLast(addresses, (address) => address.type === 'InternalIP')
?.address;
}
get isMaster() {
return (
this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_MASTER] !== undefined ||
this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_CONTROL_PLANE] !==
undefined
);
}
cordon() {
this.doActionGrowl('cordon', {});
}
uncordon() {
this.doAction('uncordon', {});
}
enableMaintenanceMode(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'HarvesterMaintenanceDialog'
});
}
disableMaintenanceMode() {
this.doAction('disableMaintenanceMode', {});
}
enableCPUManager() {
this.doActionGrowl('enableCPUManager', {});
}
disableCPUManager() {
this.doActionGrowl('disableCPUManager', {});
}
get isUnSchedulable() {
return (
this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_SCHEDULABLE] === 'false' ||
this.spec.unschedulable
);
}
get isMigratable() {
const states = ['in-progress', 'unavailable'];
return (
!this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] &&
!this.isUnSchedulable &&
!states.includes(this.state)
);
}
get isCordoned() {
return (this.isUnSchedulable && !this.isEtcd) || this.hasAction('uncordon');
}
get isEtcd() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_ETCD];
}
get isEnteringMaintenance() {
return (
this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] ===
'running'
);
}
get isMaintenance() {
return (
this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] ===
'completed'
);
}
get cpuPinningFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('cpuPinning');
}
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`](
LONGHORN.NODES,
`longhorn-system/${ this.id }`
);
const diskStatus = longhornNode?.status?.diskStatus || {};
const diskSpec = longhornNode?.spec?.disks || {};
const longhornDisks = Object.keys(diskStatus).map((key) => {
const conditions = diskStatus[key]?.conditions || [];
let readyCondition = {};
let schedulableCondition = {};
if (isArray(conditions)) {
readyCondition = findBy(conditions, 'type', 'Ready') || {};
schedulableCondition = findBy(conditions, 'type', 'Schedulable') || {};
} else {
readyCondition = conditions.Ready;
schedulableCondition = conditions.Schedulable;
}
let state;
if (readyCondition?.status !== 'True' || schedulableCondition?.status !== 'True') {
state = WARNING;
} else {
state = HEALTHY;
}
return {
...diskSpec[key],
...diskStatus[key],
name: key,
storageReserved: diskSpec[key]?.storageReserved,
storageAvailable: diskStatus[key]?.storageAvailable,
storageMaximum: diskStatus[key]?.storageMaximum,
storageScheduled: diskStatus[key]?.storageScheduled,
readyCondition,
schedulableCondition,
state,
};
});
return longhornDisks;
}
get pods() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const pods = this.$rootGetters[`${ inStore }/all`](POD) || [];
return pods.filter(
(p) => p?.spec?.nodeName === this.id && p?.metadata?.name !== 'removing'
);
}
get reserved() {
try {
return JSON.parse(this.metadata.annotations[HCI_ANNOTATIONS.HOST_REQUEST] || '{}');
} catch {
return {};
}
}
get cpuReserved() {
return parseSi(this.reserved.cpu || '0');
}
get memoryReserved() {
return parseSi(this.reserved.memory || '0');
}
get canDelete() {
const nodes = this.$rootGetters['harvester/all'](NODE) || [];
return nodes.length > 1 && super.canDelete;
}
get vlanStatuses() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const nodeId = this.id;
const vlanStatuses = this.$rootGetters[`${ inStore }/all`](HCI.VLAN_STATUS);
return vlanStatuses.filter((s) => s?.status?.node === nodeId) || [];
}
get blockDevices() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const nodeId = this.id;
const blockDevices = this.$rootGetters[`${ inStore }/all`](HCI.BLOCK_DEVICE);
return blockDevices.filter((s) => s?.spec?.nodeName === nodeId) || [];
}
get unProvisionedDisks() {
const blockDevices = this.blockDevices || [];
return blockDevices.filter((d) => d?.isProvisioned && d?.status?.provisionPhase !== 'Provisioned');
}
get diskStatusCount() {
const errorBlockDevices = this.unProvisionedDisks.filter((b) => b.metadata.state.error) || [];
let errorCount = 0;
this.longhornDisks.map((d) => {
if (d.state === 'warning') {
errorCount++;
}
});
const total = this.longhornDisks.length + errorBlockDevices.length;
return {
total,
errorCount: errorCount + errorBlockDevices.length,
useful: total - errorCount,
};
}
get manufacturer() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_MANUFACTURER];
}
get serialNumber() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_SERIAL_NUMBER];
}
get model() {
return this.metadata?.labels?.[HCI_ANNOTATIONS.NODE_MODEL];
}
get isStopped() {
const inventory = this.inventory || {};
return inventory.spec?.powerActionRequested === 'shutdown' &&
inventory.status?.powerAction?.actionStatus === 'complete';
}
get isStopping() {
const inventory = this.inventory || {};
if (!Object.prototype.hasOwnProperty.call(inventory?.status?.powerAction || {}, 'actionStatus')) {
return inventory.spec?.powerActionRequested === 'shutdown';
} else {
return false;
}
}
get isStarted() {
const inventory = this.inventory || {};
return inventory.spec?.powerActionRequested === 'poweron' &&
inventory.status?.powerAction?.actionStatus === 'complete';
}
get isStarting() {
const inventory = this.inventory || {};
if (!Object.prototype.hasOwnProperty.call(inventory?.status?.powerAction || {}, 'actionStatus')) {
return inventory.spec?.powerActionRequested === 'poweron';
} else {
return false;
}
}
get isRebooting() {
const inventory = this.inventory || {};
if (!Object.prototype.hasOwnProperty.call(inventory?.status?.powerAction || {}, 'actionStatus')) {
return inventory.spec?.powerActionRequested === 'reboot';
} else {
return false;
}
}
async shutDown(resources = this) {
try {
await this.doAction('powerActionPossible', {});
await this.doAction('powerAction', { operation: 'shutdown' });
await this.$dispatch('growl/success', {
title: this.t('generic.notification.title.succeed'),
message: this.t('harvester.host.powerAction.message.success', {
name: this.name,
operation: 'shut down'
})
}, { root: true });
} catch (err) {
await this.$dispatch('growl/error', {
title: this.t('generic.notification.title.error'),
message: err,
}, { root: true });
}
}
async powerOn(resources = this) {
const operation = 'poweron';
try {
await this.doAction('powerActionPossible', {});
await this.doAction('powerAction', { operation });
await this.$dispatch('growl/success', {
title: this.t('generic.notification.title.succeed'),
message: this.t('harvester.host.powerAction.message.success', {
name: this.name,
operation,
})
}, { root: true });
} catch (err) {
await this.$dispatch('growl/error', {
title: this.t('generic.notification.title.error'),
message: err,
}, { root: true });
}
}
async reboot(resources = this) {
const operation = 'reboot';
try {
await this.doAction('powerActionPossible', {});
await this.doAction('powerAction', { operation });
await this.$dispatch('growl/success', {
title: this.t('generic.notification.title.succeed'),
message: this.t('harvester.host.powerAction.message.success', {
name: this.name,
operation,
})
}, { root: true });
} catch (err) {
await this.$dispatch('growl/error', {
title: this.t('generic.notification.title.error'),
message: err,
}, { root: true });
}
}
get inventory() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const inventories = this.$rootGetters[`${ inStore }/all`](HCI.INVENTORY) || [];
return inventories.find((inv) => inv.id === `harvester-system/${ this.id }`);
}
get warningMessages() {
let out = [];
out = out.concat(this.inventory?.warningMessages || []);
return out;
}
}