mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-04 05:06:05 +08:00
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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']
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user