Add pkg/harvester components + shell portings - 1

Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
Francesco Torchia
2024-10-23 17:00:46 +02:00
parent f8408469f7
commit 4f2688f6ab
265 changed files with 42316 additions and 82 deletions
@@ -0,0 +1,33 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
export default class HciConfigMap extends HarvesterResource {
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.CLOUD_TEMPLATE;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.CLOUD_TEMPLATE;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.CLOUD_TEMPLATE }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
}
@@ -0,0 +1,91 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import NetworkAttachmentDef from '@pkg/harvester/models/k8s.cni.cncf.io.networkattachmentdefinition';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
const NOT_READY = 'Not Ready';
export default class HarvesterNetworkAttachmentDef extends NetworkAttachmentDef {
get listLocation() {
return this.$rootGetters['type-map/optionsFor'](this.type).customRoute || {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
},
};
}
get doneRoute() {
return this.listLocation.name;
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.NETWORK_ATTACHMENT;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource-namespace-id`;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.NETWORK_ATTACHMENT;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.NETWORK_ATTACHMENT }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get inStore() {
return this.$rootGetters['currentProduct'].inStore;
}
get clusterNetworkResource() {
const clusterNetworks = this.$rootGetters[`${ this.inStore }/all`](HCI.CLUSTER_NETWORK);
return clusterNetworks.find((c) => c.id === this.clusterNetwork);
}
get clusterNetworkErrorMessage() {
if (!this.clusterNetworkResource) {
return this.t('harvester.clusterNetwork.notExist', { clusterNetwork: this.clusterNetwork });
} else if (!this.clusterNetworkResource.isReady) {
return this.t('harvester.clusterNetwork.notReady', { clusterNetwork: this.clusterNetwork });
} else {
return '';
}
}
get stateDisplay() {
if (this.clusterNetworkErrorMessage) {
return NOT_READY;
}
return super.stateDisplay;
}
get stateBackground() {
if (this.stateDisplay === NOT_READY) {
return 'bg-warning';
}
return super.stateBackground;
}
get isNotReady() {
return this.clusterNetworkErrorMessage;
}
}
@@ -0,0 +1,55 @@
import { clone } from '@shell/utils/object';
import { LOGGING } from '@shell/config/types';
import { HCI } from '../../types';
import HarvesterFlow from './logging.banzaicloud.io.flow';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
export default class HciClusterflow extends HarvesterFlow {
get allOutputs() {
return this.$rootGetters['harvester/all'](LOGGING.CLUSTER_OUTPUT) || [];
}
get _detailLocation() {
const schema = this.$getters['schemaFor'](this.type);
const id = this.id?.replace(/.*\//, '');
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
id,
namespace: this.metadata.namespace,
},
};
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.CLUSTER_FLOW;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.CLUSTER_FLOW;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.CLUSTER_FLOW }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
}
@@ -0,0 +1,50 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import LogOutput from './logging.banzaicloud.io.output';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
export default class HciClusteroutput extends LogOutput {
get _detailLocation() {
const schema = this.$getters['schemaFor'](this.type);
const id = this.id?.replace(/.*\//, '');
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
id,
namespace: this.metadata.namespace,
},
};
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.CLUSTER_OUTPUT;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.CLUSTER_OUTPUT;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.CLUSTER_OUTPUT }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
}
@@ -0,0 +1,72 @@
import { LOGGING } from '@shell/config/types';
import { HCI } from '../../types';
import { clone } from '@shell/utils/object';
import Flow from '@shell/models/logging.banzaicloud.io.flow';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { FLOW_TYPE } from '../../config/harvester-map';
export default class HarvesterLogFlow extends Flow {
get allOutputs() {
return this.$rootGetters['harvester/all'](LOGGING.OUTPUT) || [];
}
get allClusterOutputs() {
return this.$rootGetters['harvester/all'](LOGGING.CLUSTER_OUTPUT) || [];
}
get _detailLocation() {
const schema = this.$getters['schemaFor'](this.type);
const id = this.id?.replace(/.*\//, '');
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
id,
namespace: this.metadata.namespace,
},
};
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.FLOW;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.FLOW;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.FLOW }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get loggingType() {
if (this.spec.loggingRef === 'harvester-kube-audit-log-ref') {
return FLOW_TYPE.AUDIT;
}
if ((this.spec.match || []).find(M => !!M?.select?.labels?.['app.kubernetes.io/name'])) {
return FLOW_TYPE.EVENT;
}
return FLOW_TYPE.LOGGING;
}
}
@@ -0,0 +1,63 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import LogOutput from '@shell/models/logging.banzaicloud.io.output';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { FLOW_TYPE } from '../../config/harvester-map';
export default class HarvesterLogOutput extends LogOutput {
get _detailLocation() {
const schema = this.$getters['schemaFor'](this.type);
const id = this.id?.replace(/.*\//, '');
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
id,
namespace: this.metadata.namespace,
},
};
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.OUTPUT;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.OUTPUT;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.OUTPUT }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get url() {
return this.detailLocation;
}
get loggingType() {
if (this.spec.loggingRef === 'harvester-kube-audit-log-ref') {
return FLOW_TYPE.AUDIT;
}
return FLOW_TYPE.LOGGING;
}
}
@@ -0,0 +1,45 @@
import { HCI } from '../../types';
import { clone } from '@shell/utils/object';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import ManagementSetting from '@shell/models/management.cattle.io.setting';
export default class HCIManagementSetting extends ManagementSetting {
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.SETTING;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.SETTING;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get doneRoute() {
return null;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.SETTING }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get doneParams() {
return {
product: this.$rootGetters['productId'],
cluster: this.$rootGetters['clusterId'],
resource: HCI.SETTING,
};
}
}
@@ -0,0 +1,142 @@
import { clone, set } from '@shell/utils/object';
import { _YAML, _CREATE, _VIEW, _CONFIG } from '@shell/config/query-params';
import { HCI } from '../../types';
import SteveModel from '@shell/plugins/steve/steve-class';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
export default class HciAlertmanagerConfig extends SteveModel {
get _detailLocation() {
return {
name: this.alertmanagerConfigDoneRouteName,
params: {
cluster: this.$rootGetters['clusterId'],
resource: HCI.ALERTMANAGERCONFIG,
namespace: this.metadata?.namespace,
id: this.name,
},
hash: '#receivers',
query: { as: 'config' }
};
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.ALERTMANAGERCONFIG;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.ALERTMANAGERCONFIG }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
applyDefaults() {
if (this.spec) {
return this.spec;
}
const existingReceivers = this.spec?.route?.receivers || [];
const defaultSpec = {
receivers: [...existingReceivers],
route: {
receivers: this.spec?.route?.receivers || [],
groupBy: this.spec?.route?.groupBy || [],
groupWait: this.spec?.route?.groupWait || '30s',
groupInterval: this.spec?.route?.groupInterval || '5m',
repeatInterval: this.spec?.route?.repeatInterval || '4h',
match: this.spec?.route?.match || {},
matchRe: this.spec?.route?.matchRe || {}
}
};
set(this, 'spec', defaultSpec);
}
get _availableActions() {
const out = super._availableActions;
return out;
}
getReceiverActions(alertmanagerConfigActions) {
return alertmanagerConfigActions.filter((actionData) => {
if (actionData.divider) {
return true;
}
switch (actionData.action) {
case 'goToEdit':
return true;
case 'goToEditYaml':
return true;
case 'promptRemove':
return true;
default:
return false;
}
});
}
get alertmanagerConfigDoneRouteName() {
return `${ HARVESTER_PRODUCT }-c-cluster-resource-namespace-id`;
}
getCreateReceiverRoute() {
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-alertmanagerconfig-alertmanagerconfigid-receiver`,
params: { cluster: this.$rootGetters['clusterId'], alertmanagerconfigid: this.id },
query: { mode: _CREATE, currentView: _CONFIG }
};
}
getReceiverDetailLink(receiverName) {
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-alertmanagerconfig-alertmanagerconfigid-receiver`,
params: {
cluster: this.$rootGetters['clusterId'],
alertmanagerconfigid: this.id,
receiverName
},
query: {
mode: _VIEW, receiverName, currentView: _CONFIG
}
};
}
getEditReceiverYamlRoute(receiverName, queryMode) {
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-alertmanagerconfig-alertmanagerconfigid-receiver`,
params: {
cluster: this.$rootGetters['clusterId'],
alertmanagerconfigid: this.id
},
query: {
mode: queryMode || _VIEW,
receiverName,
currentView: _YAML
}
};
}
getEditReceiverConfigRoute(receiverName, queryMode) {
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-alertmanagerconfig-alertmanagerconfigid-receiver`,
params: {
cluster: this.$rootGetters['clusterId'],
alertmanagerconfigid: this.id
},
query: {
mode: queryMode || _VIEW,
receiverName,
currentView: _CONFIG
}
};
}
}
+110
View File
@@ -0,0 +1,110 @@
import { insertAt } from '@shell/utils/array';
import namespace from '@shell/models/namespace';
import { SYSTEM_NAMESPACE, FLEET } from '@shell/config/labels-annotations';
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';
const OBSCURE_NAMESPACE_PREFIX = [
'c-', // cluster namespace
// Project namespace. When a user creates a project, Rancher creates
// namespaces in the local cluster with the 'p-' prefix which are
// used to manage RBAC for the project. If these namespaces are deleted,
// role bindings can be lost and Rancher may need to be restored from
// backup. Therefore we hide these namespaces unless the developer setting
// is turned on from the user preferences.
'p-',
'user-', // user namespace
'local', // local namespace
];
export default class HciNamespace extends namespace {
get _availableActions() {
const out = super._availableActions;
const remove = out.findIndex(a => a.action === 'promptRemove');
const promptRemove = {
action: 'promptRemove',
altAction: 'remove',
label: this.t('action.remove'),
icon: 'icon icon-trash',
bulkable: true,
enabled: this.canDelete,
bulkAction: 'promptRemove',
weight: -10,
};
if (remove > -1) {
out.splice(remove, 1);
}
insertAt(out, out.length - 1, promptRemove);
return out;
}
promptRemove(resources = this) {
this.$dispatch('promptModal', {
resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
component: 'ConfirmRelatedToRemoveDialog'
});
}
get isSystem() {
const systemNamespaces = ['fleet-default'];
if (systemNamespaces.includes(this.metadata.name)) {
return true;
}
if ( this.metadata?.annotations?.[SYSTEM_NAMESPACE] === 'true' ) {
return true;
}
if ( SYSTEM_NAMESPACES.includes(this.metadata.name) ) {
return true;
}
if ( this.metadata.name.endsWith('-system') ) {
return true;
}
if ( this.project ) {
return this.project.isSystem;
}
if (get(this, `metadata.labels."${ FLEET.MANAGED }"`) === 'true') {
return true;
}
const isSettingSystemNamespace = this.$rootGetters['systemNamespaces'].includes(this.metadata.name);
const isObscurePrefix = !!OBSCURE_NAMESPACE_PREFIX.some(prefix => this.metadata.name.startsWith(prefix));
return isSettingSystemNamespace || isObscurePrefix;
}
get detailLocation() {
const id = this.id?.replace(/.*\//, '');
const detailLocation = {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource-id`,
params: {
product: this.$rootGetters['productId'],
cluster: this.$rootGetters['clusterId'],
resource: NAMESPACE,
namespace: this.metadata?.namespace,
id,
}
};
return detailLocation;
}
get hideDetailLocation() {
return !!this.$rootGetters['currentProduct'].hideNamespaceLocation;
}
}
+607
View File
@@ -0,0 +1,607 @@
import pickBy from 'lodash/pickBy';
import { CAPI, LONGHORN, POD, NODE } from '@shell/config/types';
import { HCI } from '../../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 HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { findBy, isArray } from '@shell/utils/array';
import { ucFirst } from '@shell/utils/string';
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 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,
shutDown,
powerOn,
reboot,
...super._availableActions
];
}
promptRemove(resources = this) {
this.$dispatch('promptModal', {
resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
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];
if (!url) {
return false;
}
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return `http://${ url }`;
}
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() {
const allNotExist = !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] && !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU];
return this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] === 'false' || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU] === 'false' || allNotExist;
}
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', {});
}
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 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;
}
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?.spec?.fileSystem?.provisioned && 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;
}
}
@@ -0,0 +1,307 @@
import Vue from 'vue';
import { _CLONE } from '@shell/config/query-params';
import pick from 'lodash/pick';
import { HCI, VOLUME_SNAPSHOT } from '../../types';
import { PV, LONGHORN } from '@shell/config/types';
import { DESCRIPTION } from '@shell/config/labels-annotations';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { findBy } from '@shell/utils/array';
import { get, clone } from '@shell/utils/object';
import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
const DEGRADED_ERROR = 'replica scheduling failed';
export default class HciPv extends HarvesterResource {
applyDefaults(_, realMode) {
const accessModes = realMode === _CLONE ? this.spec.accessModes : [];
const storage =
realMode === _CLONE ? this.spec.resources.requests.storage : null;
const storageClassName =
realMode === _CLONE ? this.spec.storageClassName : '';
Vue.set(this, 'spec', {
accessModes,
storageClassName,
volumeName: '',
resources: { requests: { storage } }
});
}
get availableActions() {
const out = super._availableActions;
const clone = out.find(action => action.action === 'goToClone');
if (clone) {
clone.action = 'goToCloneVolume';
}
return [
{
action: 'exportImage',
enabled: this.hasAction('export'),
icon: 'icon icon-copy',
label: this.t('harvester.action.exportImage')
},
{
action: 'cancelExpand',
enabled: this.hasAction('cancelExpand'),
icon: 'icon icon-backup',
label: this.t('harvester.action.cancelExpand')
},
{
action: 'snapshot',
enabled: this.hasAction('snapshot'),
icon: 'icon icon-backup',
label: this.t('harvester.action.snapshot'),
},
...out
];
}
exportImage(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'HarvesterExportImageDialog'
});
}
cancelExpand(resources = this) {
this.doActionGrowl('cancelExpand', {});
}
snapshot(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'SnapshotDialog'
});
}
goToCloneVolume(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'VolumeCloneDialog'
});
}
cleanForNew() {
this.$dispatch(`cleanForNew`, this);
delete this.metadata.finalizers;
const keys = [HCI_ANNOTATIONS.IMAGE_ID, DESCRIPTION];
this.metadata.annotations = pick(this.metadata.annotations, keys);
}
get canUpdate() {
return this.hasLink('update');
}
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';
const conditions = this?.status?.conditions || [];
if (findBy(conditions, 'type', 'Resizing')?.status === 'True') {
return 'Resizing';
} else if (ownedBy && !volumeError) {
return 'In-use';
} else if (degradedVolume) {
return 'Degraded';
} else {
return status;
}
}
// state is similar with stateDisplay, the reason we keep this property is the status of In-use should not be displayed on vm detail page
get state() {
const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR];
const degradedVolume = volumeError === DEGRADED_ERROR;
let status = this?.status?.phase === 'Bound' && !volumeError ? 'Ready' : 'Not Ready';
const conditions = this?.status?.conditions || [];
if (degradedVolume) {
status = 'Degraded';
}
if (findBy(conditions, 'type', 'Resizing')?.status === 'True') {
status = 'Resizing';
}
return status;
}
get stateColor() {
const state = this.stateDisplay;
return colorForState(state);
}
get stateDescription() {
return (
super.stateDescription
);
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.VOLUME;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.VOLUME;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.VOLUME }"`, { count: 1 }).trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get phaseState() {
return this.status?.phase || 'N/A';
}
get attachVM() {
const allVMs = this.$rootGetters['harvester/all'](HCI.VM);
const ownedBy =
get(this, `metadata.annotations."${ HCI_ANNOTATIONS.OWNED_BY }"`) || '';
if (!ownedBy) {
return null;
}
const ownedId = JSON.parse(ownedBy)[0]?.refs?.[0];
return allVMs.find(D => D.id === ownedId);
}
get isAvailable() {
const unAvailable = ['Resizing', 'Not Ready'];
return !unAvailable.includes(this.stateDisplay);
}
get volumeSort() {
const volume = this.spec?.resources?.requests?.storage || 0;
return parseInt(volume);
}
get isSystemResource() {
const systemNamespaces = this.$rootGetters['systemNamespaces'];
if (systemNamespaces.includes(this.metadata?.namespace)) {
return true;
}
return false;
}
get longhornVolume() {
const inStore = this.$rootGetters['currentProduct'].inStore;
return this.$rootGetters[`${ inStore }/all`](LONGHORN.VOLUMES).find(v => v.metadata?.name === this.spec?.volumeName);
}
get longhornEngine() {
const inStore = this.$rootGetters['currentProduct'].inStore;
return this.$rootGetters[`${ inStore }/all`](LONGHORN.ENGINES).find(v => v.spec?.volumeName === this.spec?.volumeName);
}
// https://github.com/longhorn/longhorn-manager/blob/master/api/model.go#L1151
get isLonghornVolumeReady() {
let ready = true;
const longhornVolume = this.longhornVolume || {};
const scheduledCondition = (longhornVolume?.status?.conditions || []).find(c => c.type === 'Scheduled' || c.type === 'scheduled') || {};
if ((longhornVolume?.spec?.nodeID === '' && longhornVolume?.status?.state !== 'detached') ||
(longhornVolume?.status?.state === 'detached' && scheduledCondition.status !== 'True') ||
longhornVolume?.status?.robustness === 'faulted' ||
longhornVolume?.status?.restoreRequired ||
longhornVolume?.status?.cloneStatus?.state === 'failed'
) {
ready = false;
}
return ready;
}
get relatedVolumeSnapshotCounts() {
const snapshots = this.$rootGetters['harvester/all'](VOLUME_SNAPSHOT);
return snapshots.filter((snapshot) => {
const volumeId = `${ snapshot.metadata?.namespace }/${ snapshot.spec?.source?.persistentVolumeClaimName }`;
const kind = snapshot.metadata?.ownerReferences?.[0]?.kind;
return volumeId === this.id && kind === 'PersistentVolumeClaim';
});
}
get originalSnapshot() {
if (this.spec?.dataSource) {
return this.$rootGetters['harvester/all'](VOLUME_SNAPSHOT).find(V => V.metadata?.name === this.spec.dataSource.name);
} else {
return null;
}
}
get source() {
const imageId = get(this, `metadata.annotations."${ HCI_ANNOTATIONS.IMAGE_ID }"`);
return imageId ? 'image' : 'data';
}
get warnDeletionMessage() {
return this.t('harvester.volume.promptRemove.tips');
}
get relatedPV() {
return this.$rootGetters['harvester/all'](PV).find(pv => pv.metadata?.name === this.spec?.volumeName);
}
get resourceExternalLink() {
const host = window.location.host;
const { params } = this.currentRoute();
const volumeName = this.spec?.volumeName;
if (!volumeName) {
return null;
}
return {
tipsKey: 'harvester.volume.externalLink.tips',
url: `https://${ host }/k8s/clusters/${ params.cluster }/api/v1/namespaces/longhorn-system/services/http:longhorn-frontend:80/proxy/#/volume/${ volumeName }`
};
}
get customValidationRules() {
return [
{
nullable: false,
path: 'spec.resources.requests.storage',
required: true,
validators: ['volumeSize']
},
];
}
}
+185
View File
@@ -0,0 +1,185 @@
import includes from 'lodash/includes';
import { findBy } from '@shell/utils/array';
import { get } from '@shell/utils/object';
import { NODE } from '@shell/config/types';
import HarvesterResource from '../harvester';
const POD_STATUS_NOT_SCHEDULABLE = 'POD_NOT_SCHEDULABLE';
const POD_STATUS_FAILED = 'POD_FAILED';
const POD_STATUS_CRASHLOOP_BACKOFF = 'POD_CRASHLOOP_BACKOFF';
const POD_STATUS_UNKNOWN = 'POD_STATUS_UNKNOWN';
const POD_STATUS_CONTAINER_FAILING = 'POD_CONTAINER_FAILING';
const POD_STATUS_NOT_READY = 'POD_NOT_READY';
const POD_STATUS_PENDING = 'POD_STATUS_PENDING';
const POD_STATUS_COMPLETED = 'POD_STATUS_COMPLETED';
const POD_STATUS_SUCCEEDED = 'POD_STATUS_SUCCEEDED';
const POD_STATUS_RUNNING = 'POD_STATUS_RUNNING';
const failedWaitingContainerReasons = ['ImagePullBackOff', 'ErrImagePull', 'CrashLoopBackOff'];
const failedTerminationContainerReasons = ['Error'];
const errorStatusMapper = {
Failed: POD_STATUS_FAILED,
CrashLoopBackOff: POD_STATUS_CRASHLOOP_BACKOFF,
Unknown: POD_STATUS_UNKNOWN,
};
const okStatusMapper = {
Pending: POD_STATUS_PENDING,
Running: POD_STATUS_RUNNING,
Completed: POD_STATUS_COMPLETED,
Succeeded: POD_STATUS_SUCCEEDED,
};
const stateReasonResolver = {
terminated: ({ reason, exitCode }) => `Terminated with ${ reason }${ exitCode ? ` (exit code ${ exitCode }).` : '.' }`,
waiting: ({ reason }) => `Waiting (${ reason }).`,
};
export default class HciPod extends HarvesterResource {
get inStore() {
return this.$rootGetters['currentProduct'].inStore;
}
get nodes() {
return this.$rootGetters[`${ this.inStore }/all`](NODE);
}
get node() {
const { nodeName } = this.spec;
return this.nodes.filter((node) => {
return node?.metadata?.name === nodeName;
})[0];
}
get getPodStatus() {
return this.isNotSchedulable ||
this.hasErrorStatus ||
this.isContainerFailing ||
this.isNotReady ||
this.hasOkStatus || { status: POD_STATUS_UNKNOWN };
}
get isNotSchedulable() {
if (!this.isPodSchedulable) {
const conditions = get(this, 'status.conditions');
const podScheduledCond = findBy(conditions, 'type', 'PodScheduled');
return {
status: POD_STATUS_NOT_SCHEDULABLE,
message: podScheduledCond.message || 'Pod scheduling failed.',
};
}
return null;
}
get hasErrorStatus() {
const status = errorStatusMapper[this?.status?.phase];
if (status) {
return {
status,
message: this.getContainerStatusReason(this.findFailingContainerStatus),
};
}
return null;
}
get isPodSchedulable() {
const conditions = get(this, 'status.conditions');
const podScheduledCond = findBy(conditions, 'type', 'PodScheduled');
return !(
podScheduledCond &&
podScheduledCond.status !== 'True' &&
podScheduledCond.reason === 'Unschedulable'
);
}
get findFailingContainerStatus() {
return (get(this, 'status.containerStatuses'), []).find((container) => {
return !container.ready &&
(includes(failedWaitingContainerReasons, get(container, 'state.waiting.reason')) ||
includes(failedTerminationContainerReasons, get(container, 'state.terminated.reason')));
});
}
get getContainerStatusReason() {
return (containerStatus) => {
if (containerStatus) {
const stateName = Object.getOwnPropertyNames(containerStatus.state).find(
pn => !!containerStatus.state[pn].reason,
);
if (stateName) {
const state = containerStatus.state[stateName];
return (
state.message ||
(stateReasonResolver[stateName] && stateReasonResolver[stateName](state)) ||
stateName
);
}
}
return undefined;
};
}
get isContainerFailing() {
const failingContainer = this.findFailingContainerStatus;
if (failingContainer) {
return {
status: POD_STATUS_CONTAINER_FAILING,
message: this.getContainerStatusReason(failingContainer),
};
}
return null;
}
get isNotReady() {
const message = this.findPodFalseStatusConditionMessage;
if (message) {
return {
status: POD_STATUS_NOT_READY,
message,
};
}
return null;
}
get hasOkStatus() {
const status = okStatusMapper[this?.status?.phase];
if (status) {
return { status };
}
return null;
}
get findPodFalseStatusConditionMessage() {
const notReadyConditions = this.getPodFalseStatusConditions;
if (notReadyConditions.length > 0) {
return notReadyConditions[0].message || `Step: ${ notReadyConditions[0].type }`;
}
return undefined;
}
get getPodFalseStatusConditions() {
const conditions = get(this, 'status.conditions') || [];
return conditions.filter(condition => condition.status !== 'True');
}
}
+88
View File
@@ -0,0 +1,88 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import Secret from '@shell/models/secret';
export default class HciSecret extends Secret {
get _detailLocation() {
const schema = this.$getters['schemaFor'](this.type);
const id = this.id?.replace(/.*\//, '');
return {
name: `${ HARVESTER_PRODUCT }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
params: {
product: HARVESTER_PRODUCT,
cluster: this.$rootGetters['clusterId'],
resource: this.type,
id,
namespace: this.metadata.namespace,
},
};
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.SECRET;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.SECRET;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get doneRoute() {
return this.doneOverride.name;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.SECRET }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get details() {
const out = [
{
label: this.t('secret.type'),
content: this.typeDisplay
}
];
if (this.cn) {
out.push({
label: this.t('secret.certificate.cn'),
content: this.plusMoreNames ? `${ this.cn } ${ this.t('secret.certificate.plusMore', { n: this.plusMoreNames }) }` : this.cn
});
}
if (this.issuer) {
out.push({
label: this.t('secret.certificate.issuer'),
content: this.issuer
});
}
if (this.notAfter) {
out.push({
label: 'Expires',
formatter: 'Date',
formatterOpts: { class: this.dateClass },
content: this.notAfter
});
}
return out;
}
}
@@ -0,0 +1,70 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { PVC } from '@shell/config/types';
export default class HciSnapshot extends HarvesterResource {
get availableActions() {
return [
{
action: 'restoreSnapshot',
enabled: this.hasAction('restore'),
icon: 'icon icon-refresh',
label: this.t('harvester.action.restore'),
},
...super._availableActions
];
}
get canClone() {
return false;
}
restoreSnapshot(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'RestoreSnapshotDialog'
});
}
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.SNAPSHOT;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.SNAPSHOT;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.SNAPSHOT }"`, { count: 1 })?.trim();
}
get parentLocationOverride() {
return this.doneOverride;
}
get volume() {
const inStore = this.$rootGetters['currentProduct'].inStore;
const namespace = this?.metadata?.namespace;
const pvc = this?.spec?.source?.persistentVolumeClaimName;
const volume = this.$rootGetters[`${ inStore }/byId`](
PVC,
`${ namespace }/${ pvc }`
);
return volume;
}
}
@@ -0,0 +1,34 @@
import { clone } from '@shell/utils/object';
import { HCI } from '../../types';
import StorageClass from '@shell/models/storage.k8s.io.storageclass';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
export default class HciStorageClass extends StorageClass {
get detailLocation() {
const detailLocation = clone(this._detailLocation);
detailLocation.params.resource = HCI.STORAGE;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource-id`;
return detailLocation;
}
get doneOverride() {
const detailLocation = clone(this._detailLocation);
delete detailLocation.params.namespace;
delete detailLocation.params.id;
detailLocation.params.resource = HCI.STORAGE;
detailLocation.name = `${ HARVESTER_PRODUCT }-c-cluster-resource`;
return detailLocation;
}
get parentLocationOverride() {
return this.doneOverride;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.STORAGE }"`, { count: 1 })?.trim();
}
}