Compare commits

..

14 Commits

Author SHA1 Message Date
shamoon
d6e7e7e790 1.12.3
Some checks are pending
Docker CI / Docker Build & Push (push) Waiting to run
Docs / Test Build Docs (push) Waiting to run
Docs / Build & Deploy Docs (push) Waiting to run
Lint / Linting Checks (push) Waiting to run
Tests / vitest (1) (push) Waiting to run
Tests / vitest (2) (push) Waiting to run
Tests / vitest (3) (push) Waiting to run
Tests / vitest (4) (push) Waiting to run
2026-04-01 08:02:58 -07:00
shamoon
24cb274e03 Fix glances regex 2026-04-01 08:02:03 -07:00
shamoon
af852e748a Normalize widget version in URLs 2026-04-01 08:00:20 -07:00
shamoon
0ea5c3fb68 Bump package version to 1.12.2
Some checks failed
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
2026-03-31 07:35:55 -07:00
shamoon
5ede96d6ce Merge branch 'dev' 2026-03-31 07:35:43 -07:00
shamoon
a7fe80a399 Documentation: fix UniFi admonitions
Some checks failed
Docker CI / Linting Checks (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Linting Checks (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Crowdin Action / Crowdin Sync (push) Has been cancelled
2026-03-28 21:05:28 -07:00
shamoon
0b61b6c1b8 Merge branch 'dev' 2026-03-27 20:23:47 -07:00
shamoon
02989a4366 Bump version to 1.12.0 2026-03-27 15:18:07 -07:00
shamoon
bc6acf7fd1 Merge branch 'dev' 2026-03-27 15:17:33 -07:00
shamoon
a4e29bc7a7 1.11.0 2026-03-14 08:58:53 -07:00
shamoon
a7982bda06 Merge branch 'dev' 2026-03-14 08:58:38 -07:00
shamoon
6b3bff1f1d Fix typo in shortcuts documentation 2026-03-07 16:13:08 -08:00
shamoon
e3ca0adf11 Documentation: add 'unit' option for temperature in glances config 2026-02-20 22:12:12 -08:00
Kristiyan Nikolov
d62404f164 Documentation: Fix doc heading for PWA/App icons (#6290) 2026-02-05 11:36:19 -08:00
25 changed files with 125 additions and 52 deletions

View File

@@ -129,7 +129,7 @@ A progressive web app is an app that can be installed on a device and provide us
More information on PWAs can be found in [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps).
## App icons
### App icons
You can set custom icons for installable apps. More information about how you can set them can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/icons).
@@ -150,7 +150,7 @@ For icon `src` you can pass either full URL or a local path relative to the `/ap
### Shortcuts
Shortcuts can e used to specify links to tabs, to be preselected when the homepage is opened as an app.
Shortcuts can be used to specify links to tabs, to be preselected when the homepage is opened as an app.
More information about how you can set them can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/shortcuts).
```yaml

View File

@@ -16,6 +16,7 @@ The Glances widget allows you to monitor the resources (CPU, memory, storage, te
cpu: true # optional, enabled by default, disable by setting to false
mem: true # optional, enabled by default, disable by setting to false
cputemp: true # disabled by default
unit: imperial # optional for temp, default is metric
uptime: true # disabled by default
disk: / # disabled by default, use mount point of disk(s) in glances. Can also be a list (see below)
diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk
@@ -31,5 +32,3 @@ disk:
- /boot
...
```
_Added in v0.4.18, updated in v0.6.11, v0.6.21_

View File

@@ -13,7 +13,7 @@ You can display general connectivity status from your Unifi (Network) Controller
An optional 'site' parameter can be supplied, if it is not the widget will use the default site for the controller.
!!! hint
!!! tip
If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container to clear the cache.

View File

@@ -17,7 +17,7 @@ An optional 'site' parameter can be supplied, if it is not the widget will use t
Allowed fields: `["uptime", "wan", "lan", "lan_users", "lan_devices", "wlan", "wlan_users", "wlan_devices"]` (maximum of four). Fields unsupported by the unifi device will not be shown.
!!! hint
!!! tip
If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container or restart the service to clear the cache.

View File

@@ -19,6 +19,6 @@ widget:
password: your_password
```
!!! hint
!!! tip
If you enter incorrect credentials and receive an "API Error", you may need to recreate the container or restart the service to clear the cache.

View File

@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.10.1",
"version": "1.12.3",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -67,9 +67,9 @@
"empty_data": "Status do Subsistema desconhecido"
},
"unifi_drive": {
"healthy": "Saudável",
"degraded": "Degradado",
"no_data": "Sem dados de armazenamento disponíveis"
"healthy": "Healthy",
"degraded": "Degraded",
"no_data": "No storage data available"
},
"docker": {
"rx": "RX",
@@ -114,9 +114,9 @@
},
"jellyfin": {
"playing": "Jogando",
"transcoding": "Transcodificando",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "Sem Transmissões Ativas",
"no_active": "No Active Streams",
"movies": "Filmes",
"series": "Séries",
"episodes": "Episódios",
@@ -190,10 +190,10 @@
"plex_connection_error": "Verifique a conexão do Plex"
},
"tracearr": {
"no_active": "Sem Transmissões Ativas",
"streams": "Transmissões",
"transcodes": "Transcodificações",
"directplay": "Reprodução direta",
"no_active": "No Active Streams",
"streams": "Streams",
"transcodes": "Transcodes",
"directplay": "Direct Play",
"bitrate": "Bitrate"
},
"omada": {
@@ -619,13 +619,13 @@
"total": "Total"
},
"pangolin": {
"orgs": "Organizações",
"orgs": "Orgs",
"sites": "Sites",
"resources": "Recursos",
"targets": "Alvos",
"traffic": "Tráfego",
"in": "Em",
"out": "Saída"
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Carga da bateria",
@@ -1164,11 +1164,11 @@
"images": "Imagens",
"image_updates": "Atualizações de Imagem",
"images_unused": "Não utilizado",
"environment_required": "ID do ambiente necessário"
"environment_required": "Environment ID Required"
},
"dockhand": {
"running": "Executando",
"stopped": "Parado",
"stopped": "Stopped",
"cpu": "CPU",
"memory": "Memória",
"images": "Imagens",
@@ -1178,12 +1178,12 @@
"stacks": "Pilhas",
"paused": "Pausado",
"total": "Total",
"environment_not_found": "Ambiente não encontrado"
"environment_not_found": "Environment Not Found"
},
"sparkyfitness": {
"eaten": "Comido",
"burned": "Queimado",
"remaining": "Restante",
"steps": "Passos"
"eaten": "Eaten",
"burned": "Burned",
"remaining": "Remaining",
"steps": "Steps"
}
}

View File

@@ -92,6 +92,23 @@ describe("pages/api/widgets/glances", () => {
expect(res.statusCode).toBe(200);
});
it("falls back to version 3 when version is invalid", async () => {
getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances" });
httpProxy
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ total: 1 }))])
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ avg: 2 }))])
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ available: 3 }))]);
const req = { query: { index: "0", version: "3/../../secret-endpoint" } };
const res = createMockRes();
await handler(req, res);
expect(httpProxy).toHaveBeenCalledWith("http://glances/api/3/cpu", expect.any(Object));
expect(res.statusCode).toBe(200);
});
it("returns 400 when glances returns 401", async () => {
getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances" });
httpProxy.mockResolvedValueOnce([401, null, Buffer.from("nope")]);

View File

@@ -1,5 +1,6 @@
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
import createLogger from "utils/logger";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http";
const logger = createLogger("glances");
@@ -45,7 +46,7 @@ export default async function handler(req, res) {
const { index, cputemp: includeCpuTemp, uptime: includeUptime, disk: includeDisks, version } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
privateWidgetOptions.version = version ?? 3;
privateWidgetOptions.version = parseVersionForUrl(version, 3);
try {
const cpuData = await retrieveFromGlancesAPI(privateWidgetOptions, "cpu");

View File

@@ -10,6 +10,7 @@ import { getKubeConfig } from "utils/config/kubernetes";
import * as shvl from "utils/config/shvl";
import kubernetes from "utils/kubernetes/export";
import createLogger from "utils/logger";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
const logger = createLogger("service-helpers");
@@ -113,7 +114,7 @@ export async function servicesFromDocker() {
}
let substitutedVal = substituteEnvironmentVars(containerLabels[label]);
if (value === "widget.version" || /^widgets\[\d+\]\.version$/.test(value)) {
substitutedVal = parseInt(substitutedVal, 10);
substitutedVal = parseVersionForUrl(substitutedVal);
}
shvl.set(constructedService, value, substitutedVal);
}
@@ -590,7 +591,7 @@ export function cleanServiceGroups(groups) {
"vikunja",
].includes(type)
) {
if (version) widget.version = parseInt(version, 10);
widget.version = parseVersionForUrl(version);
}
if (type === "glances") {
if (metric) widget.metric = metric;

View File

@@ -12,6 +12,22 @@ export function formatApiCall(url, args) {
return url.replace(find, replace).replace(find, replace);
}
export function parseVersionForUrl(version, defaultValue = null) {
if (version === undefined || version === null || version === "") {
return defaultValue;
}
if (typeof version === "number") {
return Number.isInteger(version) && version >= 0 ? version : defaultValue;
}
if (typeof version === "string" && /^\d+$/.test(version)) {
return Number(version);
}
return defaultValue;
}
export function getURLSearchParams(widget, endpoint) {
const params = new URLSearchParams({
group: widget.service_group,

View File

@@ -7,6 +7,7 @@ import {
getURLSearchParams,
jsonArrayFilter,
jsonArrayTransform,
parseVersionForUrl,
sanitizeErrorURL,
} from "./api-helpers";
@@ -21,6 +22,20 @@ describe("utils/proxy/api-helpers", () => {
expect(formatApiCall("{a}-{a}-{missing}", { a: "x" })).toBe("x-x-");
});
it("parseVersionForUrl accepts canonical non-negative integers", () => {
expect(parseVersionForUrl("3")).toBe(3);
expect(parseVersionForUrl(4)).toBe(4);
expect(parseVersionForUrl(undefined, 3)).toBe(3);
});
it("parseVersionForUrl rejects non-canonical values", () => {
expect(parseVersionForUrl("3/../../path", 3)).toBe(3);
expect(parseVersionForUrl("1e2", 3)).toBe(3);
expect(parseVersionForUrl("0x10", 3)).toBe(3);
expect(parseVersionForUrl(-1, 3)).toBe(3);
expect(parseVersionForUrl(Number.NaN, 3)).toBe(3);
});
it("getURLSearchParams includes group/service/index and optionally endpoint", () => {
const widget = { service_group: "g", service_name: "s", index: "0" };

View File

@@ -2,7 +2,7 @@ import cache from "memory-cache";
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import { asJson, formatApiCall } from "utils/proxy/api-helpers";
import { asJson, formatApiCall, parseVersionForUrl } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http";
import widgets from "widgets/widgets";
@@ -56,7 +56,7 @@ async function getApiInfo(serviceWidget, apiName, serviceName) {
const json = asJson(data);
if (json?.data?.[apiName]) {
cgiPath = json.data[apiName].path;
maxVersion = json.data[apiName].maxVersion;
maxVersion = parseVersionForUrl(json.data[apiName].maxVersion);
logger.debug(
`Detected ${serviceWidget.type}: apiName '${apiName}', cgiPath '${cgiPath}', and maxVersion ${maxVersion}`,
);

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const statusMap = {
@@ -19,11 +20,12 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const idKey = version === 3 ? "Id" : "id";
const statusKey = version === 3 ? "Status" : "status";
const idKey = apiVersion === 3 ? "Id" : "id";
const statusKey = apiVersion === 3 ? "Status" : "status";
const { data, error } = useWidgetAPI(service.widget, `${version}/containers`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/containers`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const Chart = dynamic(() => import("../components/chart"), { ssr: false });
@@ -16,14 +17,15 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/cpu`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/cpu`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});
const { data: quicklookData, error: quicklookError } = useWidgetAPI(service.widget, `${version}/quicklook`);
const { data: quicklookData, error: quicklookError } = useWidgetAPI(service.widget, `${apiVersion}/quicklook`);
useEffect(() => {
if (data) {

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -16,6 +17,7 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, diskName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState(
@@ -23,7 +25,7 @@ export default function Component({ service }) {
);
const [ratePoints, setRatePoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/diskio`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/diskio`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const defaultInterval = 1000;
@@ -11,10 +12,11 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, fsName] = widget.metric.split("fs:");
const diskUnits = widget.diskUnits === "bbytes" ? "common.bbytes" : "common.bytes";
const { data, error } = useWidgetAPI(widget, `${version}/fs`, {
const { data, error } = useWidgetAPI(widget, `${apiVersion}/fs`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -16,11 +17,12 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, gpuName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(widget, `${version}/gpu`, {
const { data, error } = useWidgetAPI(widget, `${apiVersion}/gpu`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
function Swap({ quicklookData, className = "" }) {
@@ -75,12 +76,13 @@ const defaultSystemInterval = 30000; // This data (OS, hostname, distribution) i
export default function Component({ service }) {
const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const { data: quicklookData, errorL: quicklookError } = useWidgetAPI(service.widget, `${version}/quicklook`, {
const { data: quicklookData, errorL: quicklookError } = useWidgetAPI(service.widget, `${apiVersion}/quicklook`, {
refreshInterval,
});
const { data: systemData, errorL: systemError } = useWidgetAPI(service.widget, `${version}/system`, {
const { data: systemData, errorL: systemError } = useWidgetAPI(service.widget, `${apiVersion}/system`, {
refreshInterval: defaultSystemInterval,
});

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -17,10 +18,11 @@ export default function Component({ service }) {
const { widget } = service;
const { chart } = widget;
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/mem`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/mem`, {
refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
});

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -17,15 +18,16 @@ export default function Component({ service }) {
const { widget } = service;
const { chart, metric } = widget;
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const rxKey = version === 3 ? "rx" : "bytes_recv";
const txKey = version === 3 ? "tx" : "bytes_sent";
const rxKey = apiVersion === 3 ? "rx" : "bytes_recv";
const txKey = apiVersion === 3 ? "tx" : "bytes_sent";
const [, interfaceName] = metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(widget, `${version}/network`, {
const { data, error } = useWidgetAPI(widget, `${apiVersion}/network`, {
refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
});

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const statusMap = {
@@ -22,10 +23,11 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const memoryInfoKey = version === 3 ? 0 : "rss";
const memoryInfoKey = apiVersion === 3 ? 0 : "rss";
const { data, error } = useWidgetAPI(service.widget, `${version}/processlist`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/processlist`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block";
import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api";
const Chart = dynamic(() => import("../components/chart"), { ssr: false });
@@ -16,11 +17,12 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, sensorName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/sensors`, {
const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/sensors`, {
refreshInterval: Math.max(defaultInterval, refreshInterval),
});

View File

@@ -3,7 +3,7 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/api/{endpoint}",
proxyHandler: credentialedProxyHandler,
allowedEndpoints: /\d\/quicklook|diskio|cpu|fs|gpu|system|mem|network|processlist|sensors|containers/,
allowedEndpoints: /^\d+\/(quicklook|diskio|cpu|fs|gpu|system|mem|network|processlist|sensors|containers)$/,
};
export default widget;

View File

@@ -8,6 +8,10 @@ describe("glances widget config", () => {
it("exports a valid widget config", () => {
expectWidgetConfigShape(widget);
expect(widget.allowedEndpoints?.test("3/quicklook")).toBe(true);
expect(widget.allowedEndpoints?.test("12/cpu")).toBe(true);
expect(widget.allowedEndpoints?.test("unknown")).toBe(false);
expect(widget.allowedEndpoints?.test("xxcpuyy")).toBe(false);
expect(widget.allowedEndpoints?.test("3/cpu/extra")).toBe(false);
expect(widget.allowedEndpoints?.test("membrane")).toBe(false);
});
});