diff --git a/src/components/widgets/longhorn/longhorn.jsx b/src/components/widgets/longhorn/longhorn.jsx
index 3ba421d0..9fcb21b4 100644
--- a/src/components/widgets/longhorn/longhorn.jsx
+++ b/src/components/widgets/longhorn/longhorn.jsx
@@ -1,6 +1,6 @@
import useSWR from "swr";
import { BiError } from "react-icons/bi";
-import { i18n, useTranslation } from "next-i18next";
+import { useTranslation } from "next-i18next";
import Node from "./node";
diff --git a/src/pages/api/kubernetes/stats/[...service].js b/src/pages/api/kubernetes/stats/[...service].js
index 05001908..c4b5b931 100644
--- a/src/pages/api/kubernetes/stats/[...service].js
+++ b/src/pages/api/kubernetes/stats/[...service].js
@@ -2,15 +2,18 @@ import { CoreV1Api, Metrics } from "@kubernetes/client-node";
import getKubeConfig from "../../../../utils/config/kubernetes";
import { parseCpu, parseMemory } from "../../../../utils/kubernetes/kubernetes-utils";
+import createLogger from "../../../../utils/logger";
+
+const logger = createLogger("kubernetesStatsService");
export default async function handler(req, res) {
- const APP_LABEL = "app.kubernetes.io/name";
+ const APP_LABEL = "app.kubernetes.io/name";
const { service } = req.query;
const [namespace, appName] = service;
if (!namespace && !appName) {
res.status(400).send({
- error: "kubernetes query parameters are required",
+ error: "kubernetes query parameters are required"
});
return;
}
@@ -20,12 +23,23 @@ export default async function handler(req, res) {
const kc = getKubeConfig();
const coreApi = kc.makeApiClient(CoreV1Api);
const metricsApi = new Metrics(kc);
- const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector);
- const pods = podsResponse.body.items;
+ const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector)
+ .then((response) => response.body)
+ .catch((err) => {
+ logger.error("Error getting pods: %d %s %s", err.statusCode, err.body, err.response);
+ return null;
+ });
+ if (!podsResponse) {
+ res.status(500).send({
+ error: "Error communicating with kubernetes"
+ });
+ return;
+ }
+ const pods = podsResponse.items;
if (pods.length === 0) {
- res.status(200).send({
- error: "not found",
+ res.status(404).send({
+ error: "not found"
});
return;
}
@@ -46,34 +60,43 @@ export default async function handler(req, res) {
const stats = await pods.map(async (pod) => {
let depMem = 0;
let depCpu = 0;
- const podMetrics = await metricsApi.getPodMetrics(namespace, pod.metadata.name);
- podMetrics.containers.forEach((container) => {
- depMem += parseMemory(container.usage.memory);
- depCpu += parseCpu(container.usage.cpu);
- });
+ const podMetrics = await metricsApi.getPodMetrics(namespace, pod.metadata.name)
+ .then((response) => response)
+ .catch((err) => {
+ // 404 generally means that the metrics have not been populated yet
+ if (err.statusCode !== 404) {
+ logger.error("Error getting pod metrics: %d %s %s", err.statusCode, err.body, err.response);
+ }
+ return null;
+ });
+ if (podMetrics) {
+ podMetrics.containers.forEach((container) => {
+ depMem += parseMemory(container.usage.memory);
+ depCpu += parseCpu(container.usage.cpu);
+ });
+ }
return {
mem: depMem,
cpu: depCpu
- }
- }).reduce(async (finalStats, podStatPromise) => {
- const podStats = await podStatPromise;
- return {
- mem: finalStats.mem + podStats.mem,
- cpu: finalStats.cpu + podStats.cpu
};
- });
+ }).reduce(async (finalStats, podStatPromise) => {
+ const podStats = await podStatPromise;
+ return {
+ mem: finalStats.mem + podStats.mem,
+ cpu: finalStats.cpu + podStats.cpu
+ };
+ });
stats.cpuLimit = cpuLimit;
stats.memLimit = memLimit;
- stats.cpuUsage = stats.cpu / cpuLimit;
- stats.memUsage = stats.mem / memLimit;
-
+ stats.cpuUsage = cpuLimit ? stats.cpu / cpuLimit : 0;
+ stats.memUsage = memLimit ? stats.mem / memLimit : 0;
res.status(200).json({
- stats,
+ stats
});
} catch (e) {
- console.log("error", e);
+ logger.error(e);
res.status(500).send({
- error: "unknown error",
+ error: "unknown error"
});
}
}
diff --git a/src/pages/api/kubernetes/status/[...service].js b/src/pages/api/kubernetes/status/[...service].js
index dbe64f38..aa325e9b 100644
--- a/src/pages/api/kubernetes/status/[...service].js
+++ b/src/pages/api/kubernetes/status/[...service].js
@@ -1,6 +1,9 @@
import { CoreV1Api } from "@kubernetes/client-node";
import getKubeConfig from "../../../../utils/config/kubernetes";
+import createLogger from "../../../../utils/logger";
+
+const logger = createLogger("kubernetesStatusService");
export default async function handler(req, res) {
const APP_LABEL = "app.kubernetes.io/name";
@@ -18,11 +21,22 @@ export default async function handler(req, res) {
try {
const kc = getKubeConfig();
const coreApi = kc.makeApiClient(CoreV1Api);
- const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector);
- const pods = podsResponse.body.items;
+ const podsResponse = await coreApi.listNamespacedPod(namespace, null, null, null, null, labelSelector)
+ .then((response) => response.body)
+ .catch((err) => {
+ logger.error("Error getting pods: %d %s %s", err.statusCode, err.body, err.response);
+ return null;
+ });
+ if (!podsResponse) {
+ res.status(500).send({
+ error: "Error communicating with kubernetes"
+ });
+ return;
+ }
+ const pods = podsResponse.items;
if (pods.length === 0) {
- res.status(200).send({
+ res.status(404).send({
error: "not found",
});
return;
@@ -34,7 +48,8 @@ export default async function handler(req, res) {
res.status(200).json({
status
});
- } catch {
+ } catch (e) {
+ logger.error(e);
res.status(500).send({
error: "unknown error",
});
diff --git a/src/pages/api/widgets/kubernetes.js b/src/pages/api/widgets/kubernetes.js
index 350c93a2..7ca9f62b 100644
--- a/src/pages/api/widgets/kubernetes.js
+++ b/src/pages/api/widgets/kubernetes.js
@@ -2,59 +2,79 @@ import { CoreV1Api, Metrics } from "@kubernetes/client-node";
import getKubeConfig from "../../../utils/config/kubernetes";
import { parseCpu, parseMemory } from "../../../utils/kubernetes/kubernetes-utils";
+import createLogger from "../../../utils/logger";
+
+const logger = createLogger("kubernetes-widget");
export default async function handler(req, res) {
const { type } = req.query;
- const kc = getKubeConfig();
- const coreApi = kc.makeApiClient(CoreV1Api);
- const metricsApi = new Metrics(kc);
+ try {
+ const kc = getKubeConfig();
+ const coreApi = kc.makeApiClient(CoreV1Api);
+ const metricsApi = new Metrics(kc);
- const nodes = await coreApi.listNode();
- const nodeCapacity = new Map();
- let cpuTotal = 0;
- let cpuUsage = 0;
- let memTotal = 0;
- let memUsage = 0;
+ const nodes = await coreApi.listNode()
+ .then((response) => response.body)
+ .catch((error) => {
+ logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response);
+ return null;
+ });
+ if (!nodes) {
+ return res.status(500).send({
+ error: "unknown error"
+ });
+ }
+ const nodeCapacity = new Map();
+ let cpuTotal = 0;
+ let cpuUsage = 0;
+ let memTotal = 0;
+ let memUsage = 0;
- nodes.body.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);
- });
+ 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 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);
- });
+ 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);
+ });
- if (type === "cpu") {
- return res.status(200).json({
- cpu: {
- usage: (cpuUsage / cpuTotal) * 100,
- load: cpuUsage
- }
+ 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
+ }
+ });
+ }
+
+ return res.status(400).json({
+ error: "invalid type"
+ });
+ } catch (e) {
+ logger.error("exception %s", e);
+ return res.status(500).send({
+ error: "unknown error"
});
}
-
- 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
- }
- });
- }
-
- return res.status(400).json({
- error: "invalid type"
- });
}
diff --git a/src/utils/config/api-response.js b/src/utils/config/api-response.js
index aef1650c..59f8e082 100644
--- a/src/utils/config/api-response.js
+++ b/src/utils/config/api-response.js
@@ -64,7 +64,7 @@ export async function servicesResponse() {
try {
discoveredKubernetesServices = cleanServiceGroups(await servicesFromKubernetes());
} catch (e) {
- console.error("Failed to discover services, please check docker.yaml for errors or remove example entries.");
+ console.error("Failed to discover services, please check kubernetes.yaml for errors or remove example entries.");
if (e) console.error(e);
discoveredKubernetesServices = [];
}
diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js
index b1a368ee..efcab0e5 100644
--- a/src/utils/config/service-helpers.js
+++ b/src/utils/config/service-helpers.js
@@ -6,10 +6,13 @@ import Docker from "dockerode";
import * as shvl from "shvl";
import { NetworkingV1Api } from "@kubernetes/client-node";
+import createLogger from "utils/logger";
import checkAndCopyConfig from "utils/config/config";
import getDockerArguments from "utils/config/docker";
import getKubeConfig from "utils/config/kubernetes";
+const logger = createLogger("service-helpers");
+
export async function servicesFromConfig() {
checkAndCopyConfig("services.yaml");
@@ -105,54 +108,80 @@ export async function servicesFromDocker() {
return mappedServiceGroups;
}
+function getUrlFromIngress(ingress) {
+ let url = ingress.metadata.annotations['homepage/url'];
+ if(!url) {
+ const host = ingress.spec.rules[0].host;
+ const path = ingress.spec.rules[0].http.paths[0].path;
+ const schema = ingress.spec.tls ? 'https' : 'http';
+
+ url = `${schema}://${host}${path}`;
+ }
+ return url;
+}
+
export async function servicesFromKubernetes() {
checkAndCopyConfig("kubernetes.yaml");
- const kc = getKubeConfig();
- const networking = kc.makeApiClient(NetworkingV1Api);
+ try {
+ const kc = getKubeConfig();
+ const networking = kc.makeApiClient(NetworkingV1Api);
- const ingressResponse = await networking.listIngressForAllNamespaces(null, null, null, "homepage/enabled=true");
- const services = ingressResponse.body.items.map((ingress) => {
- const constructedService = {
- app: ingress.metadata.name,
- namespace: ingress.metadata.namespace,
- href: `https://${ingress.spec.rules[0].host}`,
- name: ingress.metadata.annotations['homepage/name'],
- group: ingress.metadata.annotations['homepage/group'],
- icon: ingress.metadata.annotations['homepage/icon'],
- description: ingress.metadata.annotations['homepage/description']
- };
- Object.keys(ingress.metadata.labels).forEach((label) => {
- if (label.startsWith("homepage/widget/")) {
- shvl.set(constructedService, label.replace("homepage/widget/", ""), ingress.metadata.labels[label]);
- }
+ const ingressList = await networking.listIngressForAllNamespaces(null, null, null, "homepage/enabled=true")
+ .then((response) => response.body)
+ .catch((error) => {
+ logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response);
+ return null;
+ });
+ if (!ingressList) {
+ return [];
+ }
+ const services = ingressList.items.map((ingress) => {
+ const constructedService = {
+ app: ingress.metadata.name,
+ namespace: ingress.metadata.namespace,
+ href: getUrlFromIngress(ingress),
+ name: ingress.metadata.annotations['homepage/name'] || ingress.metadata.name,
+ group: ingress.metadata.annotations['homepage/group'] || "Kubernetes",
+ icon: ingress.metadata.annotations['homepage/icon'] || '',
+ description: ingress.metadata.annotations['homepage/description'] || ''
+ };
+ Object.keys(ingress.metadata.annotations).forEach((annotation) => {
+ if (annotation.startsWith("homepage/widget/")) {
+ shvl.set(constructedService, annotation.replace("homepage/widget/", ""), ingress.metadata.annotations[annotation]);
+ }
+ });
+
+ return constructedService;
});
- return constructedService;
- });
+ const mappedServiceGroups = [];
- const mappedServiceGroups = [];
+ services.forEach((serverService) => {
+ let serverGroup = mappedServiceGroups.find((searchedGroup) => searchedGroup.name === serverService.group);
+ if (!serverGroup) {
+ mappedServiceGroups.push({
+ name: serverService.group,
+ services: [],
+ });
+ serverGroup = mappedServiceGroups[mappedServiceGroups.length - 1];
+ }
- services.forEach((serverService) => {
- let serverGroup = mappedServiceGroups.find((searchedGroup) => searchedGroup.name === serverService.group);
- if (!serverGroup) {
- mappedServiceGroups.push({
- name: serverService.group,
- services: [],
- });
- serverGroup = mappedServiceGroups[mappedServiceGroups.length - 1];
- }
+ const { name: serviceName, group: serverServiceGroup, ...pushedService } = serverService;
+ const result = {
+ name: serviceName,
+ ...pushedService,
+ };
- const { name: serviceName, group: serverServiceGroup, ...pushedService } = serverService;
- const result = {
- name: serviceName,
- ...pushedService,
- };
+ serverGroup.services.push(result);
+ });
- serverGroup.services.push(result);
- });
+ return mappedServiceGroups;
- return mappedServiceGroups;
+ } catch (e) {
+ logger.error(e);
+ throw e;
+ }
}
export function cleanServiceGroups(groups) {
diff --git a/src/utils/kubernetes/kubernetes-utils.js b/src/utils/kubernetes/kubernetes-utils.js
index 08bd53d3..eac42860 100644
--- a/src/utils/kubernetes/kubernetes-utils.js
+++ b/src/utils/kubernetes/kubernetes-utils.js
@@ -2,7 +2,6 @@ export function parseCpu(cpuStr) {
const unitLength = 1;
const base = Number.parseInt(cpuStr, 10);
const units = cpuStr.substring(cpuStr.length - unitLength);
- // console.log(Number.isNaN(Number(units)), cpuStr, base, units);
if (Number.isNaN(Number(units))) {
switch (units) {
case 'n':
@@ -23,7 +22,6 @@ export function parseMemory(memStr) {
const unitLength = (memStr.substring(memStr.length - 1) === 'i' ? 2 : 1);
const base = Number.parseInt(memStr, 10);
const units = memStr.substring(memStr.length - unitLength);
- // console.log(Number.isNaN(Number(units)), memStr, base, units);
if (Number.isNaN(Number(units))) {
switch (units) {
case 'Ki':
diff --git a/src/widgets/kubernetes/component.jsx b/src/widgets/kubernetes/component.jsx
index 9ed7627d..ca3932d2 100644
--- a/src/widgets/kubernetes/component.jsx
+++ b/src/widgets/kubernetes/component.jsx
@@ -32,23 +32,19 @@ export default function Component({ service }) {
-
-
);
}
- const network = statsData.stats?.networks?.eth0 || statsData.stats?.networks?.network;
return (
-
-
- {network && (
- <>
-
-
- >
+ {statsData.stats.cpuLimit && (
+
+ ) || (
+
)}
+
);
}