diff --git a/src/components/widgets/kubernetes/kubernetes.jsx b/src/components/widgets/kubernetes/kubernetes.jsx new file mode 100644 index 000000000..a769db8f2 --- /dev/null +++ b/src/components/widgets/kubernetes/kubernetes.jsx @@ -0,0 +1,73 @@ +import useSWR from "swr"; +import { BiError } from "react-icons/bi"; +import { useTranslation } from "next-i18next"; +import Node from "./node"; + +export default function Widget({ options }) { + const { cluster, nodes } = options; + const { t, i18n } = useTranslation(); + + const defaultData = { + cpu: { + load: 0, + total: 0, + percent: 0 + }, + memory: { + used: 0, + total: 0, + free: 0, + precent: 0 + } + }; + + const { data, error } = useSWR( + `/api/widgets/kubernetes?${new URLSearchParams({ lang: i18n.language }).toString()}`, { + refreshInterval: 1500 + } + ); + + if (error || data?.error) { + return ( +
+
+
+ +
+ {t("widget.api_error")} +
+
+
+
+ ); + } + + if (!data) { + return ( +
+
+ {cluster.show && + + } + {nodes.show && + + } +
+
+ ); + } + + return ( +
+
+ {cluster.show && + + } + {nodes.show && data.nodes && + data.nodes.map((node) => + ) + } +
+
+ ); +} diff --git a/src/components/widgets/kubernetes/node.jsx b/src/components/widgets/kubernetes/node.jsx new file mode 100644 index 000000000..0d0bde4d1 --- /dev/null +++ b/src/components/widgets/kubernetes/node.jsx @@ -0,0 +1,61 @@ +import { FaMemory } from "react-icons/fa"; +import { FiAlertTriangle, FiCpu, FiServer } from "react-icons/fi"; +import { SiKubernetes } from "react-icons/si"; +import { useTranslation } from "next-i18next"; + +import UsageBar from "./usage-bar"; + + +export default function Node({ type, options, data }) { + const { t } = useTranslation(); + + console.log("Node", type, options, data); + + function icon() { + if (type === "cluster") { + return ; + } + if (data.ready) { + return ; + } + return ; + } + + return ( +
+
+
+ {icon()} +
+
+
+ {t("common.number", { + value: data.cpu.percent, + style: "unit", + unit: "percent", + maximumFractionDigits: 0 + })} +
+ +
+ +
+
+ {t("common.bytes", { + value: data.memory.free, + maximumFractionDigits: 0, + binary: true + })} +
+ +
+ + {options.showLabel && ( +
{type === "cluster" ? options.label : data.name}
+ )} +
+
+
+
+ ); +} diff --git a/src/components/widgets/kubernetes/usage-bar.jsx b/src/components/widgets/kubernetes/usage-bar.jsx new file mode 100644 index 000000000..c817db4c4 --- /dev/null +++ b/src/components/widgets/kubernetes/usage-bar.jsx @@ -0,0 +1,12 @@ +export default function UsageBar({ percent }) { + return ( +
+
+
+ ); +} diff --git a/src/components/widgets/resources/cpu.jsx b/src/components/widgets/resources/cpu.jsx index 9564d99a1..6b021193b 100644 --- a/src/components/widgets/resources/cpu.jsx +++ b/src/components/widgets/resources/cpu.jsx @@ -5,10 +5,10 @@ import { useTranslation } from "next-i18next"; import UsageBar from "./usage-bar"; -export default function Cpu({ expanded, backend }) { +export default function Cpu({ expanded }) { const { t } = useTranslation(); - const { data, error } = useSWR(`/api/widgets/${backend || 'resources'}?type=cpu`, { + const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, { refreshInterval: 1500, }); diff --git a/src/components/widgets/resources/disk.jsx b/src/components/widgets/resources/disk.jsx index eea84162e..69f560f62 100644 --- a/src/components/widgets/resources/disk.jsx +++ b/src/components/widgets/resources/disk.jsx @@ -5,10 +5,10 @@ import { useTranslation } from "next-i18next"; import UsageBar from "./usage-bar"; -export default function Disk({ options, expanded, backend }) { +export default function Disk({ options, expanded }) { const { t } = useTranslation(); - const { data, error } = useSWR(`/api/widgets/${backend || 'resources'}?type=disk&target=${options.disk}`, { + const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, { refreshInterval: 1500, }); diff --git a/src/components/widgets/resources/memory.jsx b/src/components/widgets/resources/memory.jsx index db5be4b9b..452634565 100644 --- a/src/components/widgets/resources/memory.jsx +++ b/src/components/widgets/resources/memory.jsx @@ -5,10 +5,10 @@ import { useTranslation } from "next-i18next"; import UsageBar from "./usage-bar"; -export default function Memory({ expanded, backend }) { +export default function Memory({ expanded }) { const { t } = useTranslation(); - const { data, error } = useSWR(`/api/widgets/${backend || 'resources'}?type=memory`, { + const { data, error } = useSWR(`/api/widgets/resources?type=memory`, { refreshInterval: 1500, }); diff --git a/src/components/widgets/resources/resources.jsx b/src/components/widgets/resources/resources.jsx index c4072c7a1..0524e39af 100644 --- a/src/components/widgets/resources/resources.jsx +++ b/src/components/widgets/resources/resources.jsx @@ -3,15 +3,15 @@ import Cpu from "./cpu"; import Memory from "./memory"; export default function Resources({ options }) { - const { expanded, backend } = options; + const { expanded } = options; return (
- {options.cpu && } - {options.memory && } + {options.cpu && } + {options.memory && } {Array.isArray(options.disk) - ? options.disk.map((disk) => ) - : options.disk && } + ? options.disk.map((disk) => ) + : options.disk && }
{options.label && (
{options.label}
diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index 29e7a1803..471418872 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -14,6 +14,7 @@ const widgetMappings = { glances: dynamic(() => import("components/widgets/glances/glances")), openmeteo: dynamic(() => import("components/widgets/openmeteo/openmeteo")), longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")), + kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")), }; export default function Widget({ widget }) { diff --git a/src/pages/api/widgets/kubernetes.js b/src/pages/api/widgets/kubernetes.js index f589b2b94..b0d7f5531 100644 --- a/src/pages/api/widgets/kubernetes.js +++ b/src/pages/api/widgets/kubernetes.js @@ -7,8 +7,6 @@ import createLogger from "../../../utils/logger"; const logger = createLogger("kubernetes-widget"); export default async function handler(req, res) { - const { type } = req.query; - try { const kc = getKubeConfig(); if (!kc) { @@ -30,51 +28,60 @@ export default async function handler(req, res) { error: "unknown error" }); } - const nodeCapacity = new Map(); let cpuTotal = 0; let cpuUsage = 0; let memTotal = 0; let memUsage = 0; + const nodeMap = {}; nodes.items.forEach((node) => { - nodeCapacity.set(node.metadata.name, node.status.capacity); - cpuTotal += Number.parseInt(node.status.capacity.cpu, 10); - memTotal += parseMemory(node.status.capacity.memory); + const cpu = Number.parseInt(node.status.capacity.cpu, 10); + const mem = parseMemory(node.status.capacity.memory); + const ready = node.status.conditions.filter(condition => condition.type === "Ready" && condition.status === "True").length > 0; + nodeMap[node.metadata.name] = { + name: node.metadata.name, + ready, + cpu: { + total: cpu + }, + memory: { + total: mem + } + }; + cpuTotal += cpu; + memTotal += mem; }); const nodeMetrics = await metricsApi.getNodeMetrics(); - const nodeUsage = new Map(); - nodeMetrics.items.forEach((metrics) => { - nodeUsage.set(metrics.metadata.name, metrics.usage); - cpuUsage += parseCpu(metrics.usage.cpu); - memUsage += parseMemory(metrics.usage.memory); + nodeMetrics.items.forEach((nodeMetric) => { + const cpu = parseCpu(nodeMetric.usage.cpu); + const mem = parseMemory(nodeMetric.usage.memory); + cpuUsage += cpu; + memUsage += mem; + nodeMap[nodeMetric.metadata.name].cpu.load = cpu; + nodeMap[nodeMetric.metadata.name].cpu.percent = (cpu / nodeMap[nodeMetric.metadata.name].cpu.total) * 100; + nodeMap[nodeMetric.metadata.name].memory.used = mem; + nodeMap[nodeMetric.metadata.name].memory.free = nodeMap[nodeMetric.metadata.name].memory.total - mem; + nodeMap[nodeMetric.metadata.name].memory.percent = (mem / nodeMap[nodeMetric.metadata.name].memory.total) * 100; }); - if (type === "cpu") { - return res.status(200).json({ - cpu: { - usage: (cpuUsage / cpuTotal) * 100, - load: cpuUsage - } - }); - } - - if (type === "memory") { - const SCALE_MB = 1024 * 1024; - const usedMemMb = memUsage / SCALE_MB; - const totalMemMb = memTotal / SCALE_MB; - const freeMemMb = totalMemMb - usedMemMb; - return res.status(200).json({ - memory: { - usedMemMb, - freeMemMb, - totalMemMb - } - }); - } + const cluster = { + cpu: { + load: cpuUsage, + total: cpuTotal, + percent: (cpuUsage / cpuTotal) * 100 + }, + memory: { + used: memUsage, + total: memTotal, + free: (memTotal - memUsage), + percent: (memUsage / memTotal) * 100 + } + }; - return res.status(400).json({ - error: "invalid type" + return res.status(200).json({ + cluster, + nodes: Object.entries(nodeMap).map(([name, node]) => ({ name, ...node })) }); } catch (e) { logger.error("exception %s", e);