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-*')) {
|
||||
// }
|
||||
$breakpoints: (
|
||||
'--viewport-4': 480px,
|
||||
'--viewport-7': 768px,
|
||||
'--viewport-9': 992px,
|
||||
'--viewport-12': 1281px,
|
||||
'--viewport-4': 480px, // Phone
|
||||
'--viewport-7': 768px, // Tablet
|
||||
'--viewport-9': 992px, // Laptop/Desktop
|
||||
'--viewport-12': 1281px, // Desktop
|
||||
);
|
||||
|
|
|
|||
|
|
@ -475,6 +475,10 @@ asyncButton:
|
|||
action: Download
|
||||
success: Saving
|
||||
waiting: Downloading…
|
||||
drain:
|
||||
action: Drain
|
||||
success: Drained
|
||||
waiting: Draining…
|
||||
edit:
|
||||
action: Save
|
||||
success: Saved
|
||||
|
|
@ -1136,6 +1140,7 @@ cluster:
|
|||
cluster: Cluster Configuration
|
||||
etcd: etcd
|
||||
networking: Networking
|
||||
nodePools: Node Pools
|
||||
machinePools: Machine Pools
|
||||
registry: Private Registry
|
||||
upgrade: Upgrade Strategy
|
||||
|
|
@ -1145,6 +1150,9 @@ cluster:
|
|||
services: Services
|
||||
allServices: Rotate all service certificates
|
||||
selectService: Rotate an individual service
|
||||
nodePool:
|
||||
scaleDown: Scale Pool Down
|
||||
scaleUp: Scale Pool Up
|
||||
|
||||
clusterIndexPage:
|
||||
hardwareResourceGauge:
|
||||
|
|
@ -1223,6 +1231,27 @@ detailText:
|
|||
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:
|
||||
hasLeader: "Etcd has a leader:"
|
||||
leaderChanges: "Number of leader changes:"
|
||||
|
|
@ -2110,6 +2139,12 @@ namespaceList:
|
|||
addLabel: Add Namespace
|
||||
|
||||
node:
|
||||
list:
|
||||
pool: Pool
|
||||
nodeTaint: Taints
|
||||
poolDescription:
|
||||
noSize: No Size
|
||||
noLocation: No Location
|
||||
detail:
|
||||
detailTop:
|
||||
containerRuntime: Container Runtime
|
||||
|
|
@ -2888,6 +2923,8 @@ resourceTable:
|
|||
project: "<span>Project:</span> {name}"
|
||||
notInAWorkspace: Not in a Workspace
|
||||
workspace: "<span>Workspace:</span> {name}"
|
||||
notInANodePool: "Not in a Pool"
|
||||
nodePool: "<span>Node Pool:</span> {name} ({count})"
|
||||
|
||||
resourceTabs:
|
||||
conditions:
|
||||
|
|
@ -3542,6 +3579,7 @@ tableHeaders:
|
|||
subType: Kind
|
||||
success: Success
|
||||
summary: Summary
|
||||
taints: Taints
|
||||
target: Target
|
||||
targetKind: Target Type
|
||||
targetPort: Target
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export default {
|
|||
:key="col.name"
|
||||
:align="col.align || 'left'"
|
||||
:width="col.width"
|
||||
:class="{ sortable: col.sort }"
|
||||
:class="{ sortable: col.sort, [col.breakpoint]: !!col.breakpoint}"
|
||||
@click.prevent="changeSort($event, col)"
|
||||
>
|
||||
<span v-if="col.sort" @click="$router.applyQuery(queryFor(col))">
|
||||
|
|
@ -180,6 +180,26 @@ export default {
|
|||
& A {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ import sorting from './sorting';
|
|||
import paging from './paging';
|
||||
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:
|
||||
// Fixed header/scrolling
|
||||
|
||||
|
|
@ -598,7 +613,7 @@ export default {
|
|||
:key="col.name"
|
||||
:data-title="labelFor(col)"
|
||||
: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"
|
||||
>
|
||||
<slot :name="'cell:' + col.name" :row="row" :col="col" :value="valueFor(row,col)">
|
||||
|
|
@ -708,6 +723,26 @@ export default {
|
|||
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>
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default {
|
|||
onRowMouseEnter(e) {
|
||||
const tr = $(e.target).closest('TR');
|
||||
|
||||
if (tr.hasClass('state-description')) {
|
||||
if (tr.hasClass('sub-row')) {
|
||||
const trMainRow = tr.prev('TR');
|
||||
|
||||
trMainRow.toggleClass('sub-row-hovered', true);
|
||||
|
|
@ -121,7 +121,7 @@ export default {
|
|||
onRowMouseLeave(e) {
|
||||
const tr = $(e.target).closest('TR');
|
||||
|
||||
if (tr.hasClass('state-description')) {
|
||||
if (tr.hasClass('sub-row')) {
|
||||
const trMainRow = tr.prev('TR');
|
||||
|
||||
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>
|
||||
<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">
|
||||
<h3>
|
||||
<t v-if="labelKey" :k="labelKey" />
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
percentage() {
|
||||
return Number.parseFloat(this.value) / 100;
|
||||
return Number.parseFloat(this.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
|
||||
import {
|
||||
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,
|
||||
USER_ID, USERNAME, USER_DISPLAY_NAME, USER_PROVIDER, WORKLOAD_ENDPOINTS, STORAGE_CLASS_DEFAULT,
|
||||
STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE,
|
||||
|
|
@ -167,7 +167,6 @@ export function init(store) {
|
|||
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(HPA, [STATE, NAME_COL, HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA, AGE]);
|
||||
|
||||
|
|
|
|||
|
|
@ -198,8 +198,10 @@ export const PODS = {
|
|||
name: 'pods',
|
||||
labelKey: 'tableHeaders.pods',
|
||||
sort: 'pods',
|
||||
value: 'podUsage',
|
||||
formatter: 'PercentageBar'
|
||||
search: false,
|
||||
value: 'podConsumedUsage',
|
||||
formatter: 'PercentageBar',
|
||||
width: 120,
|
||||
};
|
||||
|
||||
export const AGE = {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ export const NORMAN = {
|
|||
CLUSTER_TOKEN: 'clusterregistrationtoken',
|
||||
CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterRoleTemplateBinding',
|
||||
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',
|
||||
PROJECT: 'project',
|
||||
PROJECT_ROLE_TEMPLATE_BINDING: 'projectRoleTemplateBinding',
|
||||
|
|
@ -137,6 +139,7 @@ export const MANAGEMENT = {
|
|||
FEATURE: 'management.cattle.io.feature',
|
||||
GROUP: 'management.cattle.io.group',
|
||||
KONTANIER_DRIVER: 'management.cattle.io.kontainerdriver',
|
||||
NODE: 'management.cattle.io.node',
|
||||
NODE_DRIVER: 'management.cattle.io.nodedriver',
|
||||
NODE_POOL: 'management.cattle.io.nodepool',
|
||||
NODE_TEMPLATE: 'management.cattle.io.nodetemplate',
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ export default {
|
|||
:value="value"
|
||||
:namespaced="false"
|
||||
:mode="mode"
|
||||
:extra-columns="extraColumns"
|
||||
/>
|
||||
<ResourceTabs v-model="value" :mode="mode">
|
||||
<Tab name="taints" :label="t('node.detail.tab.taints')" :weight="0">
|
||||
|
|
|
|||
222
list/node.vue
222
list/node.vue
|
|
@ -1,17 +1,27 @@
|
|||
<script>
|
||||
import ResourceTable from '@/components/ResourceTable';
|
||||
import Loading from '@/components/Loading';
|
||||
import Tag from '@/components/Tag';
|
||||
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';
|
||||
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 {
|
||||
name: 'ListNode',
|
||||
components: { Loading, ResourceTable },
|
||||
mixins: [metricPoller],
|
||||
components: {
|
||||
Loading, ResourceTable, Tag
|
||||
},
|
||||
mixins: [metricPoller],
|
||||
|
||||
props: {
|
||||
schema: {
|
||||
|
|
@ -21,21 +31,98 @@ export default {
|
|||
},
|
||||
|
||||
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) ) {
|
||||
await this.$store.dispatch('management/findAll', { type: CAPI.MACHINE });
|
||||
const canViewNodePools = this.$store.getters[`management/schemaFor`](MANAGEMENT.NODE_POOL);
|
||||
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() {
|
||||
return {
|
||||
rows: null,
|
||||
headers: [STATE, NAME, ROLES, VERSION, INTERNAL_EXTERNAL_IP, CPU, RAM],
|
||||
kubeNodes: null,
|
||||
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() {
|
||||
const schema = this.$store.getters['cluster/schemaFor'](METRIC.NODE);
|
||||
|
||||
|
|
@ -47,7 +134,30 @@ export default {
|
|||
|
||||
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"
|
||||
:schema="schema"
|
||||
:headers="headers"
|
||||
:rows="[...rows]"
|
||||
key-field="_key"
|
||||
:rows="[...kubeNodes]"
|
||||
:groupable="hasPools"
|
||||
:group-by="groupBy"
|
||||
group-tooltip="node.list.pool"
|
||||
:sub-rows="true"
|
||||
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>
|
||||
</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 { 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 { PRIVATE } from '@/plugins/steve/resource-proxy';
|
||||
import findLast from 'lodash/findLast';
|
||||
|
||||
export default {
|
||||
_availableActions() {
|
||||
const normanAction = this.normanNode?.actions || {};
|
||||
|
||||
const cordon = {
|
||||
action: 'cordon',
|
||||
enabled: this.hasLink('update') && this.isWorker && !this.isCordoned,
|
||||
enabled: !!normanAction.cordon,
|
||||
icon: 'icon icon-fw icon-pause',
|
||||
label: 'Cordon',
|
||||
total: 1,
|
||||
|
|
@ -19,13 +22,30 @@ export default {
|
|||
|
||||
const uncordon = {
|
||||
action: 'uncordon',
|
||||
enabled: this.hasLink('update') && this.isWorker && this.isCordoned,
|
||||
enabled: !!normanAction.uncordon,
|
||||
icon: 'icon icon-fw icon-play',
|
||||
label: 'Uncordon',
|
||||
total: 1,
|
||||
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 = {
|
||||
action: 'openSsh',
|
||||
enabled: !!this.provisionedMachine?.links?.shell,
|
||||
|
|
@ -46,6 +66,8 @@ export default {
|
|||
{ divider: true },
|
||||
cordon,
|
||||
uncordon,
|
||||
drain,
|
||||
stopDrain,
|
||||
{ divider: true },
|
||||
...this._standardActions
|
||||
];
|
||||
|
|
@ -90,11 +112,13 @@ export default {
|
|||
},
|
||||
|
||||
isWorker() {
|
||||
return `${ this.labels[NODE_ROLES.WORKER] }` === 'true';
|
||||
return this.managementNode ? this.managementNode.isWorker : `${ this.labels[NODE_ROLES.WORKER] }` === 'true';
|
||||
},
|
||||
|
||||
isControlPlane() {
|
||||
if (
|
||||
if (this.managementNode) {
|
||||
return this.managementNode.isControlPlane;
|
||||
} else if (
|
||||
`${ this.labels[NODE_ROLES.CONTROL_PLANE] }` === 'true' ||
|
||||
`${ this.labels[NODE_ROLES.CONTROL_PLANE_OLD] }` === 'true'
|
||||
) {
|
||||
|
|
@ -105,9 +129,7 @@ export default {
|
|||
},
|
||||
|
||||
isEtcd() {
|
||||
const { ETCD: etcd } = NODE_ROLES;
|
||||
|
||||
return `${ this.labels[etcd] }` === 'true';
|
||||
return this.managementNode ? this.managementNode.isEtcd : `${ this.labels[NODE_ROLES.ETCD] }` === 'true';
|
||||
},
|
||||
|
||||
hasARole() {
|
||||
|
|
@ -170,7 +192,7 @@ export default {
|
|||
},
|
||||
|
||||
cpuUsagePercentage() {
|
||||
return ((this.cpuUsage * 10000) / this.cpuCapacity).toString();
|
||||
return ((this.cpuUsage * 100) / this.cpuCapacity).toString();
|
||||
},
|
||||
|
||||
ramUsage() {
|
||||
|
|
@ -182,13 +204,17 @@ export default {
|
|||
},
|
||||
|
||||
ramUsagePercentage() {
|
||||
return ((this.ramUsage * 10000) / this.ramCapacity).toString();
|
||||
return ((this.ramUsage * 100) / this.ramCapacity).toString();
|
||||
},
|
||||
|
||||
podUsage() {
|
||||
return calculatePercentage(this.status.allocatable.pods, this.status.capacity.pods);
|
||||
},
|
||||
|
||||
podConsumedUsage() {
|
||||
return ((this.podConsumed / this.podCapacity) * 100).toString();
|
||||
},
|
||||
|
||||
podCapacity() {
|
||||
return Number.parseInt(this.status.capacity.pods);
|
||||
},
|
||||
|
|
@ -217,6 +243,21 @@ export default {
|
|||
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() {
|
||||
return this.status.nodeInfo.containerRuntimeVersion.replace('docker://', '');
|
||||
},
|
||||
|
|
@ -230,20 +271,71 @@ export default {
|
|||
},
|
||||
|
||||
cordon() {
|
||||
return async() => {
|
||||
Vue.set(this.spec, 'unschedulable', true);
|
||||
await this.save();
|
||||
return async(resources) => {
|
||||
const safeResources = Array.isArray(resources) ? resources : [this];
|
||||
|
||||
await Promise.all(safeResources.map((node) => {
|
||||
return node.normanNode.doAction('cordon');
|
||||
}));
|
||||
};
|
||||
},
|
||||
|
||||
uncordon() {
|
||||
return async() => {
|
||||
Vue.set(this.spec, 'unschedulable', false);
|
||||
await this.save();
|
||||
return async(resources) => {
|
||||
const safeResources = Array.isArray(resources) ? resources : [this];
|
||||
|
||||
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() {
|
||||
if (this.drainedState) {
|
||||
return this.drainedState;
|
||||
}
|
||||
if ( !this[PRIVATE].isDetailPage && this.isCordoned ) {
|
||||
return 'cordoned';
|
||||
}
|
||||
|
|
@ -310,6 +402,7 @@ export default {
|
|||
return this.$rootGetters['management/byId'](CAPI.MACHINE, `${ namespace }/${ name }`);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
providerName() {
|
||||
return this.nodeTemplate?.nameDisplay;
|
||||
},
|
||||
|
||||
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 {
|
||||
provider() {
|
||||
const allKeys = Object.keys(this);
|
||||
|
|
@ -8,9 +83,51 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
providerConfig() {
|
||||
return this[`${ this.provider }Config`];
|
||||
},
|
||||
|
||||
providerDisplay() {
|
||||
const provider = (this.provider || '').toLowerCase();
|
||||
|
||||
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
|
||||
ref="table"
|
||||
class="
|
||||
table"
|
||||
class="table"
|
||||
v-bind="$attrs"
|
||||
:schema="schema"
|
||||
:headers="headers"
|
||||
|
|
@ -224,35 +223,35 @@ export default {
|
|||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.project-namespaces {
|
||||
& ::v-deep {
|
||||
.project-name {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& ::v-deep {
|
||||
.project-name {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ export default {
|
|||
</td>
|
||||
</template>
|
||||
<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}` }}
|
||||
</td>
|
||||
<td v-else>
|
||||
|
|
|
|||
|
|
@ -2,20 +2,7 @@ import { normalizeType } from './normalize';
|
|||
|
||||
const cache = {};
|
||||
|
||||
/**
|
||||
* 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, '');
|
||||
|
||||
function find(cache, type, appSpecializationName) {
|
||||
const impl = cache[type];
|
||||
|
||||
if ( impl ) {
|
||||
|
|
@ -47,3 +34,21 @@ export function lookup(type, appSpecializationName) {
|
|||
|
||||
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' },
|
||||
disabled: { color: 'warning', icon: 'error' },
|
||||
disconnected: { color: 'warning', icon: 'error' },
|
||||
drained: { color: 'info', icon: 'tag' },
|
||||
draining: { color: 'warning', icon: 'tag' },
|
||||
errapplied: { color: 'error', icon: 'error' },
|
||||
error: { 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 customModel = lookup(mappedType, obj?.metadata?.name);
|
||||
const customModel = lookup(ctx.state.config.namespace, mappedType, obj?.metadata?.name);
|
||||
const model = customModel || ResourceInstance;
|
||||
|
||||
remapSpecialKeys(obj);
|
||||
|
|
|
|||
Loading…
Reference in New Issue