mirror of https://github.com/rancher/dashboard.git
Merge pull request #3293 from richard-cox/beefier-kube-nodes
Add grouping and more info to the explorer kube node list
This commit is contained in:
commit
3f97fcf101
|
|
@ -41,8 +41,8 @@ $z-indexes: (
|
||||||
// @media only screen and (min-width: map-get($breakpoints, '--viewport-*')) {
|
// @media only screen and (min-width: map-get($breakpoints, '--viewport-*')) {
|
||||||
// }
|
// }
|
||||||
$breakpoints: (
|
$breakpoints: (
|
||||||
'--viewport-4': 480px,
|
'--viewport-4': 480px, // Phone
|
||||||
'--viewport-7': 768px,
|
'--viewport-7': 768px, // Tablet
|
||||||
'--viewport-9': 992px,
|
'--viewport-9': 992px, // Laptop/Desktop
|
||||||
'--viewport-12': 1281px,
|
'--viewport-12': 1281px, // Desktop
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,10 @@ asyncButton:
|
||||||
action: Download
|
action: Download
|
||||||
success: Saving
|
success: Saving
|
||||||
waiting: Downloading…
|
waiting: Downloading…
|
||||||
|
drain:
|
||||||
|
action: Drain
|
||||||
|
success: Drained
|
||||||
|
waiting: Draining…
|
||||||
edit:
|
edit:
|
||||||
action: Save
|
action: Save
|
||||||
success: Saved
|
success: Saved
|
||||||
|
|
@ -1136,6 +1140,7 @@ cluster:
|
||||||
cluster: Cluster Configuration
|
cluster: Cluster Configuration
|
||||||
etcd: etcd
|
etcd: etcd
|
||||||
networking: Networking
|
networking: Networking
|
||||||
|
nodePools: Node Pools
|
||||||
machinePools: Machine Pools
|
machinePools: Machine Pools
|
||||||
registry: Private Registry
|
registry: Private Registry
|
||||||
upgrade: Upgrade Strategy
|
upgrade: Upgrade Strategy
|
||||||
|
|
@ -1145,6 +1150,9 @@ cluster:
|
||||||
services: Services
|
services: Services
|
||||||
allServices: Rotate all service certificates
|
allServices: Rotate all service certificates
|
||||||
selectService: Rotate an individual service
|
selectService: Rotate an individual service
|
||||||
|
nodePool:
|
||||||
|
scaleDown: Scale Pool Down
|
||||||
|
scaleUp: Scale Pool Up
|
||||||
|
|
||||||
clusterIndexPage:
|
clusterIndexPage:
|
||||||
hardwareResourceGauge:
|
hardwareResourceGauge:
|
||||||
|
|
@ -1223,6 +1231,27 @@ detailText:
|
||||||
other {+ {n, number} more chars}
|
other {+ {n, number} more chars}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drainNode:
|
||||||
|
action: 'Drain'
|
||||||
|
actionStop: 'Stop Drain'
|
||||||
|
titleOne: Drain {name}
|
||||||
|
titleMultiple: 'Drain {count} Nodes'
|
||||||
|
deleteLocalData: Delete Empty Dir Data
|
||||||
|
force: Force
|
||||||
|
safe:
|
||||||
|
label: Safe
|
||||||
|
helpText: If a node has standalone pods or ephemeral data it will be cordoned but not drained.
|
||||||
|
gracePeriod:
|
||||||
|
title: Grace period for pods to terminate themselves
|
||||||
|
default: Honor the default from each pod
|
||||||
|
placeholder: e.g. 30
|
||||||
|
custom: "Ignore the defaults and give each pod:"
|
||||||
|
timeout:
|
||||||
|
title: "Drain timeout"
|
||||||
|
default: Keep trying forever
|
||||||
|
placeholder: e.g. 60
|
||||||
|
custom: "Give up after:"
|
||||||
|
|
||||||
etcdInfoBanner:
|
etcdInfoBanner:
|
||||||
hasLeader: "Etcd has a leader:"
|
hasLeader: "Etcd has a leader:"
|
||||||
leaderChanges: "Number of leader changes:"
|
leaderChanges: "Number of leader changes:"
|
||||||
|
|
@ -2110,6 +2139,12 @@ namespaceList:
|
||||||
addLabel: Add Namespace
|
addLabel: Add Namespace
|
||||||
|
|
||||||
node:
|
node:
|
||||||
|
list:
|
||||||
|
pool: Pool
|
||||||
|
nodeTaint: Taints
|
||||||
|
poolDescription:
|
||||||
|
noSize: No Size
|
||||||
|
noLocation: No Location
|
||||||
detail:
|
detail:
|
||||||
detailTop:
|
detailTop:
|
||||||
containerRuntime: Container Runtime
|
containerRuntime: Container Runtime
|
||||||
|
|
@ -2888,6 +2923,8 @@ resourceTable:
|
||||||
project: "<span>Project:</span> {name}"
|
project: "<span>Project:</span> {name}"
|
||||||
notInAWorkspace: Not in a Workspace
|
notInAWorkspace: Not in a Workspace
|
||||||
workspace: "<span>Workspace:</span> {name}"
|
workspace: "<span>Workspace:</span> {name}"
|
||||||
|
notInANodePool: "Not in a Pool"
|
||||||
|
nodePool: "<span>Node Pool:</span> {name} ({count})"
|
||||||
|
|
||||||
resourceTabs:
|
resourceTabs:
|
||||||
conditions:
|
conditions:
|
||||||
|
|
@ -3542,6 +3579,7 @@ tableHeaders:
|
||||||
subType: Kind
|
subType: Kind
|
||||||
success: Success
|
success: Success
|
||||||
summary: Summary
|
summary: Summary
|
||||||
|
taints: Taints
|
||||||
target: Target
|
target: Target
|
||||||
targetKind: Target Type
|
targetKind: Target Type
|
||||||
targetPort: Target
|
targetPort: Target
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export default {
|
||||||
:key="col.name"
|
:key="col.name"
|
||||||
:align="col.align || 'left'"
|
:align="col.align || 'left'"
|
||||||
:width="col.width"
|
:width="col.width"
|
||||||
:class="{ sortable: col.sort }"
|
: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" @click="$router.applyQuery(queryFor(col))">
|
||||||
|
|
@ -180,6 +180,26 @@ export default {
|
||||||
& A {
|
& A {
|
||||||
color: var(--body-text);
|
color: var(--body-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aligns with COLUMN_BREAKPOINTS
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-4')) {
|
||||||
|
// HIDE column on sizes below 480px
|
||||||
|
&.tablet, &.laptop, &.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-9')) {
|
||||||
|
// HIDE column on sizes below 992px
|
||||||
|
&.laptop, &.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-12')) {
|
||||||
|
// HIDE column on sizes below 1281px
|
||||||
|
&.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-stack {
|
.icon-stack {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,21 @@ import sorting from './sorting';
|
||||||
import paging from './paging';
|
import paging from './paging';
|
||||||
import grouping from './grouping';
|
import grouping from './grouping';
|
||||||
|
|
||||||
|
export const COLUMN_BREAKPOINTS = {
|
||||||
|
/**
|
||||||
|
* Only show column if at tablet width or wider
|
||||||
|
*/
|
||||||
|
TABLET: 'tablet',
|
||||||
|
/**
|
||||||
|
* Only show column if at laptop width or wider
|
||||||
|
*/
|
||||||
|
LAPTOP: 'laptop',
|
||||||
|
/**
|
||||||
|
* Only show column if at desktop width or wider
|
||||||
|
*/
|
||||||
|
DESKTOP: 'desktop'
|
||||||
|
};
|
||||||
|
|
||||||
// @TODO:
|
// @TODO:
|
||||||
// Fixed header/scrolling
|
// Fixed header/scrolling
|
||||||
|
|
||||||
|
|
@ -598,7 +613,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}"
|
:class="{['col-'+dasherize(col.formatter||'')]: !!col.formatter, [col.breakpoint]: !!col.breakpoint}"
|
||||||
: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)">
|
||||||
|
|
@ -708,6 +723,26 @@ export default {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aligns with COLUMN_BREAKPOINTS
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-4')) {
|
||||||
|
// HIDE column on sizes below 480px
|
||||||
|
&.tablet, &.laptop, &.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-9')) {
|
||||||
|
// HIDE column on sizes below 992px
|
||||||
|
&.laptop, &.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: map-get($breakpoints, '--viewport-12')) {
|
||||||
|
// HIDE column on sizes below 1281px
|
||||||
|
&.desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ export default {
|
||||||
onRowMouseEnter(e) {
|
onRowMouseEnter(e) {
|
||||||
const tr = $(e.target).closest('TR');
|
const tr = $(e.target).closest('TR');
|
||||||
|
|
||||||
if (tr.hasClass('state-description')) {
|
if (tr.hasClass('sub-row')) {
|
||||||
const trMainRow = tr.prev('TR');
|
const trMainRow = tr.prev('TR');
|
||||||
|
|
||||||
trMainRow.toggleClass('sub-row-hovered', true);
|
trMainRow.toggleClass('sub-row-hovered', true);
|
||||||
|
|
@ -121,7 +121,7 @@ export default {
|
||||||
onRowMouseLeave(e) {
|
onRowMouseLeave(e) {
|
||||||
const tr = $(e.target).closest('TR');
|
const tr = $(e.target).closest('TR');
|
||||||
|
|
||||||
if (tr.hasClass('state-description')) {
|
if (tr.hasClass('sub-row')) {
|
||||||
const trMainRow = tr.prev('TR');
|
const trMainRow = tr.prev('TR');
|
||||||
|
|
||||||
trMainRow.toggleClass('sub-row-hovered', false);
|
trMainRow.toggleClass('sub-row-hovered', false);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
import AsyncButton from '@/components/AsyncButton';
|
||||||
|
import Banner from '@/components/Banner';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import RadioGroup from '@/components/form/RadioGroup';
|
||||||
|
import UnitInput from '@/components/form/UnitInput';
|
||||||
|
import { _EDIT, _VIEW } from '@/config/query-params';
|
||||||
|
|
||||||
|
import { exceptionToErrorsArray } from '@/utils/error';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AsyncButton,
|
||||||
|
Banner,
|
||||||
|
Card,
|
||||||
|
RadioGroup,
|
||||||
|
UnitInput
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
radioOptions: [{
|
||||||
|
label: this.t('generic.yes'),
|
||||||
|
value: true,
|
||||||
|
}, {
|
||||||
|
label: this.t('generic.no'),
|
||||||
|
value: false,
|
||||||
|
}],
|
||||||
|
gracePeriodOptions: [{
|
||||||
|
label: this.t('drainNode.gracePeriod.default'),
|
||||||
|
value: false,
|
||||||
|
}, {
|
||||||
|
label: this.t('drainNode.gracePeriod.custom'),
|
||||||
|
value: true,
|
||||||
|
}],
|
||||||
|
timeoutOptions: [{
|
||||||
|
label: this.t('drainNode.timeout.default'),
|
||||||
|
value: false,
|
||||||
|
}, {
|
||||||
|
label: this.t('drainNode.timeout.custom'),
|
||||||
|
value: true,
|
||||||
|
}],
|
||||||
|
|
||||||
|
gracePeriod: false,
|
||||||
|
timeout: false,
|
||||||
|
|
||||||
|
body: {
|
||||||
|
deleteLocalData: false,
|
||||||
|
force: false,
|
||||||
|
gracePeriod: null,
|
||||||
|
timeout: null
|
||||||
|
},
|
||||||
|
|
||||||
|
EDIT: _EDIT,
|
||||||
|
VIEW: _VIEW,
|
||||||
|
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
kubeNodes() {
|
||||||
|
return this.resources[0];
|
||||||
|
},
|
||||||
|
normanNodeId() {
|
||||||
|
return this.resources[1];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
gracePeriod(neu) {
|
||||||
|
if (neu && !this.body.gracePeriod) {
|
||||||
|
this.body.gracePeriod = 30;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'body.gracePeriod'(neu) {
|
||||||
|
if (neu && neu < 1) {
|
||||||
|
Vue.set(this.body, 'gracePeriod', 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeout(neu) {
|
||||||
|
if (neu && !this.body.timeout) {
|
||||||
|
this.body.timeout = 60;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'body.timeout'(neu) {
|
||||||
|
if (neu) {
|
||||||
|
if (neu < 1) {
|
||||||
|
Vue.set(this.body, 'timeout', 1);
|
||||||
|
} else if (neu > 10800) {
|
||||||
|
Vue.set(this.body, 'timeout', 10800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async apply(buttonDone) {
|
||||||
|
const { gracePeriod, timeout, ...parsedBody } = this.body;
|
||||||
|
|
||||||
|
if (this.gracePeriod) {
|
||||||
|
parsedBody.gracePeriod = gracePeriod;
|
||||||
|
}
|
||||||
|
if (this.timeout) {
|
||||||
|
parsedBody.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(this.kubeNodes.map(node => node.normanNode.doAction('drain', parsedBody)));
|
||||||
|
this.close();
|
||||||
|
} catch (e) {
|
||||||
|
this.errors = exceptionToErrorsArray(e);
|
||||||
|
buttonDone(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card class="prompt-rotate" :show-highlight-border="false">
|
||||||
|
<h4 slot="title" class="text-default-text">
|
||||||
|
<template v-if="kubeNodes.length > 1">
|
||||||
|
{{ t('drainNode.titleMultiple', { count: kubeNodes.length }) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ t('drainNode.titleOne', { name: kubeNodes[0].name }, true) }}
|
||||||
|
</template>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div slot="body" class="pl-10 pr-10">
|
||||||
|
<div>
|
||||||
|
<RadioGroup
|
||||||
|
v-model="body.deleteLocalData"
|
||||||
|
name="deleteLocalData"
|
||||||
|
:options="radioOptions"
|
||||||
|
:row="true"
|
||||||
|
class="mb-15"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<h5>{{ t('drainNode.deleteLocalData') }}</h5>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
<RadioGroup v-model="body.force" name="force" :options="radioOptions" :row="true" class="mb-15">
|
||||||
|
<template #label>
|
||||||
|
<h5>{{ t('drainNode.force') }}</h5>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
<RadioGroup v-model="gracePeriod" name="gracePeriod" :options="gracePeriodOptions" class="mb-15">
|
||||||
|
<template #label>
|
||||||
|
<h5>{{ t('drainNode.gracePeriod.title') }}</h5>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
<UnitInput
|
||||||
|
v-model="body.gracePeriod"
|
||||||
|
:mode="gracePeriod ? EDIT : VIEW"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
:suffix="t('suffix.sec')"
|
||||||
|
:placeholder="t('drainNode.gracePeriod.placeholder')"
|
||||||
|
class="mb-10"
|
||||||
|
/>
|
||||||
|
<RadioGroup v-model="timeout" name="timeout" :options="timeoutOptions" class="mb-15">
|
||||||
|
<template #label>
|
||||||
|
<h5>{{ t('drainNode.timeout.title') }}</h5>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
<UnitInput
|
||||||
|
v-model="body.timeout"
|
||||||
|
:mode="timeout ? EDIT : VIEW"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="10800"
|
||||||
|
:suffix="t('suffix.sec')"
|
||||||
|
:placeholder="t('drainNode.timeout.placeholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Banner v-for="(err, i) in errors" :key="i" color="error" :label="err" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="actions" class="buttons">
|
||||||
|
<button class="btn role-secondary mr-10" @click="close">
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<AsyncButton
|
||||||
|
mode="drain"
|
||||||
|
@click="apply"
|
||||||
|
>
|
||||||
|
</AsyncButton>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.prompt-rotate {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.card-title h4 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -121,7 +121,7 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="label || labelKey || tooltip || tooltipKey" class="radio-group label">
|
<div v-if="label || labelKey || tooltip || tooltipKey || $slots.label" class="radio-group label">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<h3>
|
<h3>
|
||||||
<t v-if="labelKey" :k="labelKey" />
|
<t v-if="labelKey" :k="labelKey" />
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
percentage() {
|
percentage() {
|
||||||
return Number.parseFloat(this.value) / 100;
|
return Number.parseFloat(this.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
STATE, NAME as NAME_COL, NAMESPACE as NAMESPACE_COL, AGE, KEYS,
|
STATE, NAME as NAME_COL, NAMESPACE as NAMESPACE_COL, AGE, KEYS,
|
||||||
INGRESS_DEFAULT_BACKEND, INGRESS_TARGET, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM,
|
INGRESS_DEFAULT_BACKEND, INGRESS_TARGET,
|
||||||
SPEC_TYPE, TARGET_PORT, SELECTOR, NODE as NODE_COL, TYPE, WORKLOAD_IMAGES, POD_IMAGES,
|
SPEC_TYPE, TARGET_PORT, SELECTOR, NODE as NODE_COL, TYPE, WORKLOAD_IMAGES, POD_IMAGES,
|
||||||
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,
|
||||||
|
|
@ -167,7 +167,6 @@ export function init(store) {
|
||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
headers(INGRESS, [STATE, NAME_COL, NAMESPACE_COL, INGRESS_TARGET, INGRESS_DEFAULT_BACKEND, AGE]);
|
headers(INGRESS, [STATE, NAME_COL, NAMESPACE_COL, INGRESS_TARGET, INGRESS_DEFAULT_BACKEND, AGE]);
|
||||||
headers(NODE, [STATE, NAME_COL, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM, AGE]);
|
|
||||||
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]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,8 +198,10 @@ export const PODS = {
|
||||||
name: 'pods',
|
name: 'pods',
|
||||||
labelKey: 'tableHeaders.pods',
|
labelKey: 'tableHeaders.pods',
|
||||||
sort: 'pods',
|
sort: 'pods',
|
||||||
value: 'podUsage',
|
search: false,
|
||||||
formatter: 'PercentageBar'
|
value: 'podConsumedUsage',
|
||||||
|
formatter: 'PercentageBar',
|
||||||
|
width: 120,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AGE = {
|
export const AGE = {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ export const NORMAN = {
|
||||||
CLUSTER_TOKEN: 'clusterregistrationtoken',
|
CLUSTER_TOKEN: 'clusterregistrationtoken',
|
||||||
CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterRoleTemplateBinding',
|
CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterRoleTemplateBinding',
|
||||||
GROUP: 'group',
|
GROUP: 'group',
|
||||||
|
// Note - This allows access to node resources, not schema's or custom components (both are accessed via 'type' which clashes with kube node)
|
||||||
|
NODE: 'node',
|
||||||
PRINCIPAL: 'principal',
|
PRINCIPAL: 'principal',
|
||||||
PROJECT: 'project',
|
PROJECT: 'project',
|
||||||
PROJECT_ROLE_TEMPLATE_BINDING: 'projectRoleTemplateBinding',
|
PROJECT_ROLE_TEMPLATE_BINDING: 'projectRoleTemplateBinding',
|
||||||
|
|
@ -137,6 +139,7 @@ export const MANAGEMENT = {
|
||||||
FEATURE: 'management.cattle.io.feature',
|
FEATURE: 'management.cattle.io.feature',
|
||||||
GROUP: 'management.cattle.io.group',
|
GROUP: 'management.cattle.io.group',
|
||||||
KONTANIER_DRIVER: 'management.cattle.io.kontainerdriver',
|
KONTANIER_DRIVER: 'management.cattle.io.kontainerdriver',
|
||||||
|
NODE: 'management.cattle.io.node',
|
||||||
NODE_DRIVER: 'management.cattle.io.nodedriver',
|
NODE_DRIVER: 'management.cattle.io.nodedriver',
|
||||||
NODE_POOL: 'management.cattle.io.nodepool',
|
NODE_POOL: 'management.cattle.io.nodepool',
|
||||||
NODE_TEMPLATE: 'management.cattle.io.nodetemplate',
|
NODE_TEMPLATE: 'management.cattle.io.nodetemplate',
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ export default {
|
||||||
:value="value"
|
:value="value"
|
||||||
:namespaced="false"
|
:namespaced="false"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:extra-columns="extraColumns"
|
|
||||||
/>
|
/>
|
||||||
<ResourceTabs v-model="value" :mode="mode">
|
<ResourceTabs v-model="value" :mode="mode">
|
||||||
<Tab name="taints" :label="t('node.detail.tab.taints')" :weight="0">
|
<Tab name="taints" :label="t('node.detail.tab.taints')" :weight="0">
|
||||||
|
|
|
||||||
222
list/node.vue
222
list/node.vue
|
|
@ -1,17 +1,27 @@
|
||||||
<script>
|
<script>
|
||||||
import ResourceTable from '@/components/ResourceTable';
|
import ResourceTable from '@/components/ResourceTable';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import Tag from '@/components/Tag';
|
||||||
import {
|
import {
|
||||||
STATE, NAME, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM
|
STATE, NAME, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM, PODS, AGE
|
||||||
} from '@/config/table-headers';
|
} from '@/config/table-headers';
|
||||||
import metricPoller from '@/mixins/metric-poller';
|
import metricPoller from '@/mixins/metric-poller';
|
||||||
|
|
||||||
import { CAPI, METRIC, NODE } from '@/config/types';
|
import {
|
||||||
|
MANAGEMENT, METRIC, NODE, NORMAN, POD
|
||||||
|
} from '@/config/types';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { allHash } from '@/utils/promise';
|
||||||
|
import { get } from '@/utils/object';
|
||||||
|
import { GROUP_RESOURCES, mapPref } from '@/store/prefs';
|
||||||
|
import { COLUMN_BREAKPOINTS } from '@/components/SortableTable/index.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListNode',
|
name: 'ListNode',
|
||||||
components: { Loading, ResourceTable },
|
components: {
|
||||||
mixins: [metricPoller],
|
Loading, ResourceTable, Tag
|
||||||
|
},
|
||||||
|
mixins: [metricPoller],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
schema: {
|
schema: {
|
||||||
|
|
@ -21,21 +31,98 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.rows = await this.$store.dispatch('cluster/findAll', { type: NODE });
|
const hash = { kubeNodes: this.$store.dispatch('cluster/findAll', { type: NODE }) };
|
||||||
|
|
||||||
if ( this.$store.getters['management/schemaFor'](CAPI.MACHINE) ) {
|
const canViewNodePools = this.$store.getters[`management/schemaFor`](MANAGEMENT.NODE_POOL);
|
||||||
await this.$store.dispatch('management/findAll', { type: CAPI.MACHINE });
|
const canViewNodeTemplates = this.$store.getters[`management/schemaFor`](MANAGEMENT.NODE_TEMPLATE);
|
||||||
|
const canViewPods = this.$store.getters[`cluster/schemaFor`](POD);
|
||||||
|
|
||||||
|
const canViewNormanNodes = this.$store.getters[`rancher/schemaFor`](NORMAN.NODE);
|
||||||
|
|
||||||
|
if (canViewNormanNodes) {
|
||||||
|
// Required for Drain action
|
||||||
|
hash.normanNodes = this.$store.dispatch('rancher/findAll', { type: NORMAN.NODE });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canViewNodePools && canViewNodeTemplates) {
|
||||||
|
// Managemnet Node's required for kube role and some reousrce states
|
||||||
|
hash.mgmtNodes = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
|
||||||
|
hash.nodePools = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_POOL });
|
||||||
|
hash.nodeTemplates = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_TEMPLATE });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canViewPods) {
|
||||||
|
// Used for running pods metrics
|
||||||
|
hash.pods = this.$store.dispatch('cluster/findAll', { type: POD });
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await allHash(hash);
|
||||||
|
|
||||||
|
this.kubeNodes = res.kubeNodes;
|
||||||
|
this.nodePools = res.nodePools || [];
|
||||||
|
this.nodeTemplates = res.nodeTemplates || [];
|
||||||
|
|
||||||
|
await this.updateNodePools(res.kubeNodes);
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
rows: null,
|
kubeNodes: null,
|
||||||
headers: [STATE, NAME, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM],
|
nodeTemplates: null,
|
||||||
|
nodePools: null,
|
||||||
|
headers: [STATE, NAME, ROLES, VERSION, INTERNAL_EXTERNAL_IP, {
|
||||||
|
...CPU,
|
||||||
|
breakpoint: COLUMN_BREAKPOINTS.LAPTOP
|
||||||
|
}, {
|
||||||
|
...RAM,
|
||||||
|
breakpoint: COLUMN_BREAKPOINTS.LAPTOP
|
||||||
|
}, {
|
||||||
|
...PODS,
|
||||||
|
breakpoint: COLUMN_BREAKPOINTS.DESKTOP
|
||||||
|
}, AGE],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
|
...mapGetters(['currentCluster']),
|
||||||
|
tableGroup: mapPref(GROUP_RESOURCES),
|
||||||
|
|
||||||
|
clusterNodePools() {
|
||||||
|
return this.nodePools?.filter(pool => pool?.spec?.clusterName === this.currentCluster.id) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
clusterNodePoolsMap() {
|
||||||
|
return this.clusterNodePools.reduce((res, node) => {
|
||||||
|
res[node.id] = node;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
|
|
||||||
|
hasPools() {
|
||||||
|
return !!this.clusterNodePools.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
groupBy() {
|
||||||
|
if (!this.hasPools) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tableGroup === 'none' ? '' : 'nodePoolId';
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
kubeNodes: {
|
||||||
|
deep: true,
|
||||||
|
handler(neu, old) {
|
||||||
|
this.updateNodePools(neu);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
async loadMetrics() {
|
async loadMetrics() {
|
||||||
const schema = this.$store.getters['cluster/schemaFor'](METRIC.NODE);
|
const schema = this.$store.getters['cluster/schemaFor'](METRIC.NODE);
|
||||||
|
|
||||||
|
|
@ -47,7 +134,30 @@ export default {
|
||||||
|
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
updateNodePools(nodes = []) {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const sNode = node.managementNode;
|
||||||
|
|
||||||
|
if (sNode) {
|
||||||
|
node.nodePoolId = sNode.spec.nodePoolName?.replace(':', '/') || '' ;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getNodePoolFromTableGroup(group) {
|
||||||
|
return this.getNodePool(group.key);
|
||||||
|
},
|
||||||
|
|
||||||
|
getNodeTemplate(nodeTemplateName) {
|
||||||
|
const parsedName = nodeTemplateName.replace(':', '/');
|
||||||
|
|
||||||
|
return this.nodeTemplates.find(nt => nt.id === parsedName);
|
||||||
|
},
|
||||||
|
|
||||||
|
get,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
@ -60,9 +170,95 @@ export default {
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:rows="[...rows]"
|
:rows="[...kubeNodes]"
|
||||||
key-field="_key"
|
:groupable="hasPools"
|
||||||
|
:group-by="groupBy"
|
||||||
|
group-tooltip="node.list.pool"
|
||||||
|
:sub-rows="true"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
>
|
>
|
||||||
|
<template #group-by="{group}">
|
||||||
|
<div class="pool-row" :class="{'has-description':clusterNodePoolsMap[group.key] && clusterNodePoolsMap[group.key].nodeTemplate}">
|
||||||
|
<div v-trim-whitespace class="group-tab">
|
||||||
|
<div v-if="clusterNodePoolsMap[group.key]" class="project-name" v-html="t('resourceTable.groupLabel.nodePool', { name: clusterNodePoolsMap[group.key].spec.hostnamePrefix, count: group.rows.length}, true)">
|
||||||
|
</div>
|
||||||
|
<div v-else class="project-name" v-html="t('resourceTable.groupLabel.notInANodePool')">
|
||||||
|
</div>
|
||||||
|
<div v-if="clusterNodePoolsMap[group.key] && clusterNodePoolsMap[group.key].nodeTemplate" class="description text-muted text-small">
|
||||||
|
{{ clusterNodePoolsMap[group.key].providerDisplay }} – {{ clusterNodePoolsMap[group.key].providerLocation }} / {{ clusterNodePoolsMap[group.key].providerSize }} ({{ clusterNodePoolsMap[group.key].providerName }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #sub-row="{fullColspan, row}">
|
||||||
|
<tr class="taints sub-row" :class="{'empty-taints': !row.spec.taints || !row.spec.taints.length}">
|
||||||
|
<template v-if="row.spec.taints && row.spec.taints.length">
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td :colspan="fullColspan-2">
|
||||||
|
{{ t('node.list.nodeTaint') }}:
|
||||||
|
<Tag v-for="taint in row.spec.taints" :key="taint.key + taint.value + taint.effect" class="mr-5">
|
||||||
|
{{ taint.key }}={{ taint.value }}:{{ taint.effect }}
|
||||||
|
</Tag>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
<td v-else :colspan="fullColspan">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.pool-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-description {
|
||||||
|
.right {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.group-tab {
|
||||||
|
&, &::after {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BUTTON {
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.taints {
|
||||||
|
td {
|
||||||
|
padding-top:0;
|
||||||
|
.tag {
|
||||||
|
margin-right: 5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.empty-taints {
|
||||||
|
// No taints... so hide sub-row (but not bottom-border)
|
||||||
|
height: 0;
|
||||||
|
line-height: 0;
|
||||||
|
td {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import Vue from 'vue';
|
|
||||||
import { formatPercent } from '@/utils/string';
|
import { formatPercent } from '@/utils/string';
|
||||||
import { CAPI as CAPI_ANNOTATIONS, NODE_ROLES, RKE } from '@/config/labels-annotations.js';
|
import { CAPI as CAPI_ANNOTATIONS, NODE_ROLES, RKE } from '@/config/labels-annotations.js';
|
||||||
import { CAPI, METRIC, POD } from '@/config/types';
|
import {
|
||||||
|
CAPI, MANAGEMENT, METRIC, NORMAN, POD
|
||||||
|
} from '@/config/types';
|
||||||
import { parseSi } from '@/utils/units';
|
import { parseSi } from '@/utils/units';
|
||||||
import { PRIVATE } from '@/plugins/steve/resource-proxy';
|
import { PRIVATE } from '@/plugins/steve/resource-proxy';
|
||||||
import findLast from 'lodash/findLast';
|
import findLast from 'lodash/findLast';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
_availableActions() {
|
_availableActions() {
|
||||||
|
const normanAction = this.normanNode?.actions || {};
|
||||||
|
|
||||||
const cordon = {
|
const cordon = {
|
||||||
action: 'cordon',
|
action: 'cordon',
|
||||||
enabled: this.hasLink('update') && this.isWorker && !this.isCordoned,
|
enabled: !!normanAction.cordon,
|
||||||
icon: 'icon icon-fw icon-pause',
|
icon: 'icon icon-fw icon-pause',
|
||||||
label: 'Cordon',
|
label: 'Cordon',
|
||||||
total: 1,
|
total: 1,
|
||||||
|
|
@ -19,13 +22,30 @@ export default {
|
||||||
|
|
||||||
const uncordon = {
|
const uncordon = {
|
||||||
action: 'uncordon',
|
action: 'uncordon',
|
||||||
enabled: this.hasLink('update') && this.isWorker && this.isCordoned,
|
enabled: !!normanAction.uncordon,
|
||||||
icon: 'icon icon-fw icon-play',
|
icon: 'icon icon-fw icon-play',
|
||||||
label: 'Uncordon',
|
label: 'Uncordon',
|
||||||
total: 1,
|
total: 1,
|
||||||
bulkable: true
|
bulkable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drain = {
|
||||||
|
action: 'drain',
|
||||||
|
enabled: !!normanAction.drain,
|
||||||
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
|
label: this.t('drainNode.action'),
|
||||||
|
bulkable: true,
|
||||||
|
bulkAction: 'drain'
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrain = {
|
||||||
|
action: 'stopDrain',
|
||||||
|
enabled: !!normanAction.stopDrain,
|
||||||
|
icon: 'icon icon-fw icon-x',
|
||||||
|
label: this.t('drainNode.actionStop'),
|
||||||
|
bulkable: true,
|
||||||
|
};
|
||||||
|
|
||||||
const openSsh = {
|
const openSsh = {
|
||||||
action: 'openSsh',
|
action: 'openSsh',
|
||||||
enabled: !!this.provisionedMachine?.links?.shell,
|
enabled: !!this.provisionedMachine?.links?.shell,
|
||||||
|
|
@ -46,6 +66,8 @@ export default {
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
cordon,
|
cordon,
|
||||||
uncordon,
|
uncordon,
|
||||||
|
drain,
|
||||||
|
stopDrain,
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
...this._standardActions
|
...this._standardActions
|
||||||
];
|
];
|
||||||
|
|
@ -90,11 +112,13 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
isWorker() {
|
isWorker() {
|
||||||
return `${ this.labels[NODE_ROLES.WORKER] }` === 'true';
|
return this.managementNode ? this.managementNode.isWorker : `${ this.labels[NODE_ROLES.WORKER] }` === 'true';
|
||||||
},
|
},
|
||||||
|
|
||||||
isControlPlane() {
|
isControlPlane() {
|
||||||
if (
|
if (this.managementNode) {
|
||||||
|
return this.managementNode.isControlPlane;
|
||||||
|
} else if (
|
||||||
`${ this.labels[NODE_ROLES.CONTROL_PLANE] }` === 'true' ||
|
`${ this.labels[NODE_ROLES.CONTROL_PLANE] }` === 'true' ||
|
||||||
`${ this.labels[NODE_ROLES.CONTROL_PLANE_OLD] }` === 'true'
|
`${ this.labels[NODE_ROLES.CONTROL_PLANE_OLD] }` === 'true'
|
||||||
) {
|
) {
|
||||||
|
|
@ -105,9 +129,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
isEtcd() {
|
isEtcd() {
|
||||||
const { ETCD: etcd } = NODE_ROLES;
|
return this.managementNode ? this.managementNode.isEtcd : `${ this.labels[NODE_ROLES.ETCD] }` === 'true';
|
||||||
|
|
||||||
return `${ this.labels[etcd] }` === 'true';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hasARole() {
|
hasARole() {
|
||||||
|
|
@ -170,7 +192,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
cpuUsagePercentage() {
|
cpuUsagePercentage() {
|
||||||
return ((this.cpuUsage * 10000) / this.cpuCapacity).toString();
|
return ((this.cpuUsage * 100) / this.cpuCapacity).toString();
|
||||||
},
|
},
|
||||||
|
|
||||||
ramUsage() {
|
ramUsage() {
|
||||||
|
|
@ -182,13 +204,17 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
ramUsagePercentage() {
|
ramUsagePercentage() {
|
||||||
return ((this.ramUsage * 10000) / this.ramCapacity).toString();
|
return ((this.ramUsage * 100) / this.ramCapacity).toString();
|
||||||
},
|
},
|
||||||
|
|
||||||
podUsage() {
|
podUsage() {
|
||||||
return calculatePercentage(this.status.allocatable.pods, this.status.capacity.pods);
|
return calculatePercentage(this.status.allocatable.pods, this.status.capacity.pods);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
podConsumedUsage() {
|
||||||
|
return ((this.podConsumed / this.podCapacity) * 100).toString();
|
||||||
|
},
|
||||||
|
|
||||||
podCapacity() {
|
podCapacity() {
|
||||||
return Number.parseInt(this.status.capacity.pods);
|
return Number.parseInt(this.status.capacity.pods);
|
||||||
},
|
},
|
||||||
|
|
@ -217,6 +243,21 @@ export default {
|
||||||
return !!this.spec.unschedulable;
|
return !!this.spec.unschedulable;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
drainedState() {
|
||||||
|
const sNodeCondition = this.managementNode?.status.conditions.find(c => c.type === 'Drained');
|
||||||
|
|
||||||
|
if (sNodeCondition) {
|
||||||
|
if (sNodeCondition.status === 'True') {
|
||||||
|
return 'drained';
|
||||||
|
}
|
||||||
|
if (sNodeCondition.transitioning) {
|
||||||
|
return 'draining';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
containerRuntimeVersion() {
|
containerRuntimeVersion() {
|
||||||
return this.status.nodeInfo.containerRuntimeVersion.replace('docker://', '');
|
return this.status.nodeInfo.containerRuntimeVersion.replace('docker://', '');
|
||||||
},
|
},
|
||||||
|
|
@ -230,20 +271,71 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
cordon() {
|
cordon() {
|
||||||
return async() => {
|
return async(resources) => {
|
||||||
Vue.set(this.spec, 'unschedulable', true);
|
const safeResources = Array.isArray(resources) ? resources : [this];
|
||||||
await this.save();
|
|
||||||
|
await Promise.all(safeResources.map((node) => {
|
||||||
|
return node.normanNode.doAction('cordon');
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
uncordon() {
|
uncordon() {
|
||||||
return async() => {
|
return async(resources) => {
|
||||||
Vue.set(this.spec, 'unschedulable', false);
|
const safeResources = Array.isArray(resources) ? resources : [this];
|
||||||
await this.save();
|
|
||||||
|
await Promise.all(safeResources.map((node) => {
|
||||||
|
return node.normanNode.doAction('uncordon');
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
clusterId() {
|
||||||
|
const parts = this.links.self.split('/');
|
||||||
|
|
||||||
|
return parts[parts.length - 4];
|
||||||
|
},
|
||||||
|
|
||||||
|
normanNodeId() {
|
||||||
|
const managementNode = (this.$rootGetters['management/all'](MANAGEMENT.NODE) || []).find((n) => {
|
||||||
|
return n.id.startsWith(this.clusterId) && n.status.nodeName === this.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (managementNode) {
|
||||||
|
return managementNode.id.replace('/', ':');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
normanNode() {
|
||||||
|
return this.$rootGetters['rancher/byId'](NORMAN.NODE, this.normanNodeId);
|
||||||
|
},
|
||||||
|
|
||||||
|
managementNode() {
|
||||||
|
return this.$rootGetters['management/all'](MANAGEMENT.NODE).find((mNode) => {
|
||||||
|
return mNode.id.startsWith(this.clusterId) && mNode.status.nodeName === this.id;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
drain() {
|
||||||
|
return (resources) => {
|
||||||
|
this.$dispatch('promptModal', { component: 'DrainNode', resources: [resources || [this], this.normanNodeId] });
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
stopDrain() {
|
||||||
|
return async(resources) => {
|
||||||
|
const safeResources = Array.isArray(resources) ? resources : [this];
|
||||||
|
|
||||||
|
await Promise.all(safeResources.map((node) => {
|
||||||
|
return node.normanNode.doAction('stopDrain');
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
|
if (this.drainedState) {
|
||||||
|
return this.drainedState;
|
||||||
|
}
|
||||||
if ( !this[PRIVATE].isDetailPage && this.isCordoned ) {
|
if ( !this[PRIVATE].isDetailPage && this.isCordoned ) {
|
||||||
return 'cordoned';
|
return 'cordoned';
|
||||||
}
|
}
|
||||||
|
|
@ -310,6 +402,7 @@ export default {
|
||||||
return this.$rootGetters['management/byId'](CAPI.MACHINE, `${ namespace }/${ name }`);
|
return this.$rootGetters['management/byId'](CAPI.MACHINE, `${ namespace }/${ name }`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function calculatePercentage(allocatable, capacity) {
|
function calculatePercentage(allocatable, capacity) {
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isWorker() {
|
||||||
|
return this.spec.worker;
|
||||||
|
},
|
||||||
|
|
||||||
|
isControlPlane() {
|
||||||
|
return this.spec.controlPlane;
|
||||||
|
},
|
||||||
|
|
||||||
|
isEtcd() {
|
||||||
|
return this.spec.etcd;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -12,7 +12,20 @@ export default {
|
||||||
return this.nodeTemplate?.provider;
|
return this.nodeTemplate?.provider;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
providerName() {
|
||||||
|
return this.nodeTemplate?.nameDisplay;
|
||||||
|
},
|
||||||
|
|
||||||
providerDisplay() {
|
providerDisplay() {
|
||||||
return this.nodeTemplate?.providerDisplay;
|
return this.nodeTemplate?.providerDisplay;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
providerLocation() {
|
||||||
|
return this.nodeTemplate?.providerLocation;
|
||||||
|
},
|
||||||
|
|
||||||
|
providerSize() {
|
||||||
|
return this.nodeTemplate?.providerSize;
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,78 @@
|
||||||
|
import { formatSi } from '@/utils/units';
|
||||||
|
|
||||||
|
const CONFIG_KEYS = [
|
||||||
|
{
|
||||||
|
driver: 'aliyunecs',
|
||||||
|
size: { key: 'instanceType' },
|
||||||
|
location: {
|
||||||
|
getDisplayProperty(that) {
|
||||||
|
return `${ that.providerConfig.region }${ that.providerConfig.zone }`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'amazonec2',
|
||||||
|
size: { key: 'instanceType' },
|
||||||
|
location: {
|
||||||
|
getDisplayProperty(that) {
|
||||||
|
return `${ that.providerConfig.region }${ that.providerConfig.zone }`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'azure',
|
||||||
|
size: { key: 'size' },
|
||||||
|
location: { key: 'location' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'digitalocean',
|
||||||
|
size: { key: 'size' },
|
||||||
|
location: { key: 'region' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'exoscale',
|
||||||
|
size: { key: 'instanceProfile' },
|
||||||
|
location: { key: 'availabilityZone' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'linode',
|
||||||
|
size: { key: 'instanceType' },
|
||||||
|
location: { key: 'region' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'oci',
|
||||||
|
size: { key: 'nodeShape' },
|
||||||
|
location: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'packet',
|
||||||
|
size: { key: 'plan' },
|
||||||
|
location: { key: 'facilityCode' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'pnap',
|
||||||
|
size: { key: 'serverType' },
|
||||||
|
location: { key: 'serverLocation' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'rackspace',
|
||||||
|
size: { key: 'flavorId' },
|
||||||
|
location: { key: 'region' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
driver: 'vmwarevsphere',
|
||||||
|
size: {
|
||||||
|
getDisplayProperty(that) {
|
||||||
|
const size = formatSi(that.providerConfig.memorySize * 1048576, 1024, 'iB');
|
||||||
|
|
||||||
|
return `${ size }, ${ that.providerConfig.cpuCount } Core`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
location: { key: null }
|
||||||
|
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
provider() {
|
provider() {
|
||||||
const allKeys = Object.keys(this);
|
const allKeys = Object.keys(this);
|
||||||
|
|
@ -8,9 +83,51 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
providerConfig() {
|
||||||
|
return this[`${ this.provider }Config`];
|
||||||
|
},
|
||||||
|
|
||||||
providerDisplay() {
|
providerDisplay() {
|
||||||
const provider = (this.provider || '').toLowerCase();
|
const provider = (this.provider || '').toLowerCase();
|
||||||
|
|
||||||
return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provider }"`, null, 'generic.unknown', true);
|
return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provider }"`, null, 'generic.unknown', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
providerLocation() {
|
||||||
|
if (this.provider) {
|
||||||
|
const config = CONFIG_KEYS.find(k => k.driver === this.provider);
|
||||||
|
|
||||||
|
if (config?.location) {
|
||||||
|
if (config.location.getDisplayProperty) {
|
||||||
|
return config.location.getDisplayProperty(this);
|
||||||
|
}
|
||||||
|
const value = this.providerConfig[config.location.key];
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.providerConfig?.region || this.t('node.list.poolDescription.noLocation');
|
||||||
|
},
|
||||||
|
|
||||||
|
providerSize() {
|
||||||
|
if (this.provider) {
|
||||||
|
const config = CONFIG_KEYS.find(k => k.driver === this.provider);
|
||||||
|
|
||||||
|
if (config?.size) {
|
||||||
|
if (config.size.getDisplayProperty) {
|
||||||
|
return config.size.getDisplayProperty(this);
|
||||||
|
}
|
||||||
|
const value = this.providerConfig[config.size.key];
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.providerConfig?.size || this.t('node.list.poolDescription.noSize');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -175,8 +175,7 @@ export default {
|
||||||
/>
|
/>
|
||||||
<ResourceTable
|
<ResourceTable
|
||||||
ref="table"
|
ref="table"
|
||||||
class="
|
class="table"
|
||||||
table"
|
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
|
|
@ -224,35 +223,35 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.project-namespaces {
|
.project-namespaces {
|
||||||
& ::v-deep {
|
& ::v-deep {
|
||||||
.project-name {
|
.project-name {
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
|
||||||
|
|
||||||
.project-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
&.has-description {
|
|
||||||
.right {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
.group-tab {
|
|
||||||
&, &::after {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
right: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: -20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.has-description {
|
||||||
|
.right {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.group-tab {
|
||||||
|
&, &::after {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -372,7 +372,7 @@ export default {
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
<template #col:pods="{row}">
|
<template #col:pods="{row}">
|
||||||
<td v-if="row.status.allocatable.pods && row.status.allocatable.pods!== '0'">
|
<td v-if="row.status.allocatable && row.status.allocatable.pods!== '0'">
|
||||||
{{ `${row.status.requested.pods}/${row.status.allocatable.pods}` }}
|
{{ `${row.status.requested.pods}/${row.status.allocatable.pods}` }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,7 @@ import { normalizeType } from './normalize';
|
||||||
|
|
||||||
const cache = {};
|
const cache = {};
|
||||||
|
|
||||||
/**
|
function find(cache, type, appSpecializationName) {
|
||||||
* This will lookup and load a model based on the type and appSpecializationName if specified.
|
|
||||||
*
|
|
||||||
* We want to have the ability to treat chart apps as if they were native resources.
|
|
||||||
* As part of this desire to treat apps as a native resource we also want to be able to customize their models.
|
|
||||||
* If we attempt to load an 'app' type with an 'appSpecializationName' we will first
|
|
||||||
* load the 'app' type and then merge that with a model found in '@/models/apps/${appSpecializationName}'
|
|
||||||
* if the file exists.
|
|
||||||
* @param {*} type the type we'd like to lookup
|
|
||||||
* @param {*} appSpecializationName the name of the app so we can lookup a model with the given name and merge that with the app base type.
|
|
||||||
*/
|
|
||||||
export function lookup(type, appSpecializationName) {
|
|
||||||
type = normalizeType(type).replace(/\//g, '');
|
|
||||||
|
|
||||||
const impl = cache[type];
|
const impl = cache[type];
|
||||||
|
|
||||||
if ( impl ) {
|
if ( impl ) {
|
||||||
|
|
@ -47,3 +34,21 @@ export function lookup(type, appSpecializationName) {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will lookup and load a model based on the type and appSpecializationName if specified.
|
||||||
|
*
|
||||||
|
* We want to have the ability to treat chart apps as if they were native resources.
|
||||||
|
* As part of this desire to treat apps as a native resource we also want to be able to customize their models.
|
||||||
|
* If we attempt to load an 'app' type with an 'appSpecializationName' we will first
|
||||||
|
* load the 'app' type and then merge that with a model found in '@/models/apps/${appSpecializationName}'
|
||||||
|
* if the file exists.
|
||||||
|
* @param {*} store the name of the store that the type comes from
|
||||||
|
* @param {*} type the type we'd like to lookup
|
||||||
|
* @param {*} appSpecializationName the name of the app so we can lookup a model with the given name and merge that with the app base type.
|
||||||
|
*/
|
||||||
|
export function lookup(store, type, appSpecializationName) {
|
||||||
|
type = normalizeType(type).replace(/\//g, '');
|
||||||
|
|
||||||
|
return find(cache, `${ store }/${ type }`, appSpecializationName) || find(cache, type, appSpecializationName) || null;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@ export const STATES = {
|
||||||
deployed: { color: 'success', icon: 'dot-open' },
|
deployed: { color: 'success', icon: 'dot-open' },
|
||||||
disabled: { color: 'warning', icon: 'error' },
|
disabled: { color: 'warning', icon: 'error' },
|
||||||
disconnected: { color: 'warning', icon: 'error' },
|
disconnected: { color: 'warning', icon: 'error' },
|
||||||
|
drained: { color: 'info', icon: 'tag' },
|
||||||
|
draining: { color: 'warning', icon: 'tag' },
|
||||||
errapplied: { color: 'error', icon: 'error' },
|
errapplied: { color: 'error', icon: 'error' },
|
||||||
error: { color: 'error', icon: 'error' },
|
error: { color: 'error', icon: 'error' },
|
||||||
erroring: { color: 'error', icon: 'error' },
|
erroring: { color: 'error', icon: 'error' },
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,8 @@ export function proxyFor(ctx, obj, isClone = false) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedType = ctx.rootGetters['type-map/componentFor'](obj.type);
|
const mappedType = ctx.rootGetters['type-map/componentFor'](obj.type);
|
||||||
const customModel = lookup(mappedType, obj?.metadata?.name);
|
const customModel = lookup(ctx.state.config.namespace, mappedType, obj?.metadata?.name);
|
||||||
const model = customModel || ResourceInstance;
|
const model = customModel || ResourceInstance;
|
||||||
|
|
||||||
remapSpecialKeys(obj);
|
remapSpecialKeys(obj);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue