mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
936cbd53d4
commit
1d0815abd2
797 files changed
+73714
-215
No files matched your search
@@ -0,0 +1,4 @@
|
||||
export * from "./nodes"
|
||||
export * from "./nodes.route"
|
||||
export * from "./node-menu"
|
||||
export * from "./node-details"
|
||||
@@ -0,0 +1,154 @@
|
||||
import React, { useContext } from "react";
|
||||
import { t } from "@lingui/macro";
|
||||
import { IClusterMetrics, Node } from "../../api/endpoints";
|
||||
import { BarChart, cpuOptions, memoryOptions } from "../chart";
|
||||
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChartOptions, ChartPoint } from "chart.js";
|
||||
import { themeStore } from "../../theme.store";
|
||||
import { _i18n } from "../../i18n";
|
||||
|
||||
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
|
||||
|
||||
export const NodeCharts = observer(() => {
|
||||
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
||||
const id = object.getId();
|
||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
||||
|
||||
if (!metrics) return null;
|
||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||
|
||||
const values = Object.values(metrics).map(metric =>
|
||||
normalizeMetrics(metric).data.result[0].values
|
||||
);
|
||||
const [
|
||||
memoryUsage,
|
||||
memoryRequests,
|
||||
memoryLimits,
|
||||
memoryCapacity,
|
||||
cpuUsage,
|
||||
cpuRequests,
|
||||
cpuLimits,
|
||||
cpuCapacity,
|
||||
podUsage,
|
||||
podCapacity,
|
||||
fsSize,
|
||||
fsUsage
|
||||
] = values;
|
||||
|
||||
const datasets = [
|
||||
// CPU
|
||||
[
|
||||
{
|
||||
id: `${id}-cpuUsage`,
|
||||
label: _i18n._(t`Usage`),
|
||||
tooltip: _i18n._(t`CPU cores usage`),
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-cpuRequests`,
|
||||
label: _i18n._(t`Requests`),
|
||||
tooltip: _i18n._(t`CPU requests`),
|
||||
borderColor: "#30b24d",
|
||||
data: cpuRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-cpuCapacity`,
|
||||
label: _i18n._(t`Capacity`),
|
||||
tooltip: _i18n._(t`CPU capacity`),
|
||||
borderColor: chartCapacityColor,
|
||||
data: cpuCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Memory
|
||||
[
|
||||
{
|
||||
id: `${id}-memoryUsage`,
|
||||
label: _i18n._(t`Usage`),
|
||||
tooltip: _i18n._(t`Memory usage`),
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryRequests",
|
||||
label: _i18n._(t`Requests`),
|
||||
tooltip: _i18n._(t`Memory requests`),
|
||||
borderColor: "#30b24d",
|
||||
data: memoryRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-memoryCapacity`,
|
||||
label: _i18n._(t`Capacity`),
|
||||
tooltip: _i18n._(t`Memory capacity`),
|
||||
borderColor: chartCapacityColor,
|
||||
data: memoryCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Disk
|
||||
[
|
||||
{
|
||||
id: `${id}-fsUsage`,
|
||||
label: _i18n._(t`Usage`),
|
||||
tooltip: _i18n._(t`Node filesystem usage in bytes`),
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-fsSize`,
|
||||
label: _i18n._(t`Size`),
|
||||
tooltip: _i18n._(t`Node filesystem size in bytes`),
|
||||
borderColor: chartCapacityColor,
|
||||
data: fsSize.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Pods
|
||||
[
|
||||
{
|
||||
id: `${id}-podUsage`,
|
||||
label: _i18n._(t`Usage`),
|
||||
tooltip: _i18n._(t`Number of running Pods`),
|
||||
borderColor: "#30b24d",
|
||||
data: podUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-podCapacity`,
|
||||
label: _i18n._(t`Capacity`),
|
||||
tooltip: _i18n._(t`Node Pods capacity`),
|
||||
borderColor: chartCapacityColor,
|
||||
data: podCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
const podOptions: ChartOptions = {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
callback: value => value
|
||||
}
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: ({ datasetIndex, index }, { datasets }) => {
|
||||
const { label, data } = datasets[datasetIndex];
|
||||
const value = data[index] as ChartPoint;
|
||||
return `${label}: ${value.y}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const options = [cpuOptions, memoryOptions, memoryOptions, podOptions];
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
name={`${object.getName()}-metric-${tabId}`}
|
||||
options={options[tabId]}
|
||||
data={{ datasets: datasets[tabId] }}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
.NodeDetails {
|
||||
--status-ready-bg: #{$colorOk};
|
||||
--status-default-bg: #{$colorError};
|
||||
|
||||
.conditions {
|
||||
.Badge {
|
||||
cursor: default;
|
||||
@include node-status-bgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import "./node-details.scss";
|
||||
|
||||
import React from "react";
|
||||
import upperFirst from "lodash/upperFirst";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
||||
import { Badge } from "../badge";
|
||||
import { TooltipContent } from "../tooltip";
|
||||
import { nodesStore } from "./nodes.store";
|
||||
import { ResourceMetrics } from "../resource-metrics";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { KubeObjectDetailsProps } from "../kube-object";
|
||||
import { Node, nodesApi } from "../../api/endpoints";
|
||||
import { NodeCharts } from "./node-charts";
|
||||
import { reaction } from "mobx";
|
||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Node> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class NodeDetails extends React.Component<Props> {
|
||||
@disposeOnUnmount
|
||||
clean = reaction(() => this.props.object.getName(), () => {
|
||||
nodesStore.nodeMetrics = null;
|
||||
});
|
||||
|
||||
async componentDidMount() {
|
||||
if (!podsStore.isLoaded) {
|
||||
podsStore.loadAll();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
nodesStore.nodeMetrics = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object: node } = this.props;
|
||||
if (!node) return;
|
||||
const { status } = node
|
||||
const { nodeInfo, addresses, capacity, allocatable } = status
|
||||
const conditions = node.getActiveConditions();
|
||||
const taints = node.getTaints()
|
||||
const childPods = podsStore.getPodsByNode(node.getName())
|
||||
const metrics = nodesStore.nodeMetrics
|
||||
const metricTabs = [
|
||||
<Trans>CPU</Trans>,
|
||||
<Trans>Memory</Trans>,
|
||||
<Trans>Disk</Trans>,
|
||||
<Trans>Pods</Trans>,
|
||||
];
|
||||
return (
|
||||
<div className="NodeDetails">
|
||||
{podsStore.isLoaded && (
|
||||
<ResourceMetrics
|
||||
loader={() => nodesStore.loadMetrics(node.getName())}
|
||||
tabs={metricTabs} object={node} params={{ metrics }}
|
||||
>
|
||||
<NodeCharts/>
|
||||
</ResourceMetrics>
|
||||
)}
|
||||
<KubeObjectMeta object={node} hideFields={["labels", "annotations", "uid", "resourceVersion", "selfLink"]}/>
|
||||
<DrawerItem name={<Trans>Capacity</Trans>}>
|
||||
<Trans>CPU</Trans>: {capacity.cpu},{" "}
|
||||
<Trans>Memory</Trans>: {Math.floor(parseInt(capacity.memory) / 1024)}Mi,{" "}
|
||||
<Trans>Pods</Trans>: {capacity.pods}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Allocatable</Trans>}>
|
||||
<Trans>CPU</Trans>: {allocatable.cpu},{" "}
|
||||
<Trans>Memory</Trans>: {Math.floor(parseInt(allocatable.memory) / 1024)}Mi,{" "}
|
||||
<Trans>Pods</Trans>: {allocatable.pods}
|
||||
</DrawerItem>
|
||||
{addresses &&
|
||||
<DrawerItem name={<Trans>Addresses</Trans>}>
|
||||
{
|
||||
addresses.map(({ type, address }) => (
|
||||
<p key={type}>{type}: {address}</p>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
<DrawerItem name={<Trans>OS</Trans>}>
|
||||
{nodeInfo.operatingSystem} ({nodeInfo.architecture})
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>OS Image</Trans>}>
|
||||
{nodeInfo.osImage}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Kernel version</Trans>}>
|
||||
{nodeInfo.kernelVersion}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Container runtime</Trans>}>
|
||||
{nodeInfo.containerRuntimeVersion}
|
||||
</DrawerItem>
|
||||
<DrawerItem name={<Trans>Kubelet version</Trans>}>
|
||||
{nodeInfo.kubeletVersion}
|
||||
</DrawerItem>
|
||||
<DrawerItemLabels
|
||||
name={<Trans>Labels</Trans>}
|
||||
labels={node.getLabels()}
|
||||
/>
|
||||
<DrawerItemLabels
|
||||
name={<Trans>Annotations</Trans>}
|
||||
labels={node.getAnnotations()}
|
||||
/>
|
||||
{taints.length > 0 && (
|
||||
<DrawerItem name={<Trans>Taints</Trans>} labelsOnly>
|
||||
{
|
||||
taints.map(({ key, effect, value }) => (
|
||||
<Badge key={key} label={key + ": " + effect} tooltip={value}/>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{conditions &&
|
||||
<DrawerItem name={<Trans>Conditions</Trans>} className="conditions" labelsOnly>
|
||||
{
|
||||
conditions.map(condition => {
|
||||
const { type } = condition
|
||||
return (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
className={kebabCase(type)}
|
||||
tooltip={
|
||||
<TooltipContent tableView>
|
||||
{Object.entries(condition).map(([key, value]) =>
|
||||
<div key={key} className="flex gaps align-center">
|
||||
<div className="name">{upperFirst(key)}</div>
|
||||
<div className="value">{value}</div>
|
||||
</div>
|
||||
)}
|
||||
</TooltipContent>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
<PodDetailsList
|
||||
pods={childPods}
|
||||
owner={node}
|
||||
maxCpu={node.getCpuCapacity()}
|
||||
maxMemory={node.getMemoryCapacity()}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
apiManager.registerViews(nodesApi, {
|
||||
Details: NodeDetails,
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as React from "react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { Node } from "../../api/endpoints/nodes.api";
|
||||
import { createTerminalTab, terminalStore } from "../dock/terminal.store";
|
||||
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { hideDetails } from "../../navigation";
|
||||
|
||||
export function NodeMenu(props: KubeObjectMenuProps<Node>) {
|
||||
const { object: node, toolbar } = props;
|
||||
if (!node) return null;
|
||||
const nodeName = node.getName();
|
||||
|
||||
const sendToTerminal = (command: string) => {
|
||||
terminalStore.sendCommand(command, {
|
||||
enter: true,
|
||||
newTab: true,
|
||||
});
|
||||
hideDetails();
|
||||
}
|
||||
|
||||
const shell = () => {
|
||||
createTerminalTab({
|
||||
title: _i18n._(t`Shell: ${nodeName}`),
|
||||
node: nodeName,
|
||||
});
|
||||
hideDetails();
|
||||
}
|
||||
|
||||
const cordon = () => {
|
||||
sendToTerminal(`kubectl cordon ${nodeName}`);
|
||||
}
|
||||
|
||||
const unCordon = () => {
|
||||
sendToTerminal(`kubectl uncordon ${nodeName}`)
|
||||
}
|
||||
|
||||
const drain = () => {
|
||||
const command = `kubectl drain ${nodeName} --delete-local-data --ignore-daemonsets --force`;
|
||||
ConfirmDialog.open({
|
||||
ok: () => sendToTerminal(command),
|
||||
labelOk: _i18n._(t`Drain Node`),
|
||||
message: (
|
||||
<p>
|
||||
<Trans>Are you sure you want to drain <b>{nodeName}</b>?</Trans>
|
||||
</p>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<KubeObjectMenu {...props}>
|
||||
<MenuItem onClick={shell}>
|
||||
<Icon svg="ssh" interactive={toolbar} title={_i18n._(t`Node shell`)}/>
|
||||
<span className="title"><Trans>Shell</Trans></span>
|
||||
</MenuItem>
|
||||
{!node.isUnschedulable() && (
|
||||
<MenuItem onClick={cordon}>
|
||||
<Icon material="pause_circle_filled" title={_i18n._(t`Cordon`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Cordon</Trans></span>
|
||||
</MenuItem>
|
||||
)}
|
||||
{node.isUnschedulable() && (
|
||||
<MenuItem onClick={unCordon}>
|
||||
<Icon material="play_circle_filled" title={_i18n._(t`Uncordon`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Uncordon</Trans></span>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={drain}>
|
||||
<Icon material="delete_sweep" title={_i18n._(t`Drain`)} interactive={toolbar}/>
|
||||
<span className="title"><Trans>Drain</Trans></span>
|
||||
</MenuItem>
|
||||
</KubeObjectMenu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
$node-status-color-list: (
|
||||
out-of-disk: #ce3933,
|
||||
network-unavailable: #812727,
|
||||
memory-pressure: #901995,
|
||||
disk-pressure: #CC8D00,
|
||||
pid-pressure: #068DBF,
|
||||
ready: #4caf50,
|
||||
scheduling-disabled: #ff9800,
|
||||
invalid-license: #ce3933,
|
||||
cordoned: $colorWarning
|
||||
);
|
||||
|
||||
@mixin node-status-bgs {
|
||||
@each $status, $color in $node-status-color-list {
|
||||
&.#{$status} {
|
||||
background: $color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin node-status-colors {
|
||||
@each $status, $color in $node-status-color-list {
|
||||
&.#{$status} {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
|
||||
export const nodesRoute: RouteProps = {
|
||||
path: "/nodes"
|
||||
}
|
||||
|
||||
export interface INodesRouteParams {
|
||||
}
|
||||
|
||||
export const nodesURL = buildURL<INodesRouteParams>(nodesRoute.path)
|
||||
@@ -0,0 +1,53 @@
|
||||
@import "./nodes-mixins";
|
||||
|
||||
.Nodes {
|
||||
.TableCell {
|
||||
&.name {
|
||||
flex: 2;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.cpu {
|
||||
flex: 1.0;
|
||||
align-self: center;
|
||||
|
||||
.LineProgress {
|
||||
color: $kontenaBlue;
|
||||
}
|
||||
}
|
||||
|
||||
&.memory {
|
||||
flex: 1.0;
|
||||
align-self: center;
|
||||
|
||||
.LineProgress {
|
||||
color: $kontenaMagenta;
|
||||
}
|
||||
}
|
||||
|
||||
&.disk {
|
||||
flex: 1.0;
|
||||
align-self: center;
|
||||
|
||||
.LineProgress {
|
||||
color: $golden;
|
||||
}
|
||||
}
|
||||
|
||||
&.taints {
|
||||
flex: 0.6;
|
||||
}
|
||||
|
||||
&.conditions {
|
||||
flex: 1.7;
|
||||
text-align: right;
|
||||
|
||||
.condition {
|
||||
display: inline-block;
|
||||
margin-right: $margin;
|
||||
|
||||
@include node-status-colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { action, computed, observable } from "mobx"
|
||||
import { clusterApi, IClusterMetrics, INodeMetrics, Node, nodesApi } from "../../api/endpoints";
|
||||
import { autobind } from "../../utils";
|
||||
import { KubeObjectStore } from "../../kube-object.store";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
|
||||
@autobind()
|
||||
export class NodesStore extends KubeObjectStore<Node> {
|
||||
api = nodesApi
|
||||
|
||||
@observable metrics: Partial<INodeMetrics> = {};
|
||||
@observable nodeMetrics: Partial<IClusterMetrics> = null;
|
||||
@observable metricsLoading = false;
|
||||
@observable metricsLoaded = false;
|
||||
|
||||
@action
|
||||
async loadUsageMetrics() {
|
||||
this.metricsLoading = true;
|
||||
try {
|
||||
this.metrics = await nodesApi.getMetrics();
|
||||
this.metricsLoaded = true;
|
||||
} finally {
|
||||
this.metricsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async loadMetrics(nodeName: string) {
|
||||
this.nodeMetrics = await clusterApi.getMetrics([nodeName]);
|
||||
}
|
||||
|
||||
@computed get masterNodes() {
|
||||
return this.items.filter(node => node.getRoleLabels().includes("master"))
|
||||
}
|
||||
|
||||
@computed get workerNodes() {
|
||||
return this.items.filter(node => !node.getRoleLabels().includes("master"))
|
||||
}
|
||||
|
||||
getLastMetricValues(node: Node, metricNames: string[]): number[] {
|
||||
if (!this.metricsLoaded) {
|
||||
return;
|
||||
}
|
||||
const nodeName = node.getName();
|
||||
return metricNames.map(metricName => {
|
||||
try {
|
||||
const metric = this.metrics[metricName];
|
||||
const result = metric.data.result.find(result => {
|
||||
return [
|
||||
result.metric.node,
|
||||
result.metric.instance,
|
||||
result.metric.kubernetes_node,
|
||||
].includes(nodeName)
|
||||
});
|
||||
return result ? parseFloat(result.values.slice(-1)[0][1]) : 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
this.metrics = {};
|
||||
this.nodeMetrics = null;
|
||||
this.metricsLoading = false;
|
||||
this.metricsLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const nodesStore = new NodesStore()
|
||||
apiManager.registerStore(nodesApi, nodesStore);
|
||||
@@ -0,0 +1,186 @@
|
||||
import "./nodes.scss";
|
||||
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { cssNames, interval } from "../../utils";
|
||||
import { MainLayout } from "../layout/main-layout";
|
||||
import { nodesStore } from "./nodes.store";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { KubeObjectListLayout } from "../kube-object";
|
||||
import { INodesRouteParams } from "./nodes.route";
|
||||
import { Node, nodesApi } from "../../api/endpoints/nodes.api";
|
||||
import { NodeMenu } from "./node-menu";
|
||||
import { LineProgress } from "../line-progress";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { bytesToUnits } from "../../utils/convertMemory";
|
||||
import { Tooltip, TooltipContent } from "../tooltip";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import upperFirst from "lodash/upperFirst";
|
||||
import { apiManager } from "../../api/api-manager";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
cpu = "cpu",
|
||||
memory = "memory",
|
||||
disk = "disk",
|
||||
conditions = "condition",
|
||||
taints = "taints",
|
||||
roles = "roles",
|
||||
age = "age",
|
||||
version = "version",
|
||||
status = "status",
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<INodesRouteParams> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Nodes extends React.Component<Props> {
|
||||
private metricsWatcher = interval(30, () => nodesStore.loadUsageMetrics());
|
||||
|
||||
componentDidMount() {
|
||||
this.metricsWatcher.start(true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.metricsWatcher.stop();
|
||||
}
|
||||
|
||||
renderCpuUsage(node: Node) {
|
||||
const metrics = nodesStore.getLastMetricValues(node, ["cpuUsage", "cpuCapacity"]);
|
||||
if (!metrics || !metrics[1]) return <LineProgress value={0}/>;
|
||||
const usage = metrics[0];
|
||||
const cores = metrics[1];
|
||||
return (
|
||||
<LineProgress
|
||||
max={cores}
|
||||
value={usage}
|
||||
tooltip={_i18n._(t`CPU:`) + ` ${Math.ceil(usage * 100) / cores}\%, ` + _i18n._(t`cores:`) + ` ${cores}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMemoryUsage(node: Node) {
|
||||
const metrics = nodesStore.getLastMetricValues(node, ["memoryUsage", "memoryCapacity"]);
|
||||
if (!metrics || !metrics[1]) return <LineProgress value={0}/>;
|
||||
const usage = metrics[0];
|
||||
const capacity = metrics[1];
|
||||
return (
|
||||
<LineProgress
|
||||
max={capacity}
|
||||
value={usage}
|
||||
tooltip={_i18n._(t`Memory:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderDiskUsage(node: Node): any {
|
||||
const metrics = nodesStore.getLastMetricValues(node, ["fsUsage", "fsSize"]);
|
||||
if (!metrics || !metrics[1]) return <LineProgress value={0}/>;
|
||||
const usage = metrics[0];
|
||||
const capacity = metrics[1];
|
||||
return (
|
||||
<LineProgress
|
||||
max={capacity}
|
||||
value={usage}
|
||||
tooltip={_i18n._(t`Disk:`) + ` ${Math.ceil(usage * 100 / capacity)}%, ${bytesToUnits(usage, 3)}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderConditions(node: Node) {
|
||||
if (!node.status.conditions) {
|
||||
return null
|
||||
}
|
||||
const conditions = node.getActiveConditions();
|
||||
return conditions.map(condition => {
|
||||
const { type } = condition
|
||||
const tooltipId = `node-${node.getName()}-condition-${type}`
|
||||
return (
|
||||
<div key={type} id={tooltipId} className={cssNames("condition", kebabCase(type))}>
|
||||
{type}
|
||||
<Tooltip htmlFor={tooltipId} following>
|
||||
<TooltipContent tableView>
|
||||
{Object.entries(condition).map(([key, value]) =>
|
||||
<div key={key} className="flex gaps align-center">
|
||||
<div className="name">{upperFirst(key)}</div>
|
||||
<div className="value">{value}</div>
|
||||
</div>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>)
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MainLayout>
|
||||
<KubeObjectListLayout
|
||||
className="Nodes"
|
||||
store={nodesStore} isClusterScoped
|
||||
isReady={nodesStore.isLoaded && nodesStore.metricsLoaded}
|
||||
dependentStores={[podsStore]}
|
||||
isSelectable={false}
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (node: Node) => node.getName(),
|
||||
[sortBy.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]),
|
||||
[sortBy.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]),
|
||||
[sortBy.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]),
|
||||
[sortBy.conditions]: (node: Node) => node.getNodeConditionText(),
|
||||
[sortBy.taints]: (node: Node) => node.getTaints().length,
|
||||
[sortBy.roles]: (node: Node) => node.getRoleLabels(),
|
||||
[sortBy.age]: (node: Node) => node.getAge(false),
|
||||
[sortBy.version]: (node: Node) => node.getKubeletVersion(),
|
||||
}}
|
||||
searchFilters={[
|
||||
(node: Node) => node.getSearchFields(),
|
||||
(node: Node) => node.getRoleLabels(),
|
||||
(node: Node) => node.getKubeletVersion(),
|
||||
(node: Node) => node.getNodeConditionText(),
|
||||
]}
|
||||
renderHeaderTitle={<Trans>Nodes</Trans>}
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ title: <Trans>CPU</Trans>, className: "cpu", sortBy: sortBy.cpu },
|
||||
{ title: <Trans>Memory</Trans>, className: "memory", sortBy: sortBy.memory },
|
||||
{ title: <Trans>Disk</Trans>, className: "disk", sortBy: sortBy.disk },
|
||||
{ title: <Trans>Taints</Trans>, className: "taints", sortBy: sortBy.taints },
|
||||
{ title: <Trans>Roles</Trans>, className: "roles", sortBy: sortBy.roles },
|
||||
{ title: <Trans>Version</Trans>, className: "version", sortBy: sortBy.version },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
{ title: <Trans>Conditions</Trans>, className: "conditions", sortBy: sortBy.conditions },
|
||||
]}
|
||||
renderTableContents={(node: Node) => {
|
||||
const tooltipId = `node-taints-${node.getId()}`;
|
||||
return [
|
||||
node.getName(),
|
||||
this.renderCpuUsage(node),
|
||||
this.renderMemoryUsage(node),
|
||||
this.renderDiskUsage(node),
|
||||
<>
|
||||
<span id={tooltipId}>{node.getTaints().length}</span>
|
||||
<Tooltip htmlFor={tooltipId} style={{ whiteSpace: "pre-line" }}>
|
||||
{node.getTaints().map(({ key, effect }) => `${key}: ${effect}`).join("\n")}
|
||||
</Tooltip>
|
||||
</>,
|
||||
node.getRoleLabels(),
|
||||
node.status.nodeInfo.kubeletVersion,
|
||||
node.getAge(),
|
||||
this.renderConditions(node),
|
||||
]
|
||||
}}
|
||||
renderItemMenu={(item: Node) => {
|
||||
return <NodeMenu object={item}/>
|
||||
}}
|
||||
/>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
apiManager.registerViews(nodesApi, {
|
||||
Menu: NodeMenu,
|
||||
});
|
||||
Reference in new issue
Block a user