mirror of https://github.com/rancher/dashboard.git
327 lines
9.1 KiB
Vue
327 lines
9.1 KiB
Vue
<script>
|
|
import CreateEditView from '@/mixins/create-edit-view';
|
|
import { STATE, NAME, NODE, POD_IMAGES } from '@/config/table-headers';
|
|
import { POD, WORKLOAD_TYPES } from '@/config/types';
|
|
import SortableTable from '@/components/SortableTable';
|
|
import Tab from '@/components/Tabbed/Tab';
|
|
import Loading from '@/components/Loading';
|
|
import ResourceTabs from '@/components/form/ResourceTabs';
|
|
import CountGauge from '@/components/CountGauge';
|
|
import { allHash } from '@/utils/promise';
|
|
import { get } from '@/utils/object';
|
|
import DashboardMetrics from '@/components/DashboardMetrics';
|
|
import V1WorkloadMetrics from '@/mixins/v1-workload-metrics';
|
|
import { mapGetters } from 'vuex';
|
|
import { allDashboardsExist } from '@/utils/grafana';
|
|
|
|
const WORKLOAD_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/rancher-workload-pods-1/rancher-workload-pods?orgId=1';
|
|
const WORKLOAD_METRICS_SUMMARY_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/rancher-workload-1/rancher-workload?orgId=1';
|
|
|
|
export const WORKLOAD_TYPE_TO_KIND_MAPPING = {
|
|
[WORKLOAD_TYPES.DEPLOYMENT]: 'Deployment',
|
|
[WORKLOAD_TYPES.CRON_JOB]: 'CronJob',
|
|
[WORKLOAD_TYPES.DAEMON_SET]: 'DaemonSet',
|
|
[WORKLOAD_TYPES.JOB]: 'Job',
|
|
[WORKLOAD_TYPES.STATEFUL_SET]: 'StatefulSet',
|
|
[WORKLOAD_TYPES.REPLICA_SET]: 'ReplicaSet',
|
|
[WORKLOAD_TYPES.REPLICATION_CONTROLLER]: 'ReplicationController',
|
|
};
|
|
|
|
const METRICS_SUPPORTED_KINDS = [
|
|
WORKLOAD_TYPES.DAEMON_SET,
|
|
WORKLOAD_TYPES.REPLICA_SET,
|
|
WORKLOAD_TYPES.STATEFUL_SET
|
|
];
|
|
|
|
export default {
|
|
components: {
|
|
DashboardMetrics,
|
|
Tab,
|
|
Loading,
|
|
ResourceTabs,
|
|
CountGauge,
|
|
SortableTable
|
|
},
|
|
|
|
mixins: [CreateEditView, V1WorkloadMetrics],
|
|
|
|
async fetch() {
|
|
const hash = { allPods: this.$store.dispatch('cluster/findAll', { type: POD }) };
|
|
|
|
if (this.value.type === WORKLOAD_TYPES.CRON_JOB) {
|
|
hash.allJobs = this.$store.dispatch('cluster/findAll', { type: WORKLOAD_TYPES.JOB });
|
|
}
|
|
const res = await allHash(hash);
|
|
|
|
for ( const k in res ) {
|
|
this[k] = res[k];
|
|
}
|
|
|
|
const isMetricsSupportedKind = METRICS_SUPPORTED_KINDS.includes(this.value.type);
|
|
|
|
this.showMetrics = isMetricsSupportedKind && await allDashboardsExist(this.$store.dispatch, this.currentCluster.id, [WORKLOAD_METRICS_DETAIL_URL, WORKLOAD_METRICS_SUMMARY_URL]);
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
allPods: null, allJobs: [], WORKLOAD_METRICS_DETAIL_URL, WORKLOAD_METRICS_SUMMARY_URL, showMetrics: false
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
...mapGetters(['currentCluster']),
|
|
pods() {
|
|
const relationships = get(this.value, 'metadata.relationships') || [];
|
|
const podRelationship = relationships.filter(relationship => relationship.toType === POD)[0];
|
|
|
|
if (podRelationship) {
|
|
return this.$store.getters['cluster/matching'](POD, podRelationship.selector).filter(pod => pod?.metadata?.namespace === this.value.metadata.namespace);
|
|
} else {
|
|
return [];
|
|
}
|
|
},
|
|
isJob() {
|
|
return this.value.type === WORKLOAD_TYPES.JOB;
|
|
},
|
|
|
|
isCronJob() {
|
|
return this.value.type === WORKLOAD_TYPES.CRON_JOB;
|
|
},
|
|
|
|
podSchema() {
|
|
return this.$store.getters['cluster/schemaFor'](POD);
|
|
},
|
|
|
|
podTemplateSpec() {
|
|
const isCronJob = this.value.type === WORKLOAD_TYPES.CRON_JOB;
|
|
|
|
if ( isCronJob ) {
|
|
return this.value.spec.jobTemplate.spec.template.spec;
|
|
} else {
|
|
return this.value.spec?.template?.spec;
|
|
}
|
|
},
|
|
|
|
container() {
|
|
return this.podTemplateSpec?.containers[0];
|
|
},
|
|
|
|
jobRelationships() {
|
|
if (!this.isCronJob) {
|
|
return;
|
|
}
|
|
|
|
return (get(this.value, 'metadata.relationships') || []).filter(relationship => relationship.toType === WORKLOAD_TYPES.JOB);
|
|
},
|
|
|
|
jobs() {
|
|
if (!this.isCronJob) {
|
|
return;
|
|
}
|
|
|
|
return this.jobRelationships.map((obj) => {
|
|
return this.$store.getters['cluster/byId'](WORKLOAD_TYPES.JOB, obj.toId );
|
|
}).filter(x => !!x);
|
|
},
|
|
|
|
jobSchema() {
|
|
return this.$store.getters['cluster/schemaFor'](WORKLOAD_TYPES.JOB);
|
|
},
|
|
|
|
jobHeaders() {
|
|
return this.$store.getters['type-map/headersFor'](this.jobSchema);
|
|
},
|
|
|
|
jobGauges() {
|
|
const out = {
|
|
succeeded: { color: 'success', count: 0 }, running: { color: 'info', count: 0 }, failed: { color: 'error', count: 0 }
|
|
};
|
|
|
|
if (this.value.type === WORKLOAD_TYPES.CRON_JOB) {
|
|
this.jobs.forEach((job) => {
|
|
const { status = {} } = job;
|
|
|
|
out.running.count += status.active || 0;
|
|
out.succeeded.count += status.succeeded || 0;
|
|
out.failed.count += status.failed || 0;
|
|
});
|
|
} else if (this.value.type === WORKLOAD_TYPES.JOB) {
|
|
const { status = {} } = this.value;
|
|
|
|
out.running.count = status.active || 0;
|
|
out.succeeded.count = status.succeeded || 0;
|
|
out.failed.count = status.failed || 0;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
totalRuns() {
|
|
if (!this.jobs) {
|
|
return;
|
|
}
|
|
|
|
return this.jobs.reduce((total, job) => {
|
|
const { status = {} } = job;
|
|
|
|
total += (status.active || 0);
|
|
total += (status.succeeded || 0);
|
|
total += (status.failed || 0);
|
|
|
|
return total;
|
|
}, 0);
|
|
},
|
|
|
|
podRestarts() {
|
|
return this.pods.reduce((total, pod) => {
|
|
const { status:{ containerStatuses = [] } } = pod;
|
|
|
|
if (containerStatuses.length) {
|
|
total += containerStatuses.reduce((tot, container) => {
|
|
tot += container.restartCount;
|
|
|
|
return tot;
|
|
}, 0);
|
|
}
|
|
|
|
return total;
|
|
}, 0);
|
|
},
|
|
|
|
podGauges() {
|
|
const out = {
|
|
active: { color: 'success' }, transitioning: { color: 'info' }, warning: { color: 'warning' }, error: { color: 'error' }
|
|
};
|
|
|
|
if (!this.pods) {
|
|
return out;
|
|
}
|
|
|
|
this.pods.map((pod) => {
|
|
const { status:{ phase } } = pod;
|
|
let group;
|
|
|
|
switch (phase) {
|
|
case 'Running':
|
|
group = 'active';
|
|
break;
|
|
case 'Pending':
|
|
group = 'transitioning';
|
|
break;
|
|
case 'Failed':
|
|
group = 'error';
|
|
break;
|
|
default:
|
|
group = 'warning';
|
|
}
|
|
|
|
out[group].count ? out[group].count++ : out[group].count = 1;
|
|
});
|
|
|
|
return out;
|
|
},
|
|
|
|
podHeaders() {
|
|
return [
|
|
STATE,
|
|
NAME,
|
|
NODE,
|
|
POD_IMAGES
|
|
];
|
|
},
|
|
|
|
graphVars() {
|
|
return {
|
|
namespace: this.value.namespace,
|
|
kind: WORKLOAD_TYPE_TO_KIND_MAPPING[this.value.type],
|
|
workload: this.value.shortId
|
|
};
|
|
}
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Loading v-if="$fetchState.pending" />
|
|
<div v-else>
|
|
<h3>
|
|
{{ isJob || isCronJob ? t('workload.detailTop.runs') :t('workload.detailTop.pods') }}
|
|
</h3>
|
|
<div v-if="pods || jobGauges" class="gauges mb-20">
|
|
<template v-if="jobGauges">
|
|
<CountGauge
|
|
v-for="(group, key) in jobGauges"
|
|
:key="key"
|
|
:total="isCronJob? totalRuns : pods.length"
|
|
:useful="group.count || 0"
|
|
:primary-color-var="`--sizzle-${group.color}`"
|
|
:name="t(`workload.gaugeStates.${key}`)"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<CountGauge
|
|
v-for="(group, key) in podGauges"
|
|
:key="key"
|
|
:total="pods.length"
|
|
:useful="group.count || 0"
|
|
:primary-color-var="`--sizzle-${group.color}`"
|
|
:name="t(`workload.gaugeStates.${key}`)"
|
|
/>
|
|
</template>
|
|
</div>
|
|
<ResourceTabs :value="value">
|
|
<Tab v-if="isCronJob" name="jobs" :label="t('tableHeaders.jobs')" :weight="4">
|
|
<SortableTable
|
|
:rows="jobs"
|
|
:headers="jobHeaders"
|
|
key-field="id"
|
|
:schema="jobSchema"
|
|
:show-groups="false"
|
|
:search="false"
|
|
/>
|
|
</Tab>
|
|
<Tab v-else name="pods" :label="t('tableHeaders.pods')" :weight="4">
|
|
<SortableTable
|
|
v-if="pods"
|
|
:rows="pods"
|
|
:headers="podHeaders"
|
|
key-field="id"
|
|
:table-actions="false"
|
|
:schema="podSchema"
|
|
:groupable="false"
|
|
:search="false"
|
|
/>
|
|
</Tab>
|
|
<Tab v-if="showMetrics" :label="t('workload.container.titles.metrics')" name="workload-metrics" :weight="3">
|
|
<template #default="props">
|
|
<DashboardMetrics
|
|
v-if="props.active"
|
|
:detail-url="WORKLOAD_METRICS_DETAIL_URL"
|
|
:summary-url="WORKLOAD_METRICS_SUMMARY_URL"
|
|
:vars="graphVars"
|
|
graph-height="550px"
|
|
/>
|
|
</template>
|
|
</Tab>
|
|
<Tab v-if="v1MonitoringUrl" name="v1Metrics" :label="t('node.detail.tab.metrics')" :weight="10">
|
|
<div id="ember-anchor">
|
|
<EmberPage inline="ember-anchor" :src="v1MonitoringUrl" />
|
|
</div>
|
|
</Tab>
|
|
</ResourceTabs>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang='scss' scoped>
|
|
.gauges {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
&>*{
|
|
flex: 1;
|
|
margin-right: $column-gutter;
|
|
}
|
|
}
|
|
</style>
|