Enhanced glances widget (#1534)

* Enhanced glances widget (resource match)

* Make widget clickable + cleanup helperrs

* Prevent unused glances API calls

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
pull/1542/head
Georges-Antoine Assi 12 months ago committed by GitHub
parent 3bc750bfe7
commit cdd7b2d44b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -357,10 +357,14 @@
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"load": "Load",
"wait": "Please wait",
"temp": "TEMP",
"warn": "Warn",
"uptime": "UP",
"total": "Total",
"free": "Free",
"used": "Used",
"days": "d",
"hours": "h"
},

@ -1,11 +1,14 @@
import useSWR from "swr";
import { useContext } from "react";
import { BiError } from "react-icons/bi";
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu } from "react-icons/fi";
import { FiCpu, FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next";
import UsageBar from "../resources/usage-bar";
import { SettingsContext } from "utils/contexts/settings";
const cpuSensorLabels = ["cpu_thermal", "Core", "Tctl"];
function convertToFahrenheit(t) {
@ -14,6 +17,7 @@ function convertToFahrenheit(t) {
export default function Widget({ options }) {
const { t, i18n } = useTranslation();
const { settings } = useContext(SettingsContext);
const { data, error } = useSWR(
`/api/widgets/glances?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`, {
@ -88,8 +92,16 @@ export default function Widget({ options }) {
}
const tempPercent = Math.round((mainTemp / maxTemp) * 100);
let disks = [];
if (options.disk) {
disks = Array.isArray(options.disk)
? options.disk.map((disk) => data.fs.find((d) => d.mnt_point === disk)).filter((d) => d)
: [data.fs.find((d) => d.mnt_point === options.disk)].filter((d) => d);
}
return (
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
<a href={options.url} target={settings.target ?? "_blank"} 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">
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
@ -97,7 +109,7 @@ export default function Widget({ options }) {
<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.quicklook.cpu,
value: data.cpu.total,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
@ -105,7 +117,20 @@ export default function Widget({ options }) {
</div>
<div className="pr-1">{t("glances.cpu")}</div>
</div>
<UsageBar percent={data.quicklook.cpu} />
{options.expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">
{t("common.number", {
value: data.load.min15,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
})}
</div>
<div className="pr-1">{t("glances.load")}</div>
</span>
)}
<UsageBar percent={data.cpu.total} />
</div>
</div>
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
@ -113,18 +138,46 @@ export default function Widget({ options }) {
<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.quicklook.mem,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
{t("common.bytes", {
value: data.mem.free,
maximumFractionDigits: 1,
binary: true,
})}
</div>
<div className="pr-1">{t("glances.mem")}</div>
<div className="pr-1">{t("glances.free")}</div>
</div>
<UsageBar percent={data.quicklook.mem} />
{options.expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">
{t("common.bytes", {
value: data.mem.total,
maximumFractionDigits: 1,
binary: true,
})}
</div>
<div className="pr-1">{t("glances.total")}</div>
</span>
)}
<UsageBar percent={data.mem.percent} />
</div>
</div>
{disks.map((disk) => (
<div key={disk.mnt_point} className="flex-none flex flex-row items-center mr-3 py-1.5">
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{t("common.bytes", { value: disk.free })}</div>
<div className="pr-1">{t("glances.free")}</div>
</span>
{options.expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">{t("common.bytes", { value: disk.size })}</div>
<div className="pr-1">{t("glances.total")}</div>
</span>
)}
<UsageBar percent={disk.percent} />
</div>
</div>))}
{options.cputemp && mainTemp > 0 &&
(<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" />
@ -140,6 +193,19 @@ export default function Widget({ options }) {
</div>
<div className="pr-1">{t("glances.temp")}</div>
</span>
{options.expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">
{t("common.number", {
value: maxTemp,
maximumFractionDigits: 1,
style: "unit",
unit
})}
</div>
<div className="pr-1">{t("glances.warn")}</div>
</span>
)}
<UsageBar percent={tempPercent} />
</div>
</div>)}
@ -160,6 +226,6 @@ export default function Widget({ options }) {
{options.label && (
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</div>
</a>
);
}

@ -40,20 +40,32 @@ async function retrieveFromGlancesAPI(privateWidgetOptions, endpoint) {
}
export default async function handler(req, res) {
const { index } = req.query;
const { index, cputemp: includeCpuTemp, uptime: includeUptime, disk: includeDisks } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
try {
const quicklookData = await retrieveFromGlancesAPI(privateWidgetOptions, "quicklook");
const cpuData = await retrieveFromGlancesAPI(privateWidgetOptions, "cpu");
const loadData = await retrieveFromGlancesAPI(privateWidgetOptions, "load");
const memoryData = await retrieveFromGlancesAPI(privateWidgetOptions, "mem");
const data = {
quicklook: quicklookData
cpu: cpuData,
load: loadData,
mem: memoryData,
}
// Disabled by default, dont call unless needed
if (includeUptime) {
data.uptime = await retrieveFromGlancesAPI(privateWidgetOptions, "uptime");
}
if (includeCpuTemp) {
data.sensors = await retrieveFromGlancesAPI(privateWidgetOptions, "sensors");
}
if (includeDisks) {
data.fs = await retrieveFromGlancesAPI(privateWidgetOptions, "fs");
}
data.uptime = await retrieveFromGlancesAPI(privateWidgetOptions, "uptime");
data.sensors = await retrieveFromGlancesAPI(privateWidgetOptions, "sensors");
return res.status(200).send(data);
} catch (e) {

@ -5,8 +5,6 @@ import yaml from "js-yaml";
import checkAndCopyConfig, { substituteEnvironmentVars } from "utils/config/config";
const exemptWidgets = ["search"];
export async function widgetsFromConfig() {
checkAndCopyConfig("widgets.yaml");
@ -32,15 +30,17 @@ export async function cleanWidgetGroups(widgets) {
return widgets.map((widget, index) => {
const sanitizedOptions = widget.options;
const optionKeys = Object.keys(sanitizedOptions);
if (!exemptWidgets.includes(widget.type)) {
["url", "username", "password", "key"].forEach((pO) => {
if (optionKeys.includes(pO)) {
// allow URL in search
if (widget.type !== "search" && pO !== "key") {
delete sanitizedOptions[pO];
}
}
});
// delete private options from the sanitized options
["username", "password", "key"].forEach((pO) => {
if (optionKeys.includes(pO)) {
delete sanitizedOptions[pO];
}
});
// delete url from the sanitized options if the widget is not a search or glances widgeth
if (widget.type !== "search" && widget.type !== "glances" && optionKeys.includes("url")) {
delete sanitizedOptions.url;
}
return {
@ -78,4 +78,4 @@ export async function getPrivateWidgetOptions(type, widgetIndex) {
});
return (type !== undefined && widgetIndex !== undefined) ? privateOptions.find(o => o.type === type && o.options.index === parseInt(widgetIndex, 10))?.options : privateOptions;
}
}

Loading…
Cancel
Save