diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 1261c293..d53d480c 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 00000000..a48cef50 --- /dev/null +++ b/src/components/widgets/glances/glances.jsx @@ -0,0 +1,110 @@ +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 ac5353eb..bd31ed93 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 00000000..86992dd1 --- /dev/null +++ b/src/pages/api/widgets/glances.js @@ -0,0 +1,52 @@ +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 { 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 = instanceSettings?.url; + if (!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 (instanceSettings.username && instanceSettings.password) { + headers.Authorization = `Basic ${Buffer.from(`${instanceSettings.username}:${instanceSettings.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. Data: %s", status, data); + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(data); +}