mirror of https://github.com/rancher/dashboard.git
379 lines
10 KiB
JavaScript
379 lines
10 KiB
JavaScript
import Vue from 'vue';
|
|
import { CATALOG, CLUSTER_BADGE } from '@/config/labels-annotations';
|
|
import { NODE, FLEET, MANAGEMENT } from '@/config/types';
|
|
import { insertAt } from '@/utils/array';
|
|
import { downloadFile } from '@/utils/download';
|
|
import { parseSi } from '@/utils/units';
|
|
import { parseColor, textColor } from '@/utils/color';
|
|
import jsyaml from 'js-yaml';
|
|
import { eachLimit } from '@/utils/promise';
|
|
import { addParams } from '@/utils/url';
|
|
import { isEmpty } from '@/utils/object';
|
|
import { NAME as HARVESTER } from '@/config/product/harvester';
|
|
import { isHarvesterCluster } from '@/utils/cluster';
|
|
import HybridModel from '@/plugins/steve/hybrid-class';
|
|
import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
|
|
// See translation file cluster.providers for list of providers
|
|
// If the logo is not named with the provider name, add an override here
|
|
const PROVIDER_LOGO_OVERRIDE = {};
|
|
|
|
export default class MgmtCluster extends HybridModel {
|
|
get details() {
|
|
const out = [
|
|
{
|
|
label: 'Provisioner',
|
|
content: this.provisionerDisplay
|
|
},
|
|
{
|
|
label: 'Machine Provider',
|
|
content: this.machineProviderDisplay
|
|
},
|
|
{
|
|
label: 'Kubernetes Version',
|
|
content: this.kubernetesVersion,
|
|
},
|
|
];
|
|
|
|
return out;
|
|
}
|
|
|
|
get _availableActions() {
|
|
const out = super._availableActions;
|
|
|
|
insertAt(out, 0, {
|
|
action: 'openShell',
|
|
label: this.t('nav.shell'),
|
|
icon: 'icon icon-terminal',
|
|
enabled: !!this.links.shell,
|
|
});
|
|
|
|
insertAt(out, 1, {
|
|
action: 'downloadKubeConfig',
|
|
bulkAction: 'downloadKubeConfigBulk',
|
|
label: this.t('nav.kubeconfig.download'),
|
|
icon: 'icon icon-download',
|
|
bulkable: true,
|
|
enabled: this.$rootGetters['isRancher'] && this.hasAction('generateKubeconfig'),
|
|
});
|
|
|
|
insertAt(out, 2, {
|
|
action: 'copyKubeConfig',
|
|
label: this.t('cluster.copyConfig'),
|
|
bulkable: false,
|
|
enabled: true,
|
|
icon: 'icon icon-copy',
|
|
});
|
|
|
|
return out;
|
|
}
|
|
|
|
get canDelete() {
|
|
return this.hasLink('remove') && !this?.spec?.internal;
|
|
}
|
|
|
|
get machinePools() {
|
|
const pools = this.$getters['all'](MANAGEMENT.NODE_POOL);
|
|
|
|
return pools.filter(x => x.spec?.clusterName === this.id);
|
|
}
|
|
|
|
get provisioner() {
|
|
return this.status?.driver ? this.status.driver : 'imported';
|
|
}
|
|
|
|
get machineProvider() {
|
|
const kind = this.machinePools?.[0]?.provider;
|
|
|
|
if ( kind ) {
|
|
return kind.replace(/config$/i, '').toLowerCase();
|
|
} else if ( this.spec?.internal ) {
|
|
return 'local';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get emberEditPath() {
|
|
// Ember wants one word called provider to tell what component to show, but has much indirect mapping to figure out what it is.
|
|
let provider;
|
|
|
|
// Provisioner is the "<something>Config" in the model
|
|
const provisioner = KONTAINER_TO_DRIVER[(this.provisioner || '').toLowerCase()] || this.provisioner;
|
|
|
|
if ( provisioner === 'rancherKubernetesEngine' ) {
|
|
if ( this.machinePools?.[0] ) {
|
|
provider = this.machinePools[0]?.nodeTemplate?.spec?.driver || null;
|
|
} else {
|
|
provider = 'custom';
|
|
}
|
|
} else if ( this.driver ) {
|
|
provider = this.driver;
|
|
} else if ( provisioner && provisioner.endsWith('v2') ) {
|
|
provider = provisioner;
|
|
} else {
|
|
provider = 'import';
|
|
}
|
|
|
|
const qp = { provider };
|
|
|
|
// Copied out of https://github.com/rancher/ui/blob/20f56dc54c4fc09b5f911e533cb751c13609adaf/app/models/cluster.js#L844
|
|
if ( provider === 'import' && isEmpty(this.eksConfig) && isEmpty(this.gkeConfig) ) {
|
|
qp.importProvider = 'other';
|
|
} else if (
|
|
(provider === 'amazoneks' && !isEmpty(this.eksConfig) ) ||
|
|
(provider === 'gke' && !isEmpty(this.gkeConfig) )
|
|
// || something for aks v2
|
|
) {
|
|
qp.importProvider = KONTAINER_TO_DRIVER[provider];
|
|
}
|
|
|
|
if ( this.clusterTemplateRevisionId ) {
|
|
qp.clusterTemplateRevision = this.clusterTemplateRevisionId;
|
|
}
|
|
|
|
return addParams(`/c/${ escape(this.id) }/edit`, qp);
|
|
}
|
|
|
|
get groupByLabel() {
|
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
|
|
}
|
|
|
|
get isReady() {
|
|
// If the Connected condition exists, use that (2.6+)
|
|
if ( this.hasCondition('Connected') ) {
|
|
return this.isCondition('Connected');
|
|
}
|
|
|
|
// Otherwise use Ready (older)
|
|
return this.isCondition('Ready');
|
|
}
|
|
|
|
get kubernetesVersionRaw() {
|
|
const fromStatus = this.status?.version?.gitVersion;
|
|
const fromSpec = this.spec?.[`${ this.provisioner }Config`]?.kubernetesVersion;
|
|
|
|
return fromStatus || fromSpec;
|
|
}
|
|
|
|
get kubernetesVersion() {
|
|
return this.kubernetesVersionRaw || this.$rootGetters['i18n/t']('generic.provisioning');
|
|
}
|
|
|
|
get kubernetesVersionBase() {
|
|
return this.kubernetesVersion.replace(/[+-].*$/, '');
|
|
}
|
|
|
|
get kubernetesVersionExtension() {
|
|
if ( this.kubernetesVersion.match(/[+-]]/) ) {
|
|
return this.kubernetesVersion.replace(/^.*([+-])/, '$1');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
get providerOs() {
|
|
if ( this.status?.provider.endsWith('.windows') ) {
|
|
return 'windows';
|
|
}
|
|
|
|
return 'linux';
|
|
}
|
|
|
|
get providerOsLogo() {
|
|
return require(`~/assets/images/vendor/${ this.providerOs }.svg`);
|
|
}
|
|
|
|
get isLocal() {
|
|
return this.spec?.internal === true;
|
|
}
|
|
|
|
get isHarvester() {
|
|
return isHarvesterCluster(this);
|
|
}
|
|
|
|
get providerLogo() {
|
|
const provider = this.status?.provider || 'kubernetes';
|
|
// Only interested in the part before the period
|
|
const prv = provider.split('.')[0];
|
|
// Allow overrides if needed
|
|
const logo = PROVIDER_LOGO_OVERRIDE[prv] || prv;
|
|
|
|
let icon;
|
|
|
|
try {
|
|
icon = require(`~/assets/images/providers/${ prv }.svg`);
|
|
} catch (e) {
|
|
console.warn(`Can not find provider logo for provider ${ logo }`); // eslint-disable-line no-console
|
|
// Use fallback generic Kubernetes icon
|
|
icon = require(`~/assets/images/providers/kubernetes.svg`);
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
|
|
get providerMenuLogo() {
|
|
if (this?.status?.provider === HARVESTER) {
|
|
return require(`~/assets/images/providers/kubernetes.svg`);
|
|
}
|
|
|
|
return this.providerLogo;
|
|
}
|
|
|
|
get providerNavLogo() {
|
|
if (this?.status?.provider === HARVESTER && this.$rootGetters['currentProduct'].inStore !== HARVESTER) {
|
|
return require(`~/assets/images/providers/kubernetes.svg`);
|
|
}
|
|
|
|
return this.providerLogo;
|
|
}
|
|
|
|
// Custom badge to show for the Cluster (if the appropriate annotations are set)
|
|
get badge() {
|
|
const text = this.metadata?.annotations[CLUSTER_BADGE.TEXT];
|
|
|
|
if (!text) {
|
|
return undefined;
|
|
}
|
|
|
|
const color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || '#7f7f7f';
|
|
const iconText = this.metadata?.annotations[CLUSTER_BADGE.ICON_TEXT] || '';
|
|
|
|
return {
|
|
text,
|
|
color,
|
|
textColor: textColor(parseColor(color)),
|
|
iconText: iconText.substr(0, 2),
|
|
};
|
|
}
|
|
|
|
get scope() {
|
|
return this.isLocal ? CATALOG._MANAGEMENT : CATALOG._DOWNSTREAM;
|
|
}
|
|
|
|
setClusterNameLabel(andSave) {
|
|
if ( this.ownerReferences?.length || this.metadata?.labels?.[FLEET.CLUSTER_NAME] === this.id ) {
|
|
return;
|
|
}
|
|
|
|
this.metadata = this.metadata || {};
|
|
this.metadata.labels = this.metadata.labels || {};
|
|
this.metadata.labels[FLEET.CLUSTER_NAME] = this.id;
|
|
|
|
if ( andSave ) {
|
|
return this.save();
|
|
}
|
|
}
|
|
|
|
get availableCpu() {
|
|
const reserved = parseSi(this.status.requested?.cpu);
|
|
const allocatable = parseSi(this.status.allocatable?.cpu);
|
|
|
|
if ( allocatable > 0 && reserved >= 0 ) {
|
|
return Math.max(0, allocatable - reserved);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
get availableMemory() {
|
|
const reserved = parseSi(this.status.requested?.memory);
|
|
const allocatable = parseSi(this.status.allocatable?.memory);
|
|
|
|
if ( allocatable > 0 && reserved >= 0 ) {
|
|
return Math.max(0, allocatable - reserved);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
openShell() {
|
|
this.$dispatch('wm/open', {
|
|
id: `kubectl-${ this.id }`,
|
|
label: this.$rootGetters['i18n/t']('wm.kubectlShell.title', { name: this.nameDisplay }),
|
|
icon: 'terminal',
|
|
component: 'KubectlShell',
|
|
attrs: {
|
|
cluster: this,
|
|
pod: {}
|
|
}
|
|
}, { root: true });
|
|
}
|
|
|
|
async generateKubeConfig() {
|
|
const res = await this.doAction('generateKubeconfig');
|
|
|
|
return res.config;
|
|
}
|
|
|
|
async downloadKubeConfig() {
|
|
const config = await this.generateKubeConfig();
|
|
|
|
downloadFile(`${ this.nameDisplay }.yaml`, config, 'application/yaml');
|
|
}
|
|
|
|
async downloadKubeConfigBulk(items) {
|
|
let obj = {};
|
|
let first = true;
|
|
|
|
await eachLimit(items, 10, (item, idx) => {
|
|
return item.generateKubeConfig().then((config) => {
|
|
const entry = jsyaml.load(config);
|
|
|
|
if ( first ) {
|
|
obj = entry;
|
|
first = false;
|
|
} else {
|
|
obj.clusters.push(...entry.clusters);
|
|
obj.users.push(...entry.users);
|
|
obj.contexts.push(...entry.contexts);
|
|
}
|
|
});
|
|
});
|
|
|
|
delete obj['current-context'];
|
|
|
|
const out = jsyaml.dump(obj);
|
|
|
|
downloadFile('kubeconfig.yaml', out, 'application/yaml');
|
|
}
|
|
|
|
async copyKubeConfig() {
|
|
const config = await this.generateKubeConfig();
|
|
|
|
Vue.prototype.$copyText(config);
|
|
}
|
|
|
|
async fetchNodeMetrics() {
|
|
const nodes = await this.$dispatch('cluster/findAll', { type: NODE }, { root: true });
|
|
const nodeMetrics = await this.$dispatch('cluster/findAll', { type: NODE }, { root: true });
|
|
|
|
const someNonWorkerRoles = nodes.some(node => node.hasARole && !node.isWorker);
|
|
|
|
const metrics = nodeMetrics.filter((metric) => {
|
|
const node = nodes.find(nd => nd.id === metric.id);
|
|
|
|
return node && (!someNonWorkerRoles || node.isWorker);
|
|
});
|
|
const initialAggregation = {
|
|
cpu: 0,
|
|
memory: 0
|
|
};
|
|
|
|
if (isEmpty(metrics)) {
|
|
return null;
|
|
}
|
|
|
|
return metrics.reduce((agg, metric) => {
|
|
agg.cpu += parseSi(metric?.usage?.cpu);
|
|
agg.memory += parseSi(metric?.usage?.memory);
|
|
|
|
return agg;
|
|
}, initialAggregation);
|
|
}
|
|
|
|
get nodes() {
|
|
return this.$getters['all'](MANAGEMENT.NODE).filter(node => node.id.startsWith(this.id));
|
|
}
|
|
}
|