mirror of https://github.com/rancher/dashboard.git
Merge pull request #4588 from rancher/workload-health-scaling
Show Workload Pod/Job State and Replica Scale buttons
This commit is contained in:
commit
ea5592322c
|
|
@ -4195,6 +4195,7 @@ tableHeaders:
|
||||||
one { Host }
|
one { Host }
|
||||||
other { Hosts }
|
other { Hosts }
|
||||||
}
|
}
|
||||||
|
health: Health
|
||||||
id: ID
|
id: ID
|
||||||
image: Image
|
image: Image
|
||||||
imageSize: Size
|
imageSize: Size
|
||||||
|
|
@ -4732,6 +4733,8 @@ workload:
|
||||||
label: Successful Job History Limit
|
label: Successful Job History Limit
|
||||||
tip: The number of successful finished jobs to retain.
|
tip: The number of successful finished jobs to retain.
|
||||||
suspend: Suspend
|
suspend: Suspend
|
||||||
|
list:
|
||||||
|
errorCannotScale: Failed to scale {workloadName} {direction, select, up { up } down { down } }
|
||||||
metrics:
|
metrics:
|
||||||
pod: Pod Metrics
|
pod: Pod Metrics
|
||||||
metricsView: Metrics View
|
metricsView: Metrics View
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.growl-container {
|
.growl-container {
|
||||||
|
z-index: 15;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,15 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'resourceTable.groupBy.namespace',
|
default: 'resourceTable.groupBy.namespace',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
overflowX: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
overflowY: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -263,6 +272,8 @@ export default {
|
||||||
:paging-params="pagingParams"
|
:paging-params="pagingParams"
|
||||||
:paging-label="pagingLabel"
|
:paging-label="pagingLabel"
|
||||||
:table-actions="_showBulkActions"
|
:table-actions="_showBulkActions"
|
||||||
|
:overflow-x="overflowX"
|
||||||
|
:overflow-y="overflowY"
|
||||||
key-field="_key"
|
key-field="_key"
|
||||||
:sort-generation-fn="sortGenerationFn"
|
:sort-generation-fn="sortGenerationFn"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ export default {
|
||||||
:class="{ sortable: col.sort, [col.breakpoint]: !!col.breakpoint}"
|
:class="{ sortable: col.sort, [col.breakpoint]: !!col.breakpoint}"
|
||||||
@click.prevent="changeSort($event, col)"
|
@click.prevent="changeSort($event, col)"
|
||||||
>
|
>
|
||||||
<span v-if="col.sort" @click="$router.applyQuery(queryFor(col))">
|
<span v-if="col.sort" v-tooltip="col.tooltip" @click="$router.applyQuery(queryFor(col))">
|
||||||
<span v-html="labelFor(col)" />
|
<span v-html="labelFor(col)" />
|
||||||
<span class="icon-stack">
|
<span class="icon-stack">
|
||||||
<i class="icon icon-sort icon-stack-1x faded" />
|
<i class="icon icon-sort icon-stack-1x faded" />
|
||||||
|
|
@ -143,7 +143,7 @@ export default {
|
||||||
<i v-if="isCurrent(col) && descending" class="icon icon-sort-up icon-stack-1x" />
|
<i v-if="isCurrent(col) && descending" class="icon icon-sort-up icon-stack-1x" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ labelFor(col) }}</span>
|
<span v-else v-tooltip="col.tooltip">{{ labelFor(col) }}</span>
|
||||||
</th>
|
</th>
|
||||||
<th v-if="rowActions" :width="rowActionsWidth">
|
<th v-if="rowActions" :width="rowActionsWidth">
|
||||||
</th>
|
</th>
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,15 @@ export default {
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
overflowX: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
overflowY: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If pagination of the data is enabled or not
|
* If pagination of the data is enabled or not
|
||||||
*/
|
*/
|
||||||
|
|
@ -391,12 +400,15 @@ export default {
|
||||||
classObject() {
|
classObject() {
|
||||||
return {
|
return {
|
||||||
'top-divider': this.topDivider,
|
'top-divider': this.topDivider,
|
||||||
'body-dividers': this.bodyDividers
|
'body-dividers': this.bodyDividers,
|
||||||
|
'overflow-y': this.overflowY,
|
||||||
|
'overflow-x': this.overflowX,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
get,
|
get,
|
||||||
dasherize,
|
dasherize,
|
||||||
|
|
||||||
|
|
@ -675,7 +687,7 @@ export default {
|
||||||
:key="col.name"
|
:key="col.name"
|
||||||
:data-title="labelFor(col)"
|
:data-title="labelFor(col)"
|
||||||
:align="col.align || 'left'"
|
:align="col.align || 'left'"
|
||||||
:class="{['col-'+dasherize(col.formatter||'')]: !!col.formatter, [col.breakpoint]: !!col.breakpoint}"
|
:class="{['col-'+dasherize(col.formatter||'')]: !!col.formatter, [col.breakpoint]: !!col.breakpoint, ['skip-select']: col.skipSelect}"
|
||||||
:width="col.width"
|
:width="col.width"
|
||||||
>
|
>
|
||||||
<slot :name="'cell:' + col.name" :row="row" :col="col" :value="valueFor(row,col)">
|
<slot :name="'cell:' + col.name" :row="row" :col="col" :value="valueFor(row,col)">
|
||||||
|
|
@ -686,6 +698,7 @@ export default {
|
||||||
:row="row"
|
:row="row"
|
||||||
:col="col"
|
:col="col"
|
||||||
v-bind="col.formatterOpts"
|
v-bind="col.formatterOpts"
|
||||||
|
:row-key="get(row,keyField)"
|
||||||
/>
|
/>
|
||||||
<template v-else-if="valueFor(row,col) !== ''">
|
<template v-else-if="valueFor(row,col) !== ''">
|
||||||
{{ formatValue(row,col) }}
|
{{ formatValue(row,col) }}
|
||||||
|
|
@ -831,6 +844,13 @@ $spacing: 10px;
|
||||||
background: var(--sortable-table-bg);
|
background: var(--sortable-table-bg);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.overflow-x {
|
||||||
|
overflow-x: visible;
|
||||||
|
}
|
||||||
|
&.overflow-y {
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 8px 5px;
|
padding: 8px 5px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,11 @@ export default {
|
||||||
async onRowClick(e) {
|
async onRowClick(e) {
|
||||||
const node = this.nodeForEvent(e);
|
const node = this.nodeForEvent(e);
|
||||||
const td = $(e.target).closest('TD');
|
const td = $(e.target).closest('TD');
|
||||||
|
const skipSelect = td.hasClass('skip-select');
|
||||||
|
|
||||||
|
if (skipSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const selection = this.selectedNodes;
|
const selection = this.selectedNodes;
|
||||||
const isCheckbox = this.isSelectionCheckbox(e.target) || td.hasClass('row-check');
|
const isCheckbox = this.isSelectionCheckbox(e.target) || td.hasClass('row-check');
|
||||||
const isExpand = td.hasClass('row-expand');
|
const isExpand = td.hasClass('row-expand');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
minusTooltip: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
plusTooltip: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
canMinus() {
|
||||||
|
return (this.min !== undefined && this.min !== null) ? this.value > this.min : true;
|
||||||
|
},
|
||||||
|
canPlus() {
|
||||||
|
return (this.max !== undefined && this.max !== null) ? this.value < this.max : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="plus-minus">
|
||||||
|
<button v-tooltip="minusTooltip" :disabled="disabled || !canMinus" type="button" class="btn btn-sm role-secondary" @click="$emit('minus')">
|
||||||
|
<i class="icon icon-sm icon-minus" />
|
||||||
|
</button>
|
||||||
|
<div class="value">
|
||||||
|
{{ value }}
|
||||||
|
</div>
|
||||||
|
<button v-tooltip="plusTooltip" :disabled="disabled || !canPlus" type="button" class="btn btn-sm role-secondary" @click="$emit('plus')">
|
||||||
|
<i class="icon icon-sm icon-plus" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.plus-minus {
|
||||||
|
min-width: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--accent-btn);
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
width: 25px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
import ProgressBarMulti from '@/components/ProgressBarMulti';
|
||||||
|
import PlusMinus from '@/components/form/PlusMinus';
|
||||||
|
import { SCALABLE_WORKLOAD_TYPES } from '~/config/types';
|
||||||
|
import { ucFirst } from '~/utils/string';
|
||||||
|
|
||||||
|
const SCALABLE_TYPES = Object.values(SCALABLE_WORKLOAD_TYPES);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { PlusMinus, ProgressBarMulti },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
document.addEventListener('click', this.onClickOutside);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
document.removeEventListener('click', this.onClickOutside);
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return { disabled: false, expanded: false };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
id() {
|
||||||
|
return `${ this.rowKey }-workload-health-scale`.replaceAll('-', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
canScale() {
|
||||||
|
return !!SCALABLE_TYPES.includes(this.row.type) && this.row.canUpdate;
|
||||||
|
},
|
||||||
|
|
||||||
|
parts() {
|
||||||
|
return Object.entries(this.row.jobGauges || this.row.podGauges || [])
|
||||||
|
.map(([name, value]) => ({
|
||||||
|
color: `bg-${ value.color }`,
|
||||||
|
value: value.count || 0,
|
||||||
|
label: ucFirst(name)
|
||||||
|
})).filter(x => x.value > 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClickOutside(event) {
|
||||||
|
const { [`root-${ this.id }`]: component } = this.$refs;
|
||||||
|
|
||||||
|
if (!component || component.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.expanded = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async scaleDown() {
|
||||||
|
await this.scale(false);
|
||||||
|
},
|
||||||
|
async scaleUp() {
|
||||||
|
await this.scale(true);
|
||||||
|
},
|
||||||
|
async scale(isUp) {
|
||||||
|
Vue.set(this, 'disabled', true);
|
||||||
|
try {
|
||||||
|
if (isUp) {
|
||||||
|
await this.row.scaleUp();
|
||||||
|
} else {
|
||||||
|
await this.row.scaleDown();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.$store.dispatch('growl/fromError', {
|
||||||
|
title: this.t('workload.list.errorCannotScale', { direction: isUp ? 'up' : 'down', workloadName: this.row.name }),
|
||||||
|
err
|
||||||
|
},
|
||||||
|
{ root: true });
|
||||||
|
}
|
||||||
|
Vue.set(this, 'disabled', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
insideBounds(bounding, bounds) {
|
||||||
|
return bounding.top >= bounds.top &&
|
||||||
|
bounding.left >= bounds.left &&
|
||||||
|
bounding.right <= bounds.right &&
|
||||||
|
bounding.bottom <= bounds.bottom;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
expanded(neu) {
|
||||||
|
// If the drop down content appears outside of the window then move it to be above the trigger
|
||||||
|
// Do this is three steps
|
||||||
|
// expanded: false & expanded-checked = false - Content does not appear in DOM
|
||||||
|
// expanded: true & expanded-checked = false - Content appears in DOM (so it's location can be calcualated to be in or out of an area) but isn't visible (user doesn't see content blip from below to above trigger)
|
||||||
|
// expanded: true & expanded-checked = true - Content appears in DOM and is visible (it's final location is known so user can see)
|
||||||
|
setTimeout(() => { // There be beasts without this (classes don't get applied... so drop down never gets shown)
|
||||||
|
const dropdown = document.getElementById(this.id);
|
||||||
|
|
||||||
|
if (!neu) {
|
||||||
|
dropdown.classList.remove('expanded-checked');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensire drop down will be inside of the window, otherwise show above the trigger
|
||||||
|
const bounding = dropdown.getBoundingClientRect();
|
||||||
|
const insideWindow = this.insideBounds(bounding, {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: window.innerWidth || document.documentElement.clientWidth,
|
||||||
|
bottom: window.innerHeight || document.documentElement.clientHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (insideWindow) {
|
||||||
|
dropdown.classList.remove('out-of-view');
|
||||||
|
} else {
|
||||||
|
dropdown.classList.add('out-of-view');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will trigger the actual display of the drop down (after we've calculated if it goes below or above trigger)
|
||||||
|
dropdown.classList.add('expanded-checked');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :id="`root-${id}`" :ref="`root-${id}`" class="hs-popover">
|
||||||
|
<div id="trigger" class="hs-popover__trigger" :class="{expanded}" @click="expanded = !expanded">
|
||||||
|
<ProgressBarMulti v-if="parts" class="health" :values="parts" :show-zeros="true" />
|
||||||
|
<i :class="{icon: true, 'icon-chevron-up': expanded, 'icon-chevron-down': !expanded}" />
|
||||||
|
</div>
|
||||||
|
<div :id="id" class="hs-popover__content" :class="{expanded, [id]:true}">
|
||||||
|
<div>
|
||||||
|
<div v-for="obj in parts" :key="obj.label" class="counts">
|
||||||
|
<span>{{ obj.label }}</span>
|
||||||
|
<span>{{ obj.value }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="canScale" class="text-center scale">
|
||||||
|
<span>{{ t('tableHeaders.scale') }} </span>
|
||||||
|
<PlusMinus :value="row.spec.replicas" :disabled="disabled" @minus="scaleDown" @plus="scaleUp" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
$height: 30px;
|
||||||
|
$width: 150px;
|
||||||
|
|
||||||
|
.hs-popover {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: $width;
|
||||||
|
height: $height;
|
||||||
|
|
||||||
|
border: solid thin var(--sortable-table-top-divider);
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
background-color: var(--sortable-table-row-bg);
|
||||||
|
}
|
||||||
|
&:not(.expanded):hover {
|
||||||
|
background-color: var(--accent-btn);
|
||||||
|
.icon {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.health {
|
||||||
|
width: $width - $height; // height is width of icon;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: $height;
|
||||||
|
width: $height;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
z-index: 14;
|
||||||
|
width: $width;
|
||||||
|
|
||||||
|
border: solid thin var(--sortable-table-top-divider);
|
||||||
|
background-color: var(--sortable-table-row-bg);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
margin-top: -1px;
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
&.expanded {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
&.expanded-checked {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.out-of-view {
|
||||||
|
// Flip to show drop down above trigger
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: $height - 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.scale {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
USER_ID, USERNAME, USER_DISPLAY_NAME, USER_PROVIDER, WORKLOAD_ENDPOINTS, STORAGE_CLASS_DEFAULT,
|
USER_ID, USERNAME, USER_DISPLAY_NAME, USER_PROVIDER, WORKLOAD_ENDPOINTS, STORAGE_CLASS_DEFAULT,
|
||||||
STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE,
|
STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE,
|
||||||
HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA,
|
HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA,
|
||||||
ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON
|
ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE
|
||||||
} from '@/config/table-headers';
|
} from '@/config/table-headers';
|
||||||
|
|
||||||
import { DSL } from '@/store/type-map';
|
import { DSL } from '@/store/type-map';
|
||||||
|
|
@ -168,14 +168,14 @@ export function init(store) {
|
||||||
headers(SERVICE, [STATE, NAME_COL, NAMESPACE_COL, TARGET_PORT, SELECTOR, SPEC_TYPE, AGE]);
|
headers(SERVICE, [STATE, NAME_COL, NAMESPACE_COL, TARGET_PORT, SELECTOR, SPEC_TYPE, AGE]);
|
||||||
headers(HPA, [STATE, NAME_COL, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, AGE]);
|
headers(HPA, [STATE, NAME_COL, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, AGE]);
|
||||||
|
|
||||||
headers(WORKLOAD, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, TYPE, 'Ready', AGE]);
|
headers(WORKLOAD, [STATE, NAME_COL, NAMESPACE_COL, TYPE, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.DEPLOYMENT, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', AGE]);
|
headers(WORKLOAD_TYPES.DEPLOYMENT, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.DAEMON_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]);
|
headers(WORKLOAD_TYPES.DAEMON_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.REPLICA_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]);
|
headers(WORKLOAD_TYPES.REPLICA_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.STATEFUL_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', AGE]);
|
headers(WORKLOAD_TYPES.STATEFUL_SET, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Completions', 'Duration', AGE]);
|
headers(WORKLOAD_TYPES.JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Completions', 'Duration', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.CRON_JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Schedule', 'Last Schedule', AGE]);
|
headers(WORKLOAD_TYPES.CRON_JOB, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Schedule', 'Last Schedule', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(WORKLOAD_TYPES.REPLICATION_CONTROLLER, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE]);
|
headers(WORKLOAD_TYPES.REPLICATION_CONTROLLER, [STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', AGE, WORKLOAD_HEALTH_SCALE]);
|
||||||
headers(POD, [STATE, NAME_COL, NAMESPACE_COL, POD_IMAGES, 'Ready', 'Restarts', 'IP', NODE_COL, AGE]);
|
headers(POD, [STATE, NAME_COL, NAMESPACE_COL, POD_IMAGES, 'Ready', 'Restarts', 'IP', NODE_COL, AGE]);
|
||||||
headers(STORAGE_CLASS, [STATE, NAME_COL, STORAGE_CLASS_PROVISIONER, STORAGE_CLASS_DEFAULT, AGE]);
|
headers(STORAGE_CLASS, [STATE, NAME_COL, STORAGE_CLASS_PROVISIONER, STORAGE_CLASS_DEFAULT, AGE]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { CATTLE_PUBLIC_ENDPOINTS } from '@/config/labels-annotations';
|
import { CATTLE_PUBLIC_ENDPOINTS } from '@/config/labels-annotations';
|
||||||
import { NODE as NODE_TYPE } from '@/config/types';
|
import { NODE as NODE_TYPE } from '@/config/types';
|
||||||
|
import { COLUMN_BREAKPOINTS } from '@/components/SortableTable/index.vue';
|
||||||
|
|
||||||
// Note: 'id' is always the last sort, so you don't have to specify it here.
|
// Note: 'id' is always the last sort, so you don't have to specify it here.
|
||||||
|
|
||||||
|
|
@ -602,7 +603,11 @@ export const WORKSPACE = {
|
||||||
sort: ['metadata.namespace', 'nameSort'],
|
sort: ['metadata.namespace', 'nameSort'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WORKLOAD_IMAGES = { ...POD_IMAGES, value: '' };
|
export const WORKLOAD_IMAGES = {
|
||||||
|
...POD_IMAGES,
|
||||||
|
value: '',
|
||||||
|
breakpoint: COLUMN_BREAKPOINTS.LAPTOP
|
||||||
|
};
|
||||||
|
|
||||||
export const WORKLOAD_ENDPOINTS = {
|
export const WORKLOAD_ENDPOINTS = {
|
||||||
name: 'workloadEndpoints',
|
name: 'workloadEndpoints',
|
||||||
|
|
@ -610,6 +615,15 @@ export const WORKLOAD_ENDPOINTS = {
|
||||||
value: `$['metadata']['annotations']['${ CATTLE_PUBLIC_ENDPOINTS }']`,
|
value: `$['metadata']['annotations']['${ CATTLE_PUBLIC_ENDPOINTS }']`,
|
||||||
formatter: 'Endpoints',
|
formatter: 'Endpoints',
|
||||||
dashIfEmpty: true,
|
dashIfEmpty: true,
|
||||||
|
breakpoint: COLUMN_BREAKPOINTS.DESKTOP
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WORKLOAD_HEALTH_SCALE = {
|
||||||
|
name: 'workloadHealthScale',
|
||||||
|
labelKey: 'tableHeaders.health',
|
||||||
|
formatter: 'WorkloadHealthScale',
|
||||||
|
width: 150,
|
||||||
|
skipSelect: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FLEET_SUMMARY = {
|
export const FLEET_SUMMARY = {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import Loading from '@/components/Loading';
|
||||||
import ResourceTabs from '@/components/form/ResourceTabs';
|
import ResourceTabs from '@/components/form/ResourceTabs';
|
||||||
import CountGauge from '@/components/CountGauge';
|
import CountGauge from '@/components/CountGauge';
|
||||||
import { allHash } from '@/utils/promise';
|
import { allHash } from '@/utils/promise';
|
||||||
import { get } from '@/utils/object';
|
|
||||||
import DashboardMetrics from '@/components/DashboardMetrics';
|
import DashboardMetrics from '@/components/DashboardMetrics';
|
||||||
import V1WorkloadMetrics from '@/mixins/v1-workload-metrics';
|
import V1WorkloadMetrics from '@/mixins/v1-workload-metrics';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
@ -72,16 +71,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['currentCluster']),
|
...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() {
|
isJob() {
|
||||||
return this.value.type === WORKLOAD_TYPES.JOB;
|
return this.value.type === WORKLOAD_TYPES.JOB;
|
||||||
},
|
},
|
||||||
|
|
@ -108,24 +98,6 @@ export default {
|
||||||
return this.podTemplateSpec?.containers[0];
|
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() {
|
jobSchema() {
|
||||||
return this.$store.getters['cluster/schemaFor'](WORKLOAD_TYPES.JOB);
|
return this.$store.getters['cluster/schemaFor'](WORKLOAD_TYPES.JOB);
|
||||||
},
|
},
|
||||||
|
|
@ -134,38 +106,12 @@ export default {
|
||||||
return this.$store.getters['type-map/headersFor'](this.jobSchema);
|
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() {
|
totalRuns() {
|
||||||
if (!this.jobs) {
|
if (!this.value.jobs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.jobs.reduce((total, job) => {
|
return this.value.jobs.reduce((total, job) => {
|
||||||
const { status = {} } = job;
|
const { status = {} } = job;
|
||||||
|
|
||||||
total += (status.active || 0);
|
total += (status.active || 0);
|
||||||
|
|
@ -177,7 +123,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
podRestarts() {
|
podRestarts() {
|
||||||
return this.pods.reduce((total, pod) => {
|
return this.value.pods.reduce((total, pod) => {
|
||||||
const { status:{ containerStatuses = [] } } = pod;
|
const { status:{ containerStatuses = [] } } = pod;
|
||||||
|
|
||||||
if (containerStatuses.length) {
|
if (containerStatuses.length) {
|
||||||
|
|
@ -192,39 +138,6 @@ export default {
|
||||||
}, 0);
|
}, 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() {
|
podHeaders() {
|
||||||
return [
|
return [
|
||||||
STATE,
|
STATE,
|
||||||
|
|
@ -255,12 +168,12 @@ export default {
|
||||||
<h3>
|
<h3>
|
||||||
{{ isJob || isCronJob ? t('workload.detailTop.runs') :t('workload.detailTop.pods') }}
|
{{ isJob || isCronJob ? t('workload.detailTop.runs') :t('workload.detailTop.pods') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div v-if="pods || jobGauges" class="gauges mb-20">
|
<div v-if="value.pods || value.jobGauges" class="gauges mb-20">
|
||||||
<template v-if="jobGauges">
|
<template v-if="value.jobGauges">
|
||||||
<CountGauge
|
<CountGauge
|
||||||
v-for="(group, key) in jobGauges"
|
v-for="(group, key) in value.jobGauges"
|
||||||
:key="key"
|
:key="key"
|
||||||
:total="isCronJob? totalRuns : pods.length"
|
:total="isCronJob? totalRuns : value.pods.length"
|
||||||
:useful="group.count || 0"
|
:useful="group.count || 0"
|
||||||
:primary-color-var="`--sizzle-${group.color}`"
|
:primary-color-var="`--sizzle-${group.color}`"
|
||||||
:name="t(`workload.gaugeStates.${key}`)"
|
:name="t(`workload.gaugeStates.${key}`)"
|
||||||
|
|
@ -268,9 +181,9 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<CountGauge
|
<CountGauge
|
||||||
v-for="(group, key) in podGauges"
|
v-for="(group, key) in value.podGauges"
|
||||||
:key="key"
|
:key="key"
|
||||||
:total="pods.length"
|
:total="value.pods.length"
|
||||||
:useful="group.count || 0"
|
:useful="group.count || 0"
|
||||||
:primary-color-var="`--sizzle-${group.color}`"
|
:primary-color-var="`--sizzle-${group.color}`"
|
||||||
:name="t(`workload.gaugeStates.${key}`)"
|
:name="t(`workload.gaugeStates.${key}`)"
|
||||||
|
|
@ -280,7 +193,7 @@ export default {
|
||||||
<ResourceTabs :value="value">
|
<ResourceTabs :value="value">
|
||||||
<Tab v-if="isCronJob" name="jobs" :label="t('tableHeaders.jobs')" :weight="4">
|
<Tab v-if="isCronJob" name="jobs" :label="t('tableHeaders.jobs')" :weight="4">
|
||||||
<SortableTable
|
<SortableTable
|
||||||
:rows="jobs"
|
:rows="value.jobs"
|
||||||
:headers="jobHeaders"
|
:headers="jobHeaders"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
:schema="jobSchema"
|
:schema="jobSchema"
|
||||||
|
|
@ -290,8 +203,8 @@ export default {
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab v-else name="pods" :label="t('tableHeaders.pods')" :weight="4">
|
<Tab v-else name="pods" :label="t('tableHeaders.pods')" :weight="4">
|
||||||
<SortableTable
|
<SortableTable
|
||||||
v-if="pods"
|
v-if="value.pods"
|
||||||
:rows="pods"
|
:rows="value.pods"
|
||||||
:headers="podHeaders"
|
:headers="podHeaders"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
:table-actions="false"
|
:table-actions="false"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import ResourceTable from '@/components/ResourceTable';
|
import ResourceTable from '@/components/ResourceTable';
|
||||||
import { WORKLOAD_TYPES, SCHEMA, NODE } from '@/config/types';
|
import { WORKLOAD_TYPES, SCHEMA, NODE, POD } from '@/config/types';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
|
|
@ -28,6 +28,8 @@ export default {
|
||||||
|
|
||||||
let resources;
|
let resources;
|
||||||
|
|
||||||
|
this.loadHeathResources();
|
||||||
|
|
||||||
if ( this.allTypes ) {
|
if ( this.allTypes ) {
|
||||||
resources = await Promise.all(Object.values(WORKLOAD_TYPES).map((type) => {
|
resources = await Promise.all(Object.values(WORKLOAD_TYPES).map((type) => {
|
||||||
// You may not have RBAC to see some of the types
|
// You may not have RBAC to see some of the types
|
||||||
|
|
@ -88,6 +90,29 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
loadHeathResources() {
|
||||||
|
// Fetch these in the background to populate workload health
|
||||||
|
if ( this.allTypes ) {
|
||||||
|
this.$store.dispatch('cluster/findAll', { type: POD });
|
||||||
|
this.$store.dispatch('cluster/findAll', { type: WORKLOAD_TYPES.JOB });
|
||||||
|
} else {
|
||||||
|
const type = this.$route.params.resource;
|
||||||
|
|
||||||
|
if (type === WORKLOAD_TYPES.JOB) {
|
||||||
|
// Ignore job (we're fetching this anyway, plus they contain their own state)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === WORKLOAD_TYPES.CRON_JOB) {
|
||||||
|
this.$store.dispatch('cluster/findAll', { type: WORKLOAD_TYPES.JOB });
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('cluster/findAll', { type: POD });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
typeDisplay() {
|
typeDisplay() {
|
||||||
const { params:{ resource:type } } = this.$route;
|
const { params:{ resource:type } } = this.$route;
|
||||||
let paramSchema = schema;
|
let paramSchema = schema;
|
||||||
|
|
@ -103,5 +128,5 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Loading v-if="$fetchState.pending" />
|
<Loading v-if="$fetchState.pending" />
|
||||||
<ResourceTable v-else :schema="schema" :rows="rows" />
|
<ResourceTable v-else :schema="schema" :rows="rows" :overflow-y="true" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ export default class ClusterRepo extends SteveModel {
|
||||||
get stateObj() {
|
get stateObj() {
|
||||||
return this.metadata?.state ? {
|
return this.metadata?.state ? {
|
||||||
...this.metadata.state,
|
...this.metadata.state,
|
||||||
transitioning: this.metadata.generation > this.status.observedGeneration ? false : this.metadata.state.transitioning
|
transitioning: this.metadata.generation > this.status?.observedGeneration ? false : this.metadata.state.transitioning
|
||||||
} : undefined;
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { findBy, insertAt } from '@/utils/array';
|
import { findBy, insertAt } from '@/utils/array';
|
||||||
import { TARGET_WORKLOADS, TIMESTAMP, UI_MANAGED } from '@/config/labels-annotations';
|
import { TARGET_WORKLOADS, TIMESTAMP, UI_MANAGED } from '@/config/labels-annotations';
|
||||||
import { WORKLOAD_TYPES, SERVICE } from '@/config/types';
|
import { POD, WORKLOAD_TYPES, SERVICE } from '@/config/types';
|
||||||
import { clone, get, set } from '@/utils/object';
|
import { clone, get, set } from '@/utils/object';
|
||||||
import day from 'dayjs';
|
import day from 'dayjs';
|
||||||
import SteveModel from '@/plugins/steve/steve-class';
|
import SteveModel from '@/plugins/steve/steve-class';
|
||||||
|
|
@ -127,6 +127,20 @@ export default class Workload extends SteveModel {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async scaleDown() {
|
||||||
|
const newScale = this.spec.replicas - 1;
|
||||||
|
|
||||||
|
if (newScale >= 0) {
|
||||||
|
set(this.spec, 'replicas', newScale);
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async scaleUp() {
|
||||||
|
set(this.spec, 'replicas', this.spec.replicas + 1);
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
get state() {
|
get state() {
|
||||||
if ( this.spec?.paused === true ) {
|
if ( this.spec?.paused === true ) {
|
||||||
return 'paused';
|
return 'paused';
|
||||||
|
|
@ -612,4 +626,93 @@ export default class Workload extends SteveModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get pods() {
|
||||||
|
const relationships = get(this, 'metadata.relationships') || [];
|
||||||
|
const podRelationship = relationships.filter(relationship => relationship.toType === POD)[0];
|
||||||
|
|
||||||
|
if (podRelationship) {
|
||||||
|
return this.$getters['matching'](POD, podRelationship.selector).filter(pod => pod?.metadata?.namespace === this.metadata.namespace);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job Specific
|
||||||
|
get jobRelationships() {
|
||||||
|
if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (get(this, 'metadata.relationships') || []).filter(relationship => relationship.toType === WORKLOAD_TYPES.JOB);
|
||||||
|
}
|
||||||
|
|
||||||
|
get jobs() {
|
||||||
|
if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.jobRelationships.map((obj) => {
|
||||||
|
return this.$getters['byId'](WORKLOAD_TYPES.JOB, obj.toId );
|
||||||
|
}).filter(x => !!x);
|
||||||
|
}
|
||||||
|
|
||||||
|
get jobGauges() {
|
||||||
|
const out = {
|
||||||
|
succeeded: { color: 'success', count: 0 }, running: { color: 'info', count: 0 }, failed: { color: 'error', count: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.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.type === WORKLOAD_TYPES.JOB) {
|
||||||
|
const { status = {} } = this;
|
||||||
|
|
||||||
|
out.running.count = status.active || 0;
|
||||||
|
out.succeeded.count = status.succeeded || 0;
|
||||||
|
out.failed.count = status.failed || 0;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -932,15 +932,18 @@ export const getters = {
|
||||||
const exists = rootGetters['i18n/exists'];
|
const exists = rootGetters['i18n/exists'];
|
||||||
const t = rootGetters['i18n/t'];
|
const t = rootGetters['i18n/t'];
|
||||||
const labelKey = `tableHeaders.${ col.name }`;
|
const labelKey = `tableHeaders.${ col.name }`;
|
||||||
|
const description = col.description || '';
|
||||||
|
const tooltip = description && description[description.length - 1] === '.' ? description.slice(0, -1) : description;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: col.name.toLowerCase(),
|
name: col.name.toLowerCase(),
|
||||||
label: exists(labelKey) ? t(labelKey) : col.name,
|
label: exists(labelKey) ? t(labelKey) : col.name,
|
||||||
value: col.field.startsWith('.') ? `$${ col.field }` : col.field,
|
value: col.field.startsWith('.') ? `$${ col.field }` : col.field,
|
||||||
sort: [col.field],
|
sort: [col.field],
|
||||||
formatter,
|
formatter,
|
||||||
formatterOpts,
|
formatterOpts,
|
||||||
width,
|
width,
|
||||||
|
tooltip
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue