mirror of https://github.com/rancher/dashboard.git
Merge pull request #276 from codyrancher/node
Adding the 'node' list and detail view
This commit is contained in:
commit
12b13365bd
|
|
@ -53,7 +53,8 @@ $selected: rgba($primary, .5);
|
|||
success: $success,
|
||||
info: $info,
|
||||
warning: $warning,
|
||||
error: $error
|
||||
error: $error,
|
||||
darker: $darker
|
||||
), true);
|
||||
|
||||
--body-bg : #{$lightest};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<script>
|
||||
import VStack from '@/components/Layout/Stack/VStack';
|
||||
|
||||
const STATUS_CLASS_MAP = {
|
||||
success: {
|
||||
container: 'text-success',
|
||||
icon: 'icon-x'
|
||||
},
|
||||
warning: {
|
||||
container: 'text-warning',
|
||||
icon: 'icon-x'
|
||||
},
|
||||
info: {
|
||||
container: 'text-info',
|
||||
icon: 'icon-x'
|
||||
},
|
||||
error: {
|
||||
container: 'alert-bg-error text-error',
|
||||
icon: 'icon-x'
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { VStack },
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return Object.keys(STATUS_CLASS_MAP).includes(value);
|
||||
},
|
||||
required: true
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerClasses() {
|
||||
return STATUS_CLASS_MAP[this.status].container;
|
||||
},
|
||||
iconClasses() {
|
||||
return STATUS_CLASS_MAP[this.status].icon;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VStack class="alert" :class="containerClasses" vertical-align="center">
|
||||
<div><i class="icon" :class="iconClasses" /> {{ message }}</div>
|
||||
</VStack>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.alert-bg-error {
|
||||
background-color: rgba(var(--error), 0.5)
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
import PercentageCircle from '@/components/PercentageCircle';
|
||||
import VStack from '@/components/Layout/Stack/VStack';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PercentageCircle,
|
||||
VStack
|
||||
},
|
||||
props: {
|
||||
resourceName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
capacity: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
used: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
units: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
numberFormatter: {
|
||||
type: Function,
|
||||
default: value => value
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayUnits() {
|
||||
return this.units
|
||||
? ` ${ this.units }`
|
||||
: '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VStack class="consumption-gauge" :show-dividers="true">
|
||||
<PercentageCircle :value="used / capacity" :lower-error-bound="0.25" :lower-warning-bound="0.25" :upper-warning-bound="0.7" :upper-error-bound="0.85" />
|
||||
<VStack horizontal-align="center">
|
||||
<div>{{ resourceName }}</div>
|
||||
<div class="amount">
|
||||
{{ numberFormatter(used) }} of {{ numberFormatter(capacity) }}{{ displayUnits }} reserved
|
||||
</div>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$divider-spacing: 20px;
|
||||
|
||||
.consumption-gauge {
|
||||
min-height: 300px;
|
||||
width: 100%;
|
||||
padding-right: $divider-spacing;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > :first-child {
|
||||
padding-bottom: $divider-spacing;
|
||||
height: 75%;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
padding-top: $divider-spacing;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: var(--link-text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
clicked(event) {
|
||||
event.preventDefault();
|
||||
this.$copyText(this.text);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a href="#" @click="clicked">
|
||||
{{ text }} <i class="icon icon-copy" />
|
||||
</a>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="detail-top">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-top {
|
||||
display: flex;
|
||||
& > * {
|
||||
padding: 10px 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-right: 2px solid var(--border);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
& >:not(:first-child) {
|
||||
color: var(--input-label);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<script>
|
||||
export default { props: { title: { type: String, default: '' } } };
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<span>{{ title }}</span>
|
||||
<span><slot /></span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import Stack from './index';
|
||||
|
||||
export default {
|
||||
extends: Stack,
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import Stack from './index';
|
||||
|
||||
export default {
|
||||
extends: Stack,
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'vertical'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<template>
|
||||
<div class="stack" :class="classes">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return ['horizontal', 'vertical'].includes(value);
|
||||
},
|
||||
required: true
|
||||
},
|
||||
showDividers: { type: Boolean },
|
||||
horizontalAlign: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return ['left', 'right', 'center', 'space-between', 'space-around', 'space-evenly'].includes(value);
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
verticalAlign: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return ['top', 'bottom', 'center', 'space-between', 'space-around', 'space-evenly'].includes(value);
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
return {
|
||||
dividers: this.showDividers,
|
||||
[this.direction]: true,
|
||||
|
||||
'h-align-left': this.horizontalAlign === 'left',
|
||||
'h-align-right': this.horizontalAlign === 'right',
|
||||
'h-align-center': this.horizontalAlign === 'center',
|
||||
'h-align-space-between': this.horizontalAlign === 'space-between',
|
||||
'h-align-space-around': this.horizontalAlign === 'space-around',
|
||||
'h-align-space-evenly': this.horizontalAlign === 'space-evenly',
|
||||
|
||||
'v-align-top': this.verticalAlign === 'top',
|
||||
'v-align-bottom': this.verticalAlign === 'bottom',
|
||||
'v-align-center': this.verticalAlign === 'center',
|
||||
'v-align-space-between': this.verticalAlign === 'space-between',
|
||||
'v-align-space-around': this.verticalAlign === 'space-around',
|
||||
'v-align-space-evenly': this.verticalAlign === 'space-evenly',
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stack {
|
||||
display: flex;
|
||||
|
||||
&.horizontal {
|
||||
flex-direction: row;
|
||||
|
||||
&.dividers {
|
||||
& > *:not(:last-child) {
|
||||
border-right: 2px solid var(--border)
|
||||
}
|
||||
}
|
||||
|
||||
&.h-align-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.h-align-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&.h-align-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.h-align-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.h-align-space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&.h-align-space-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
&.v-align-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&.v-align-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&.v-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.v-align-space-between {
|
||||
align-items: space-between;
|
||||
}
|
||||
|
||||
&.v-align-space-around {
|
||||
align-items: space-around;
|
||||
}
|
||||
|
||||
&.v-align-space-evenly {
|
||||
align-items: space-evenly;
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
|
||||
&.dividers {
|
||||
& > *:not(:last-child) {
|
||||
border-bottom: 2px solid var(--border)
|
||||
}
|
||||
}
|
||||
|
||||
&.h-align-left {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&.h-align-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&.h-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.h-align-space-between {
|
||||
align-items: space-between;
|
||||
}
|
||||
|
||||
&.h-align-space-around {
|
||||
align-items: space-around;
|
||||
}
|
||||
|
||||
&.h-align-space-evenly {
|
||||
align-items: space-evenly;
|
||||
}
|
||||
|
||||
&.v-align-top {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.v-align-bottom {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&.v-align-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.v-align-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.v-align-space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&.v-align-space-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<script>
|
||||
import VStack from '@/components/Layout/Stack/VStack';
|
||||
|
||||
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
||||
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
|
||||
|
||||
return {
|
||||
x: centerX + (radius * Math.cos(angleInRadians)),
|
||||
y: centerY + (radius * Math.sin(angleInRadians))
|
||||
};
|
||||
}
|
||||
|
||||
function describeArc(x, y, radius, startAngle, endAngle) {
|
||||
const start = polarToCartesian(x, y, radius, endAngle);
|
||||
const end = polarToCartesian(x, y, radius, startAngle);
|
||||
|
||||
const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
|
||||
|
||||
const d = [
|
||||
'M', start.x, start.y,
|
||||
'A', radius, radius, 0, largeArcFlag, 0, end.x, end.y
|
||||
].join(' ');
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'PercentageCircle',
|
||||
|
||||
components: { VStack },
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
validator: (value) => {
|
||||
return value >= 0 && value <= 1;
|
||||
}
|
||||
},
|
||||
lowerWarningBound: { type: Number, default: undefined },
|
||||
upperWarningBound: { type: Number, default: undefined },
|
||||
lowerErrorBound: { type: Number, default: undefined },
|
||||
upperErrorBound: { type: Number, default: undefined }
|
||||
},
|
||||
|
||||
computed: {
|
||||
valueD() {
|
||||
return describeArc(50, 50, 45, 180, 180 + (360 * this.value));
|
||||
},
|
||||
printedValue() {
|
||||
return (this.value * 100)
|
||||
.toFixed(1)
|
||||
.replace(/\.0$/, '');
|
||||
},
|
||||
valueClass() {
|
||||
const errorClass = 'error';
|
||||
const warningClass = 'warning';
|
||||
const successClass = 'success';
|
||||
|
||||
if (this.lowerErrorBound && this.value <= this.lowerErrorBound) {
|
||||
return errorClass;
|
||||
}
|
||||
|
||||
if (this.lowerWarningBound && this.value <= this.lowerWarningBound) {
|
||||
return warningClass;
|
||||
}
|
||||
|
||||
if (this.upperErrorBound && this.value >= this.upperErrorBound) {
|
||||
return errorClass;
|
||||
}
|
||||
|
||||
if (this.upperWarningBound && this.value >= this.upperWarningBound) {
|
||||
return warningClass;
|
||||
}
|
||||
|
||||
return successClass;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VStack class="percentage-circle" horizontal-align="center">
|
||||
<svg viewBox="0 0 100 100" class="gauge">
|
||||
<circle class="dial" fill="none" cx="50" cy="50" r="45" />
|
||||
<path v-if="value < 1" class="value" :class="valueClass" fill="none" :d="valueD" />
|
||||
<circle
|
||||
v-else
|
||||
class="value"
|
||||
:class="valueClass"
|
||||
fill="none"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45"
|
||||
/>
|
||||
</svg>
|
||||
<div class="printed-value mt-10">
|
||||
<h1>{{ printedValue }}%</h1>
|
||||
</div>
|
||||
</VStack>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.percentage-circle {
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
$size: 150px;
|
||||
width: $size;
|
||||
height: $size;
|
||||
|
||||
.dial {
|
||||
stroke: var(--muted);
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
.value {
|
||||
stroke-width: 5.5;
|
||||
stroke-linecap: round;
|
||||
|
||||
&.success {
|
||||
stroke: var(--success);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
stroke: var(--warning);
|
||||
}
|
||||
|
||||
&.error {
|
||||
stroke: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.printed-value {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -31,7 +31,17 @@ export default {
|
|||
showGroups: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
search: {
|
||||
// Show search input to filter rows
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tableActions: {
|
||||
// Show bulk table actions
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
@ -145,8 +155,9 @@ export default {
|
|||
:headers="_headers"
|
||||
:rows="filteredRows"
|
||||
:group-by="groupBy"
|
||||
:search="search"
|
||||
:table-actions="tableActions"
|
||||
key-field="_key"
|
||||
table-actions
|
||||
v-on="$listeners"
|
||||
>
|
||||
<template v-if="groupable && showGroups" #header-middle>
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ export default {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
<style lang='scss' scoped>
|
||||
.flat {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
col: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { NUMBER_OF_TICKS: 10 };
|
||||
},
|
||||
methods: {
|
||||
getTickBackgroundClass(i) {
|
||||
const valuePercentage = Number.parseFloat(this.value) / 100;
|
||||
const barPercentage = i / this.NUMBER_OF_TICKS;
|
||||
|
||||
if (valuePercentage < barPercentage) {
|
||||
return 'bg-darker';
|
||||
}
|
||||
|
||||
if (barPercentage <= 0.6) {
|
||||
return 'bg-success';
|
||||
}
|
||||
|
||||
if (barPercentage <= 0.8) {
|
||||
return 'bg-info';
|
||||
}
|
||||
|
||||
return 'bg-error';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span class="percentage">{{ value }}%</span>
|
||||
<span class="bar">
|
||||
<span v-for="i in NUMBER_OF_TICKS" :key="i" class="tick" :class="getTickBackgroundClass(i)"> </span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
.percentage {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bar {
|
||||
vertical-align: middle;
|
||||
margin-left: 3px;
|
||||
.tick {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
margin-right: 3px;
|
||||
width: 3px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
import { CONFIG_MAP, SECRET, RIO, NAMESPACE } from '@/config/types';
|
||||
import {
|
||||
CONFIG_MAP, SECRET, RIO, NAMESPACE, NODE
|
||||
} from '@/config/types';
|
||||
import {
|
||||
STATE, NAME, NAMESPACE_NAME, NAMESPACE_NAME_IMAGE, AGE,
|
||||
WEIGHT, SCALE,
|
||||
KEYS, ENDPOINTS,
|
||||
MATCHES, DESTINATION,
|
||||
TARGET, TARGET_KIND, USERNAME, USER_DISPLAY_NAME, USER_ID, USER_STATUS,
|
||||
NODE_NAME, ROLES,
|
||||
VERSION, CPU,
|
||||
RAM, PODS
|
||||
} from '@/config/table-headers';
|
||||
import { _CREATE, _CLONE, _STAGE } from '@/config/query-params';
|
||||
|
||||
|
|
@ -39,6 +44,24 @@ export const FRIENDLY = {
|
|||
],
|
||||
},
|
||||
|
||||
nodes: {
|
||||
singular: 'Node',
|
||||
plural: 'Nodes',
|
||||
type: NODE,
|
||||
headers: [
|
||||
STATE,
|
||||
NODE_NAME,
|
||||
ROLES,
|
||||
VERSION,
|
||||
CPU,
|
||||
RAM,
|
||||
PODS
|
||||
],
|
||||
search: false,
|
||||
tableActions: false,
|
||||
hasDetail: true
|
||||
},
|
||||
|
||||
'public-domains': {
|
||||
singular: 'Public Domain',
|
||||
plural: 'Public Domains',
|
||||
|
|
|
|||
|
|
@ -71,6 +71,52 @@ export const NODE = {
|
|||
value: 'spec.nodeName',
|
||||
};
|
||||
|
||||
export const NODE_NAME = {
|
||||
name: 'nodeName',
|
||||
label: 'Name',
|
||||
sort: 'name',
|
||||
value: 'name',
|
||||
formatter: 'LinkDetail',
|
||||
};
|
||||
|
||||
export const ROLES = {
|
||||
name: 'roles',
|
||||
label: 'Roles',
|
||||
sort: 'roles',
|
||||
value: 'roles'
|
||||
};
|
||||
|
||||
export const VERSION = {
|
||||
name: 'version',
|
||||
label: 'Version',
|
||||
sort: 'version',
|
||||
value: 'version'
|
||||
};
|
||||
|
||||
export const CPU = {
|
||||
name: 'cpu',
|
||||
label: 'CPU',
|
||||
sort: 'cpu',
|
||||
value: 'cpuUsage',
|
||||
formatter: 'PercentageBar'
|
||||
};
|
||||
|
||||
export const RAM = {
|
||||
name: 'ram',
|
||||
label: 'RAM',
|
||||
sort: 'ram',
|
||||
value: 'ramUsage',
|
||||
formatter: 'PercentageBar'
|
||||
};
|
||||
|
||||
export const PODS = {
|
||||
name: 'pods',
|
||||
label: 'Pods',
|
||||
sort: 'pods',
|
||||
value: 'podUsage',
|
||||
formatter: 'PercentageBar'
|
||||
};
|
||||
|
||||
export const AGE = {
|
||||
name: 'age',
|
||||
label: 'Age',
|
||||
|
|
@ -208,6 +254,49 @@ export const USER_STATUS = {
|
|||
formatter: 'BadgeState'
|
||||
};
|
||||
|
||||
export const TYPE = {
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
value: 'type',
|
||||
sort: ['type']
|
||||
};
|
||||
export const STATUS = {
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
value: 'status',
|
||||
sort: ['status']
|
||||
};
|
||||
export const LAST_HEARTBEAT_TIME = {
|
||||
name: 'lastHeartbeatTime',
|
||||
label: 'Last update',
|
||||
value: 'lastHeartbeatTime',
|
||||
sort: ['lastHeartbeatTime'],
|
||||
formatter: 'LiveDate',
|
||||
};
|
||||
export const REASON = {
|
||||
name: 'reason',
|
||||
label: 'Reason',
|
||||
value: 'reason',
|
||||
sort: ['reason']
|
||||
};
|
||||
export const MESSAGE = {
|
||||
name: 'message',
|
||||
label: 'Message',
|
||||
value: 'message',
|
||||
sort: ['message']
|
||||
};
|
||||
export const KEY = {
|
||||
name: 'key',
|
||||
label: 'Key',
|
||||
value: 'key',
|
||||
sort: ['key']
|
||||
};
|
||||
export const VALUE = {
|
||||
name: 'value',
|
||||
label: 'Value',
|
||||
value: 'value',
|
||||
sort: ['value']
|
||||
};
|
||||
export function headersFor(schema) {
|
||||
const out = [];
|
||||
const attributes = schema.attributes || {};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
<script>
|
||||
import CopyToClipboardText from '@/components/CopyToClipboardText';
|
||||
import ConsumptionGauge from '@/components/ConsumptionGauge';
|
||||
import DetailTop from '@/components/DetailTop';
|
||||
import DetailTopColumn from '@/components/DetailTopColumn';
|
||||
import HStack from '@/components/Layout/Stack/HStack';
|
||||
import VStack from '@/components/Layout/Stack/VStack';
|
||||
import Alert from '@/components/Alert';
|
||||
import SortableTable from '@/components/SortableTable';
|
||||
import Tabbed from '@/components/Tabbed';
|
||||
import Tab from '@/components/Tabbed/Tab';
|
||||
import {
|
||||
TYPE,
|
||||
STATUS,
|
||||
LAST_HEARTBEAT_TIME,
|
||||
REASON,
|
||||
MESSAGE,
|
||||
KEY,
|
||||
VALUE
|
||||
} from '@/config/table-headers';
|
||||
|
||||
export default {
|
||||
name: 'DetailNode',
|
||||
|
||||
components: {
|
||||
Alert, ConsumptionGauge, CopyToClipboardText, DetailTop, DetailTopColumn, HStack, VStack, Tab, Tabbed, SortableTable
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
statusTableHeaders: [
|
||||
TYPE,
|
||||
STATUS,
|
||||
LAST_HEARTBEAT_TIME,
|
||||
REASON,
|
||||
MESSAGE
|
||||
],
|
||||
systemTableHeaders: [
|
||||
KEY,
|
||||
VALUE
|
||||
],
|
||||
labelsTableHeaders: [
|
||||
KEY,
|
||||
VALUE
|
||||
],
|
||||
annotationsTableHeaders: [
|
||||
KEY,
|
||||
VALUE
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
pidPressureStatus() {
|
||||
return this.mapToStatus(this.value.isPidPressureOk);
|
||||
},
|
||||
diskPressureStatus() {
|
||||
return this.mapToStatus(this.value.isDiskPressureOk);
|
||||
},
|
||||
memoryPressureStatus() {
|
||||
return this.mapToStatus(this.value.isMemoryPressureOk);
|
||||
},
|
||||
kubeletStatus() {
|
||||
return this.mapToStatus(this.value.isKubeletOk);
|
||||
},
|
||||
statusTableRows() {
|
||||
return this.value.status.conditions;
|
||||
},
|
||||
systemTableRows() {
|
||||
return Object.keys(this.value.status.nodeInfo)
|
||||
.map(key => ({
|
||||
key,
|
||||
value: this.value.status.nodeInfo[key]
|
||||
}));
|
||||
},
|
||||
labelsTableRows() {
|
||||
return Object.keys(this.value.metadata.labels)
|
||||
.map(key => ({
|
||||
key,
|
||||
value: this.value.metadata.labels[key]
|
||||
}));
|
||||
},
|
||||
annotationsTableRows() {
|
||||
return Object.keys(this.value.metadata.annotations)
|
||||
.map(key => ({
|
||||
key,
|
||||
value: this.value.metadata.annotations[key]
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
memoryFormatter(value) {
|
||||
return (value / 1000000).toFixed(2);
|
||||
},
|
||||
mapToStatus(isOk) {
|
||||
return isOk
|
||||
? 'success'
|
||||
: 'error';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VStack class="node">
|
||||
<DetailTop>
|
||||
<DetailTopColumn title="Description">
|
||||
{{ value.id }}
|
||||
</DetailTopColumn>
|
||||
<DetailTopColumn title="IP Address">
|
||||
<CopyToClipboardText :text="value.status.addresses[0].address" />
|
||||
</DetailTopColumn>
|
||||
<DetailTopColumn title="Version">
|
||||
<CopyToClipboardText :text="value.status.nodeInfo.kubeletVersion" />
|
||||
</DetailTopColumn>
|
||||
<DetailTopColumn title="OS">
|
||||
<CopyToClipboardText :text="value.status.nodeInfo.operatingSystem" />
|
||||
</DetailTopColumn>
|
||||
<DetailTopColumn title="Created">
|
||||
<CopyToClipboardText :text="value.metadata.creationTimestamp" />
|
||||
</DetailTopColumn>
|
||||
</DetailTop>
|
||||
<HStack class="glance" :show-dividers="true">
|
||||
<VStack class="alerts" :show-dividers="true" vertical-align="space-evenly">
|
||||
<Alert :status="pidPressureStatus" message="PID Pressure" />
|
||||
<Alert :status="diskPressureStatus" message="Disk Pressure" />
|
||||
<Alert :status="memoryPressureStatus" message="Memory Pressure" />
|
||||
<Alert :status="kubeletStatus" message="Kubelet" />
|
||||
</VStack>
|
||||
<HStack class="cluster" horizontal-align="space-evenly">
|
||||
<ConsumptionGauge resource-name="CPU" :capacity="value.cpuCapacity" :used="value.cpuConsumed" />
|
||||
<ConsumptionGauge resource-name="MEMORY" :capacity="value.ramCapacity" :used="value.ramConsumed" units="GiB" :number-formatter="memoryFormatter" />
|
||||
<ConsumptionGauge resource-name="PODS" :capacity="value.podCapacity" :used="value.podConsumed" />
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Tabbed default-tab="status">
|
||||
<Tab name="status" label="Status">
|
||||
<SortableTable
|
||||
key-field="_key"
|
||||
:headers="statusTableHeaders"
|
||||
:rows="statusTableRows"
|
||||
:row-actions="false"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab name="system" label="System">
|
||||
<SortableTable
|
||||
key-field="_key"
|
||||
:headers="systemTableHeaders"
|
||||
:rows="systemTableRows"
|
||||
:row-actions="false"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab name="labels" label="Labels">
|
||||
<SortableTable
|
||||
key-field="_key"
|
||||
:headers="labelsTableHeaders"
|
||||
:rows="labelsTableRows"
|
||||
:row-actions="false"
|
||||
/>
|
||||
</Tab>
|
||||
<Tab name="annotations" label="Annotations">
|
||||
<SortableTable
|
||||
key-field="_key"
|
||||
:headers="annotationsTableHeaders"
|
||||
:rows="annotationsTableRows"
|
||||
:row-actions="false"
|
||||
/>
|
||||
</Tab>
|
||||
</Tabbed>
|
||||
</VStack>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cluster {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
$divider-spacing: 20px;
|
||||
|
||||
.glance {
|
||||
& > * {
|
||||
padding: 0 $divider-spacing;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alerts {
|
||||
width: 25%;
|
||||
& > * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
export default {
|
||||
name() {
|
||||
return this.metadata.name;
|
||||
},
|
||||
roles() {
|
||||
console.warn('no backend for roles');
|
||||
|
||||
return 'All';
|
||||
},
|
||||
version() {
|
||||
return this.status.nodeInfo.kubeletVersion;
|
||||
},
|
||||
cpuUsage() {
|
||||
return calculatePercentage(this.status.capacity.cpu, this.status.allocatable.cpu);
|
||||
},
|
||||
cpuCapacity() {
|
||||
return Number.parseInt(this.status.capacity.cpu);
|
||||
},
|
||||
cpuConsumed() {
|
||||
return Number.parseInt(this.status.capacity.cpu) - Number.parseInt(this.status.allocatable.cpu);
|
||||
},
|
||||
ramUsage() {
|
||||
return calculatePercentage(this.status.capacity.memory, this.status.allocatable.memory);
|
||||
},
|
||||
ramCapacity() {
|
||||
return Number.parseInt(this.status.capacity.memory);
|
||||
},
|
||||
ramConsumed() {
|
||||
return Number.parseInt(this.status.capacity.memory) - Number.parseInt(this.status.allocatable.memory);
|
||||
},
|
||||
podUsage() {
|
||||
return calculatePercentage(this.status.capacity.pods, this.status.allocatable.pods);
|
||||
},
|
||||
podCapacity() {
|
||||
return Number.parseInt(this.status.capacity.pods);
|
||||
},
|
||||
podConsumed() {
|
||||
return Number.parseInt(this.status.capacity.pods) - Number.parseInt(this.status.allocatable.pods);
|
||||
},
|
||||
isPidPressureOk() {
|
||||
return isConditionOk(this, 'PIDPressure');
|
||||
},
|
||||
isDiskPressureOk() {
|
||||
return isConditionOk(this, 'DiskPressure');
|
||||
},
|
||||
isMemoryPressureOk() {
|
||||
return isConditionOk(this, 'MemoryPressure');
|
||||
},
|
||||
isKubeletOk() {
|
||||
return !isConditionOk(this, 'Ready');
|
||||
}
|
||||
};
|
||||
|
||||
function isConditionOk(that, type) {
|
||||
const condition = that.status.conditions.find(condition => condition.type === type);
|
||||
|
||||
return condition.status === 'False';
|
||||
}
|
||||
|
||||
function calculatePercentage(capacity, allocatable) {
|
||||
const c = Number.parseFloat(capacity);
|
||||
const a = Number.parseFloat(allocatable);
|
||||
|
||||
return (((c - a) / c) * 100).toString();
|
||||
}
|
||||
|
|
@ -23,6 +23,18 @@ export default {
|
|||
headers() {
|
||||
return FRIENDLY[this.resource].headers;
|
||||
},
|
||||
|
||||
search() {
|
||||
return FRIENDLY[this.resource].search;
|
||||
},
|
||||
|
||||
tableActions() {
|
||||
return FRIENDLY[this.resource].tableActions;
|
||||
},
|
||||
|
||||
showCreate() {
|
||||
return typeof this.tableActions === 'undefined' || this.tableActions;
|
||||
}
|
||||
},
|
||||
|
||||
asyncData(ctx) {
|
||||
|
|
@ -46,12 +58,12 @@ export default {
|
|||
<h1>
|
||||
{{ typeDisplay }}
|
||||
</h1>
|
||||
<div class="actions">
|
||||
<div v-if="showCreate" class="actions">
|
||||
<nuxt-link to="create" append tag="button" type="button" class="btn bg-primary">
|
||||
Create
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</header>
|
||||
<ResourceTable :schema="schema" :rows="rows" :headers="headers" />
|
||||
<ResourceTable :schema="schema" :rows="rows" :headers="headers" :search="search" :table-actions="tableActions" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in New Issue