Browse Source

Separated kubernetes widgets from resources widgets

James Wynn 2 years ago
parent
commit
fdb143304f

+ 73 - 0
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 (
+      <div className="flex flex-col justify-center first:ml-0 ml-4">
+        <div className="flex flex-row items-center justify-end">
+          <div className="flex flex-row items-center">
+            <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
+            <div className="flex flex-col ml-3 text-left">
+              <span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  if (!data) {
+    return (
+      <div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
+        <div className="flex flex-row self-center flex-wrap justify-between">
+          {cluster.show &&
+            <Node type="cluster" options={options.cluster} data={defaultData} />
+          }
+          {nodes.show &&
+            <Node type="node" options={options.nodes} data={defaultData} />
+          }
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
+      <div className="flex flex-row self-center flex-wrap justify-between">
+        {cluster.show &&
+          <Node type="cluster" options={options.cluster} data={data.cluster} />
+        }
+        {nodes.show && data.nodes &&
+          data.nodes.map((node) =>
+            <Node key={node} type="node" options={options.nodes} data={node} />)
+        }
+      </div>
+    </div>
+  );
+}

+ 61 - 0
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 <SiKubernetes className="text-theme-800 dark:text-theme-200 w-5 h-5" />;
+    }
+    if (data.ready) {
+      return <FiServer className="text-theme-800 dark:text-theme-200 w-5 h-5" />;
+    }
+    return <FiAlertTriangle className="text-theme-800 dark:text-theme-200 w-5 h-5" />;
+  }
+
+  return (
+    <div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
+      <div className="flex flex-row self-center flex-wrap justify-between">
+        <div className="flex-none flex flex-row items-center mr-3 py-1.5">
+          {icon()}
+          <div className="flex flex-col ml-3 text-left min-w-[85px]">
+            <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
+              <div className="pl-0.5">
+                {t("common.number", {
+                  value: data.cpu.percent,
+                  style: "unit",
+                  unit: "percent",
+                  maximumFractionDigits: 0
+                })}
+              </div>
+              <FiCpu className="text-theme-800 dark:text-theme-200 w-3 h-3" />
+            </div>
+            <UsageBar percent={data.cpu.percent} />
+            <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
+              <div className="pl-0.5">
+                {t("common.bytes", {
+                  value: data.memory.free,
+                  maximumFractionDigits: 0,
+                  binary: true
+                })}
+              </div>
+              <FaMemory className="text-theme-800 dark:text-theme-200 w-3 h-3" />
+            </div>
+            <UsageBar percent={data.memory.percent} />
+            {options.showLabel && (
+              <div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{type === "cluster" ? options.label : data.name}</div>
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 12 - 0
src/components/widgets/kubernetes/usage-bar.jsx

@@ -0,0 +1,12 @@
+export default function UsageBar({ percent }) {
+  return (
+    <div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-theme-200/20">
+      <div
+        className="bg-theme-800/70 h-1 rounded-full dark:bg-theme-200/50 transition-all duration-1000"
+        style={{
+          width: `${percent}%`,
+        }}
+      />
+    </div>
+  );
+}

+ 2 - 2
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,
   });
 

+ 2 - 2
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,
   });
 

+ 2 - 2
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,
   });
 

+ 5 - 5
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 (
     <div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
       <div className="flex flex-row self-center flex-wrap justify-between">
-        {options.cpu && <Cpu expanded={expanded} backend={backend} />}
-        {options.memory && <Memory expanded={expanded} backend={backend} />}
+        {options.cpu && <Cpu expanded={expanded} />}
+        {options.memory && <Memory expanded={expanded} />}
         {Array.isArray(options.disk)
-          ? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} backend={backend} />)
-          : options.disk && <Disk options={options} expanded={expanded} backend={backend} />}
+          ? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
+          : options.disk && <Disk options={options} expanded={expanded} />}
       </div>
       {options.label && (
         <div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>

+ 1 - 0
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 }) {

+ 42 - 35
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);