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