From 321efd08cc967f6e06f15487526cdee21b7c7fbc Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 10 Oct 2022 11:18:29 -0700 Subject: [PATCH 1/5] Glances info widget --- public/locales/en/common.json | 5 + src/components/widgets/glances/glances.jsx | 111 +++++++++++++++++++++ src/components/widgets/widget.jsx | 1 + src/pages/api/widgets/glances.js | 23 +++++ 4 files changed, 140 insertions(+) create mode 100644 src/components/widgets/glances/glances.jsx create mode 100644 src/pages/api/widgets/glances.js diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 1261c2930..d53d480c7 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -207,5 +207,10 @@ "cpu": "CPU", "lxc": "LXC", "vms": "VMs" + }, + "glances": { + "cpu": "CPU", + "mem": "MEM", + "wait": "Please wait" } } diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx new file mode 100644 index 000000000..75bdeb711 --- /dev/null +++ b/src/components/widgets/glances/glances.jsx @@ -0,0 +1,111 @@ +import useSWR from "swr"; + +import { BiError } from "react-icons/bi"; +import { FaMemory } from "react-icons/fa"; +import { FiCpu } from "react-icons/fi"; +import { useTranslation } from "next-i18next"; + +import UsageBar from "../resources/usage-bar"; + +export default function Widget({ options }) { + const { t, i18n } = useTranslation(); + + const { data, error } = useSWR( + `/api/widgets/glances?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`, { + refreshInterval: 1500, + } + ); + + if (error || data?.error) { + return ( +
+
+
+ +
+ {t("widget.api_error")} +
+
+
+
+ ); + } + + if (!data) { + return ( +
+
+
+ +
+
+
+ {t("glances.wait")} +
+
+ +
+
+
+ +
+
+
+ {t("glances.wait")} +
+
+ +
+
+
+ {options.label && ( +
{options.label}
+ )} +
+ ); + } + + return ( +
+
+
+ +
+
+
+ {t("common.number", { + value: data.cpu, + style: "unit", + unit: "percent", + maximumFractionDigits: 0, + })} +
+
{t("glances.cpu")}
+
+ +
+
+
+ +
+
+
+ {t("common.number", { + value: data.mem, + style: "unit", + unit: "percent", + maximumFractionDigits: 0, + })} +
+
{t("glances.mem")}
+
+ +
+
+
+ {options.label && ( +
{options.label}
+ )} +
+ ); +} diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index ac5353eb9..bd31ed934 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -11,6 +11,7 @@ const widgetMappings = { datetime: dynamic(() => import("components/widgets/datetime/datetime")), logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }), unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), + glances: dynamic(() => import("components/widgets/glances/glances")), }; export default function Widget({ widget }) { diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js new file mode 100644 index 000000000..26edbb81f --- /dev/null +++ b/src/pages/api/widgets/glances.js @@ -0,0 +1,23 @@ +import { httpProxy } from "utils/proxy/http"; + +export default async function handler(req, res) { + const { url } = req.query; + + if (!url) { + return res.status(400).json({ error: "Missing Glances URL" }); + } + + const apiUrl = `${url}/api/3/quicklook`; + const params = { method: "GET", headers: { + "Accept-Encoding": "application/json" + } }; + + const [status, contentType, data, responseHeaders] = await httpProxy(apiUrl, params); + + if (status !== 200) { + logger.error("HTTP %d getting data from glances API %s. Data: %s", status, apiUrl, data); + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(data); +} From 080bc44a6f3d9421b748d63d03ffd07cd088e358 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 10 Oct 2022 11:21:11 -0700 Subject: [PATCH 2/5] Lint glances info widget --- src/components/widgets/glances/glances.jsx | 1 - src/pages/api/widgets/glances.js | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx index 75bdeb711..a48cef50c 100644 --- a/src/components/widgets/glances/glances.jsx +++ b/src/components/widgets/glances/glances.jsx @@ -1,5 +1,4 @@ import useSWR from "swr"; - import { BiError } from "react-icons/bi"; import { FaMemory } from "react-icons/fa"; import { FiCpu } from "react-icons/fi"; diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js index 26edbb81f..b02028600 100644 --- a/src/pages/api/widgets/glances.js +++ b/src/pages/api/widgets/glances.js @@ -1,4 +1,7 @@ import { httpProxy } from "utils/proxy/http"; +import createLogger from "utils/logger"; + +const logger = createLogger("glances"); export default async function handler(req, res) { const { url } = req.query; @@ -12,7 +15,7 @@ export default async function handler(req, res) { "Accept-Encoding": "application/json" } }; - const [status, contentType, data, responseHeaders] = await httpProxy(apiUrl, params); + const [status, contentType, data] = await httpProxy(apiUrl, params); if (status !== 200) { logger.error("HTTP %d getting data from glances API %s. Data: %s", status, apiUrl, data); From 802fe0f721b641f09effab272ebd287979e89f65 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:13:19 -0700 Subject: [PATCH 3/5] Glances widget use settings for URL --- src/pages/api/widgets/glances.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js index b02028600..d8cc04b7a 100644 --- a/src/pages/api/widgets/glances.js +++ b/src/pages/api/widgets/glances.js @@ -1,12 +1,19 @@ import { httpProxy } from "utils/proxy/http"; import createLogger from "utils/logger"; +import { getSettings } from "utils/config/config"; const logger = createLogger("glances"); export default async function handler(req, res) { - const { url } = req.query; + const settings = getSettings()?.glances; + if (!settings) { + logger.error("There is no glances section in settings.yaml"); + return res.status(400).json({ error: "There is no glances section in settings.yaml" }); + } + const url = settings?.url; if (!url) { + logger.error("Missing Glances URL"); return res.status(400).json({ error: "Missing Glances URL" }); } From 99b70f96e438e37c629e95cb1b37f3ce2d06b665 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:40:15 -0700 Subject: [PATCH 4/5] Allow username + password for glances --- src/pages/api/widgets/glances.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js index d8cc04b7a..bf00862b2 100644 --- a/src/pages/api/widgets/glances.js +++ b/src/pages/api/widgets/glances.js @@ -18,14 +18,22 @@ export default async function handler(req, res) { } const apiUrl = `${url}/api/3/quicklook`; - const params = { method: "GET", headers: { + const headers = { "Accept-Encoding": "application/json" - } }; + }; + if (settings.username && settings.password) { + headers.Authorization = `Basic ${Buffer.from(`${settings.username}:${settings.password}`).toString("base64")}` + } + const params = { method: "GET", headers }; const [status, contentType, data] = await httpProxy(apiUrl, params); + if (status === 401) { + logger.error("Authorization failure getting data from glances API. Data: %s", data); + } + if (status !== 200) { - logger.error("HTTP %d getting data from glances API %s. Data: %s", status, apiUrl, data); + logger.error("HTTP %d getting data from glances API. Data: %s", status, data); } if (contentType) res.setHeader("Content-Type", contentType); From 8e2ff61f1cf809c98af06dcfa8913f29819a8728 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:34:59 -0700 Subject: [PATCH 5/5] Allow multiple glances widgets with optional `id` property --- src/pages/api/widgets/glances.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/pages/api/widgets/glances.js b/src/pages/api/widgets/glances.js index bf00862b2..86992dd10 100644 --- a/src/pages/api/widgets/glances.js +++ b/src/pages/api/widgets/glances.js @@ -5,24 +5,35 @@ import { getSettings } from "utils/config/config"; const logger = createLogger("glances"); export default async function handler(req, res) { - const settings = getSettings()?.glances; - if (!settings) { - logger.error("There is no glances section in settings.yaml"); - return res.status(400).json({ error: "There is no glances section in settings.yaml" }); + const { id } = req.query; + + let errorMessage; + + let instanceID = "glances"; + if (id) { // multiple instances + instanceID = id; + } + const settings = getSettings(); + const instanceSettings = settings[instanceID]; + if (!instanceSettings) { + errorMessage = id ? `There is no glances section with id '${id}' in settings.yaml` : "There is no glances section in settings.yaml"; + logger.error(errorMessage); + return res.status(400).json({ error: errorMessage }); } - const url = settings?.url; + const url = instanceSettings?.url; if (!url) { - logger.error("Missing Glances URL"); - return res.status(400).json({ error: "Missing Glances URL" }); + errorMessage = "Missing Glances URL"; + logger.error(errorMessage); + return res.status(400).json({ error: errorMessage }); } const apiUrl = `${url}/api/3/quicklook`; const headers = { "Accept-Encoding": "application/json" }; - if (settings.username && settings.password) { - headers.Authorization = `Basic ${Buffer.from(`${settings.username}:${settings.password}`).toString("base64")}` + if (instanceSettings.username && instanceSettings.password) { + headers.Authorization = `Basic ${Buffer.from(`${instanceSettings.username}:${instanceSettings.password}`).toString("base64")}` } const params = { method: "GET", headers };