From 50ccb0b14d85a11e29b4614a28642a70ade22170 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:51:20 -0700 Subject: [PATCH 001/195] Docs: make it a warning admonition --- docs/installation/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/index.md b/docs/installation/index.md index 778b9ef2..d954cf37 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -8,7 +8,7 @@ icon: simple/docker You have a few options for deploying homepage, depending on your needs. We offer docker images for a majority of platforms. You can also install and run homepage from source if Docker is not your thing. It can even be installed on Kubernetes with Helm.

-!!! danger +!!! warning Please note that when using features such as widgets, Homepage can access personal information (for example from your home automation system) and Homepage currently does not (and is not planned to) include any authentication layer itself. Thus, we recommend homepage be deployed behind a reverse proxy including authentication, SSL etc, and / or behind a VPN. From 19bdc0ec34a8bc9576d71e6df7b182e192c6b814 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:01:42 -0700 Subject: [PATCH 002/195] Enhancement: handle immich v1.118 breaking API change (#4110) --- docs/widgets/services/immich.md | 6 ++++++ src/utils/config/service-helpers.js | 6 +++--- src/widgets/immich/component.jsx | 18 +++++++++++++----- src/widgets/immich/widget.js | 14 ++++++++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/docs/widgets/services/immich.md b/docs/widgets/services/immich.md index 63780ee0..4da6e187 100644 --- a/docs/widgets/services/immich.md +++ b/docs/widgets/services/immich.md @@ -5,6 +5,11 @@ description: Immich Widget Configuration Learn more about [Immich](https://github.com/immich-app/immich). +| Immich Version | Homepage Widget Version | +| -------------- | ----------------------- | +| < v1.118 | 1 (default) | +| >= v1.118 | 2 | + Find your API key under `Account Settings > API Keys`. Allowed fields: `["users" ,"photos", "videos", "storage"]`. @@ -16,4 +21,5 @@ widget: type: immich url: http://immich.host.or.ip key: adminapikeyadminapikeyadminapikey + version: 2 # optional, default is 1 ``` diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 6138d2e9..24ba57e2 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -406,7 +406,7 @@ export function cleanServiceGroups(groups) { // frigate enableRecentEvents, - // glances, mealie, pihole, pfsense + // glances, immich, mealie, pihole, pfsense version, // glances @@ -568,8 +568,8 @@ export function cleanServiceGroups(groups) { if (snapshotHost) cleanedService.widget.snapshotHost = snapshotHost; if (snapshotPath) cleanedService.widget.snapshotPath = snapshotPath; } - if (["glances", "mealie", "pfsense", "pihole"].includes(type)) { - if (version) cleanedService.widget.version = version; + if (["glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) { + if (version) cleanedService.widget.version = parseInt(version, 10); } if (type === "glances") { if (metric) cleanedService.widget.metric = metric; diff --git a/src/widgets/immich/component.jsx b/src/widgets/immich/component.jsx index 5f5bbedd..ed27d4d8 100644 --- a/src/widgets/immich/component.jsx +++ b/src/widgets/immich/component.jsx @@ -8,11 +8,19 @@ export default function Component({ service }) { const { t } = useTranslation(); const { widget } = service; - const { data: versionData, error: versionError } = useWidgetAPI(widget, "version"); - // see https://github.com/gethomepage/homepage/issues/2282 - const endpoint = - versionData?.major > 1 || (versionData?.major === 1 && versionData?.minor > 84) ? "statistics" : "stats"; - const { data: immichData, error: immichError } = useWidgetAPI(widget, endpoint); + const { version = 1 } = widget; + + const versionEndpoint = version === 2 ? "version_v2" : "version"; + + const { data: versionData, error: versionError } = useWidgetAPI(widget, versionEndpoint); + + let statsEndpoint = version === 2 ? "statistics_v2" : "stats"; + if (version === 1) { + // see https://github.com/gethomepage/homepage/issues/2282 + statsEndpoint = + versionData?.major > 1 || (versionData?.major === 1 && versionData?.minor > 84) ? "statistics" : "stats"; + } + const { data: immichData, error: immichError } = useWidgetAPI(widget, statsEndpoint); if (immichError || versionError || immichData?.statusCode === 401) { return ; diff --git a/src/widgets/immich/widget.js b/src/widgets/immich/widget.js index 230f8ab2..da3308c2 100644 --- a/src/widgets/immich/widget.js +++ b/src/widgets/immich/widget.js @@ -1,18 +1,24 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { - api: "{url}/api/server-info/{endpoint}", + api: "{url}/api/{endpoint}", proxyHandler: credentialedProxyHandler, mappings: { version: { - endpoint: "version", + endpoint: "server-info/version", }, statistics: { - endpoint: "statistics", + endpoint: "server-info/statistics", }, stats: { - endpoint: "stats", + endpoint: "server-info/stats", + }, + version_v2: { + endpoint: "server/version", + }, + statistics_v2: { + endpoint: "server/statistics", }, }, }; From e6c769267706dd3b3b82f1d0ede5a6f87fb6bf1c Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:50:09 -0700 Subject: [PATCH 003/195] Fix: add noreferrer to bookmark links (#4112) --- src/components/bookmarks/item.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index 5d3b351b..dcfcd43f 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -13,6 +13,7 @@ export default function Item({ bookmark }) { Date: Sat, 12 Oct 2024 10:53:02 -0400 Subject: [PATCH 004/195] Feature: Vikunja service widget (#4118) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/index.md | 1 + docs/widgets/services/vikunja.md | 18 +++++++ mkdocs.yml | 1 + public/locales/en/common.json | 6 +++ src/utils/config/service-helpers.js | 6 +++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/components.js | 1 + src/widgets/vikunja/component.jsx | 68 ++++++++++++++++++++++++ src/widgets/vikunja/widget.js | 27 ++++++++++ src/widgets/widgets.js | 2 + 10 files changed, 131 insertions(+) create mode 100644 docs/widgets/services/vikunja.md create mode 100644 src/widgets/vikunja/component.jsx create mode 100644 src/widgets/vikunja/widget.js diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 3462fef3..91f91d38 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -128,6 +128,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Uptime Kuma](uptime-kuma.md) - [UptimeRobot](uptimerobot.md) - [UrBackup](urbackup.md) +- [Vikunja](vikunja.md) - [Watchtower](watchtower.md) - [WGEasy](wgeasy.md) - [WhatsUpDocker](whatsupdocker.md) diff --git a/docs/widgets/services/vikunja.md b/docs/widgets/services/vikunja.md new file mode 100644 index 00000000..94b99055 --- /dev/null +++ b/docs/widgets/services/vikunja.md @@ -0,0 +1,18 @@ +--- +title: Vikunja +description: Vikunja Widget Configuration +--- + +Learn more about [Vikunja](https://vikunja.io). + +Allowed fields: `["projects", "tasks7d", "tasksOverdue", "tasksInProgress"]`. + +A list of the next 5 tasks ordered by due date is disabled by default, but can be enabled with the `enableTaskList` option. + +```yaml +widget: + type: vikunja + url: http[s]://vikunja.host.or.ip[:port] + key: vikunjaapikey + enableTaskList: true # optional, defaults to false +``` diff --git a/mkdocs.yml b/mkdocs.yml index 1b3afd62..0667e5ea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -151,6 +151,7 @@ nav: - widgets/services/uptime-kuma.md - widgets/services/uptimerobot.md - widgets/services/urbackup.md + - widgets/services/vikunja.md - widgets/services/watchtower.md - widgets/services/wgeasy.md - widgets/services/whatsupdocker.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 3aea07eb..384f44be 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -953,5 +953,11 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 24ba57e2..9d55ce43 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -478,6 +478,9 @@ export function cleanServiceGroups(groups) { // unifi site, + // vikunja + enableTaskList, + // wgeasy threshold, @@ -633,6 +636,9 @@ export function cleanServiceGroups(groups) { if (type === "lubelogger") { if (vehicleID !== undefined) cleanedService.widget.vehicleID = parseInt(vehicleID, 10); } + if (type === "vikunja") { + if (enableTaskList !== undefined) cleanedService.widget.enableTaskList = !!enableTaskList; + } } return cleanedService; diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 39822075..e907912d 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -44,6 +44,7 @@ export default async function credentialedProxyHandler(req, res, map) { "tailscale", "tandoor", "pterodactyl", + "vikunja", ].includes(widget.type) ) { headers.Authorization = `Bearer ${widget.key}`; diff --git a/src/widgets/components.js b/src/widgets/components.js index 0a5a815c..62bd479f 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -125,6 +125,7 @@ const components = { uptimekuma: dynamic(() => import("./uptimekuma/component")), uptimerobot: dynamic(() => import("./uptimerobot/component")), urbackup: dynamic(() => import("./urbackup/component")), + vikunja: dynamic(() => import("./vikunja/component")), watchtower: dynamic(() => import("./watchtower/component")), wgeasy: dynamic(() => import("./wgeasy/component")), whatsupdocker: dynamic(() => import("./whatsupdocker/component")), diff --git a/src/widgets/vikunja/component.jsx b/src/widgets/vikunja/component.jsx new file mode 100644 index 00000000..09704338 --- /dev/null +++ b/src/widgets/vikunja/component.jsx @@ -0,0 +1,68 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: projectsData, error: projectsError } = useWidgetAPI(widget, "projects"); + const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "tasks"); + + if (projectsError || tasksError) { + return ; + } + + if (!projectsData || !tasksData) { + return ( + + + + + + + ); + } + + const projects = projectsData.filter((project) => project.id > 0); // saved filters have id < 0 + + const oneWeekFromNow = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); + const tasksWithDueDate = tasksData.filter((task) => !task.dueDateIsDefault); + const tasks7d = tasksWithDueDate.filter((task) => new Date(task.dueDate) <= oneWeekFromNow); + const tasksOverdue = tasksWithDueDate.filter((task) => new Date(task.dueDate) <= new Date(Date.now())); + const tasksInProgress = tasksData.filter((task) => task.inProgress); + + return ( + <> + + + + + + + {widget.enableTaskList && + tasksData.slice(0, 5).map((task) => ( +
+
+
+ {task.title} +
+
+ {!task.dueDateIsDefault && ( +
+ {t("common.relativeDate", { + value: task.dueDate, + formatParams: { value: { style: "narrow", numeric: "auto" } }, + })} +
+ )} +
+ ))} + + ); +} diff --git a/src/widgets/vikunja/widget.js b/src/widgets/vikunja/widget.js new file mode 100644 index 00000000..9a192026 --- /dev/null +++ b/src/widgets/vikunja/widget.js @@ -0,0 +1,27 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +import { asJson } from "utils/proxy/api-helpers"; + +const widget = { + api: `{url}/api/v1/{endpoint}`, + proxyHandler: credentialedProxyHandler, + + mappings: { + projects: { + endpoint: "projects", + }, + tasks: { + endpoint: "tasks/all?filter=done%3Dfalse&sort_by=due_date", + map: (data) => + asJson(data).map((task) => ({ + id: task.id, + title: task.title, + priority: task.priority, + dueDate: task.due_date, + dueDateIsDefault: task.due_date === "0001-01-01T00:00:00Z", + inProgress: task.percent_done > 0 && task.percent_done < 1, + })), + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 3334e47e..faff57eb 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -115,6 +115,7 @@ import unifi from "./unifi/widget"; import unmanic from "./unmanic/widget"; import uptimekuma from "./uptimekuma/widget"; import uptimerobot from "./uptimerobot/widget"; +import vikunja from "./vikunja/widget"; import watchtower from "./watchtower/widget"; import wgeasy from "./wgeasy/widget"; import whatsupdocker from "./whatsupdocker/widget"; @@ -246,6 +247,7 @@ const widgets = { uptimekuma, uptimerobot, urbackup, + vikunja, watchtower, wgeasy, whatsupdocker, From db1fb4b8999f78480d358ac0efe76fa37335f0c0 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:30:45 -0700 Subject: [PATCH 005/195] Enhancement: support netalertX token for password-protected instances (#4122) --- docs/widgets/services/netalertx.md | 3 +++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/netalertx/widget.js | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/widgets/services/netalertx.md b/docs/widgets/services/netalertx.md index 4579d74c..1f618182 100644 --- a/docs/widgets/services/netalertx.md +++ b/docs/widgets/services/netalertx.md @@ -9,8 +9,11 @@ _Note that the project was renamed from PiAlert to NetAlertX._ Allowed fields: `["total", "connected", "new_devices", "down_alerts"]`. +If you have enabled a password on your NetAlertX instance, you will need to provide the `SYNC_api_token` as the `key` in your config. + ```yaml widget: type: netalertx url: http://ip:port + key: netalertxsyncapitoken # optional, only if password is enabled ``` diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index e907912d..b1b9922c 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -41,6 +41,7 @@ export default async function credentialedProxyHandler(req, res, map) { "ghostfolio", "linkwarden", "mealie", + "netalertx", "tailscale", "tandoor", "pterodactyl", diff --git a/src/widgets/netalertx/widget.js b/src/widgets/netalertx/widget.js index b4329519..033d3d81 100644 --- a/src/widgets/netalertx/widget.js +++ b/src/widgets/netalertx/widget.js @@ -1,8 +1,8 @@ -import genericProxyHandler from "utils/proxy/handlers/generic"; +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { api: "{url}/php/server/devices.php?action=getDevicesTotals", - proxyHandler: genericProxyHandler, + proxyHandler: credentialedProxyHandler, mappings: { data: { From cf9109384ee30937dff7c1a5872184ddf69ed21d Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:50:47 -0700 Subject: [PATCH 006/195] Update support.yml --- .github/DISCUSSION_TEMPLATE/support.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/DISCUSSION_TEMPLATE/support.yml b/.github/DISCUSSION_TEMPLATE/support.yml index 8471940b..2493df22 100644 --- a/.github/DISCUSSION_TEMPLATE/support.yml +++ b/.github/DISCUSSION_TEMPLATE/support.yml @@ -5,7 +5,7 @@ body: ### ⚠️ Before opening a discussion: - [Check the troubleshooting guide](https://gethomepage.dev/troubleshooting/). - - [Search existing issues](https://github.com/gethomepage/homepage/search?q=&type=issues) [and discussions](https://github.com/gethomepage/homepage/search?q=&type=discussions). + - [Search existing issues](https://github.com/gethomepage/homepage/search?q=&type=issues) [and discussions](https://github.com/gethomepage/homepage/search?q=&type=discussions) (including closed ones!). - type: textarea id: description attributes: From aac573a48d2536ded87d8c6abed75233adc4e215 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:57:35 -0700 Subject: [PATCH 007/195] =?UTF-8?q?Enhancement:=20npm=20widget=20support?= =?UTF-8?q?=20=E2=89=A5=20v2.12=20(#4140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/npm/component.jsx | 4 ++-- src/widgets/npm/proxy.js | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/widgets/npm/component.jsx b/src/widgets/npm/component.jsx index 06ac91eb..e9f7e871 100644 --- a/src/widgets/npm/component.jsx +++ b/src/widgets/npm/component.jsx @@ -21,8 +21,8 @@ export default function Component({ service }) { ); } - const enabled = infoData.filter((c) => c.enabled === 1).length; - const disabled = infoData.filter((c) => c.enabled === 0).length; + const enabled = infoData.filter((c) => !!c.enabled).length; + const disabled = infoData.filter((c) => !c.enabled).length; const total = infoData.length; return ( diff --git a/src/widgets/npm/proxy.js b/src/widgets/npm/proxy.js index 92a0140b..3f1e3495 100644 --- a/src/widgets/npm/proxy.js +++ b/src/widgets/npm/proxy.js @@ -30,7 +30,7 @@ async function login(loginUrl, username, password, service) { cache.put(`${tokenCacheKey}.${service}`, data.token, expiration - 5 * 60 * 1000); // expiration -5 minutes } } catch (e) { - logger.error(`Error ${status} logging into npm`, authResponse[2]); + logger.error(`Error ${status} logging into npm`, JSON.stringify(authResponse[2])); } return [status, data.token ?? data]; } @@ -50,7 +50,6 @@ export default async function npmProxyHandler(req, res) { const loginUrl = `${widget.url}/api/tokens`; let status; - let contentType; let data; let token = cache.get(`${tokenCacheKey}.${service}`); @@ -62,7 +61,7 @@ export default async function npmProxyHandler(req, res) { } } - [status, contentType, data] = await httpProxy(url, { + [status, , data] = await httpProxy(url, { method: "GET", headers: { "Content-Type": "application/json", @@ -81,7 +80,7 @@ export default async function npmProxyHandler(req, res) { } // eslint-disable-next-line no-unused-vars - [status, contentType, data] = await httpProxy(url, { + [status, , data] = await httpProxy(url, { method: "GET", headers: { "Content-Type": "application/json", From c3476774022c4155b38ad62155fd63b1b06f2e9e Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:04:26 -0700 Subject: [PATCH 008/195] Remove a random commented out line --- src/widgets/uptimekuma/widget.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/uptimekuma/widget.js b/src/widgets/uptimekuma/widget.js index 4f5ab602..a81069d7 100644 --- a/src/widgets/uptimekuma/widget.js +++ b/src/widgets/uptimekuma/widget.js @@ -1,4 +1,3 @@ -// import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; import genericProxyHandler from "utils/proxy/handlers/generic"; const widget = { From 6e8fe458183bb50451a4b7a75322ded6c4684600 Mon Sep 17 00:00:00 2001 From: Marco Hofmann Date: Wed, 23 Oct 2024 15:58:35 +0200 Subject: [PATCH 009/195] Documentation: fix typo in zabbix doc (#4178) --- docs/widgets/services/zabbix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/zabbix.md b/docs/widgets/services/zabbix.md index 47069075..975f4926 100644 --- a/docs/widgets/services/zabbix.md +++ b/docs/widgets/services/zabbix.md @@ -3,7 +3,7 @@ title: Zabbix description: Zabbix Widget Configuration --- -Learn more about [Zabbix](https://github.com/zabbix/zabbix). The widget supports (at least) Zibbax server version 7.0. +Learn more about [Zabbix](https://github.com/zabbix/zabbix). The widget supports (at least) Zabbix server version 7.0. --- From 7cbba1ff9043c171db648d5b4b508db5f63ee97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Sch=C3=A4ffer?= Date: Fri, 25 Oct 2024 11:07:35 +0200 Subject: [PATCH 010/195] Fix: remove deprecated meta tag (#4191) --- src/pages/_document.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index 64dd7306..bfe3fc93 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -8,7 +8,7 @@ export default function Document() { name="description" content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations." /> - + From bf2efce74d4218025646975bb1a19c9589b77654 Mon Sep 17 00:00:00 2001 From: Mark <3046690+markp-mckinney@users.noreply.github.com> Date: Sat, 26 Oct 2024 20:24:46 -0700 Subject: [PATCH 011/195] Chore: filter Radarr movie response (#4199) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/widgets/radarr/widget.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widgets/radarr/widget.js b/src/widgets/radarr/widget.js index 7ce412c6..7d370378 100644 --- a/src/widgets/radarr/widget.js +++ b/src/widgets/radarr/widget.js @@ -12,7 +12,10 @@ const widget = { wanted: jsonArrayFilter(data, (item) => item.monitored && !item.hasFile && item.isAvailable).length, have: jsonArrayFilter(data, (item) => item.hasFile).length, missing: jsonArrayFilter(data, (item) => item.monitored && !item.hasFile).length, - all: asJson(data), + all: asJson(data).map((entry) => ({ + title: entry.title, + id: entry.id, + })), }), }, "queue/status": { From e9a31bafab42c92757866e73a40c5849232b7c51 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:16:32 -0700 Subject: [PATCH 012/195] Update PR guidelines --- docs/widgets/authoring/getting-started.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/widgets/authoring/getting-started.md b/docs/widgets/authoring/getting-started.md index a01b2eee..4b9126ea 100644 --- a/docs/widgets/authoring/getting-started.md +++ b/docs/widgets/authoring/getting-started.md @@ -48,15 +48,14 @@ self-hosted / open-source alternative, we ask that any widgets, etc. are develop ## New Feature Guidelines -- New features should be linked to an existing feature request with at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users. -- If you have ideas for a larger feature, please open a discussion first. -- Please note that though it is a requirement, a discussion with 10 'up-votes' in no way guarantees that a PR will be merged. +- New features should usually be linked to an existing feature request. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users. +- If you have ideas for a larger feature you may want to open a discussion first. ## Service Widget Guidelines To ensure cohesiveness of various widgets, the following should be used as a guide for developing new widgets: -- Please only submit widgets that have been requested and have at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users. +- Please only submit widgets that target a feature request discussion with at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users. - Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control. - Widgets should be only one row of blocks - Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets From f473569cb2a340530839327bc8b2b9a0815b03ed Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:16:32 -0700 Subject: [PATCH 013/195] Update PR guidelines --- docs/widgets/authoring/getting-started.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/widgets/authoring/getting-started.md b/docs/widgets/authoring/getting-started.md index a01b2eee..4b9126ea 100644 --- a/docs/widgets/authoring/getting-started.md +++ b/docs/widgets/authoring/getting-started.md @@ -48,15 +48,14 @@ self-hosted / open-source alternative, we ask that any widgets, etc. are develop ## New Feature Guidelines -- New features should be linked to an existing feature request with at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users. -- If you have ideas for a larger feature, please open a discussion first. -- Please note that though it is a requirement, a discussion with 10 'up-votes' in no way guarantees that a PR will be merged. +- New features should usually be linked to an existing feature request. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users. +- If you have ideas for a larger feature you may want to open a discussion first. ## Service Widget Guidelines To ensure cohesiveness of various widgets, the following should be used as a guide for developing new widgets: -- Please only submit widgets that have been requested and have at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users. +- Please only submit widgets that target a feature request discussion with at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users. - Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control. - Widgets should be only one row of blocks - Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets From 2a6debbc79cd496b654611a948173e585def2dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Jasi=C5=84ski?= Date: Mon, 28 Oct 2024 15:58:24 +0100 Subject: [PATCH 014/195] Documentation: Typo in LubeLogger widget documentation (#4205) --- docs/widgets/services/lubelogger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/lubelogger.md b/docs/widgets/services/lubelogger.md index baa0e4d5..cf2b6439 100644 --- a/docs/widgets/services/lubelogger.md +++ b/docs/widgets/services/lubelogger.md @@ -8,7 +8,7 @@ Learn more about [LubeLogger](https://github.com/hargata/lubelog) (v1.3.7 or hig The widget comes in two 'flavors', one shows data for all vehicles or for just a specific vehicle with the `vehicleID` parameter. Allowed fields: `["vehicles", "serviceRecords", "reminders"]`. -For the single-vehicle version: `["vehicle", "serviceRecords", "reminders", "nextReminder"] +For the single-vehicle version: `["vehicle", "serviceRecords", "reminders", "nextReminder"]`. ```yaml widget: From 261c907f524e7c9280a70b813d038ad7f6fc9fd8 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:27:11 -0700 Subject: [PATCH 015/195] Documentation: add correct pihole v6 password info --- docs/widgets/services/pihole.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/pihole.md b/docs/widgets/services/pihole.md index c8ab83ae..d77b2dfc 100644 --- a/docs/widgets/services/pihole.md +++ b/docs/widgets/services/pihole.md @@ -14,5 +14,5 @@ widget: type: pihole url: http://pi.hole.or.ip version: 6 # required if running v6 or higher, defaults to 5 - key: yourpiholeapikey # optional + key: yourpiholeapikey # optional, in v6 can be your password or app password ``` From a938f8b9faae455a64922dd4a0f4bfb8f9633b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Jasi=C5=84ski?= Date: Mon, 28 Oct 2024 15:58:24 +0100 Subject: [PATCH 016/195] Documentation: Typo in LubeLogger widget documentation (#4205) --- docs/widgets/services/lubelogger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/lubelogger.md b/docs/widgets/services/lubelogger.md index baa0e4d5..cf2b6439 100644 --- a/docs/widgets/services/lubelogger.md +++ b/docs/widgets/services/lubelogger.md @@ -8,7 +8,7 @@ Learn more about [LubeLogger](https://github.com/hargata/lubelog) (v1.3.7 or hig The widget comes in two 'flavors', one shows data for all vehicles or for just a specific vehicle with the `vehicleID` parameter. Allowed fields: `["vehicles", "serviceRecords", "reminders"]`. -For the single-vehicle version: `["vehicle", "serviceRecords", "reminders", "nextReminder"] +For the single-vehicle version: `["vehicle", "serviceRecords", "reminders", "nextReminder"]`. ```yaml widget: From 01cb9e8830166eee01f75f6720576c8525d8977b Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:27:11 -0700 Subject: [PATCH 017/195] Documentation: add correct pihole v6 password info --- docs/widgets/services/pihole.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/pihole.md b/docs/widgets/services/pihole.md index c8ab83ae..d77b2dfc 100644 --- a/docs/widgets/services/pihole.md +++ b/docs/widgets/services/pihole.md @@ -14,5 +14,5 @@ widget: type: pihole url: http://pi.hole.or.ip version: 6 # required if running v6 or higher, defaults to 5 - key: yourpiholeapikey # optional + key: yourpiholeapikey # optional, in v6 can be your password or app password ``` From 0aea6a6c3ffdc93ca6414e0b84b99ff31ce6f00d Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:37:18 -0700 Subject: [PATCH 018/195] Fix: pyload widget - encode proxy params (#4210) --- src/widgets/pyload/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/pyload/proxy.js b/src/widgets/pyload/proxy.js index 9cdd5231..d9469d1c 100644 --- a/src/widgets/pyload/proxy.js +++ b/src/widgets/pyload/proxy.js @@ -15,7 +15,7 @@ async function fetchFromPyloadAPI(url, sessionId, params, service) { const options = { body: params ? Object.keys(params) - .map((prop) => `${prop}=${params[prop]}`) + .map((prop) => `${prop}=${encodeURIComponent(params[prop])}`) .join("&") : `session=${sessionId}`, method: "POST", From 3af86ffebb7e9942fe6b1551ab9bb9b443959fea Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Wed, 30 Oct 2024 00:17:09 -0400 Subject: [PATCH 019/195] Fix: text overflowing in bookmarks (#4217) Co-Authored-By: shamoon <4887959+shamoon@users.noreply.github.com> --- src/components/bookmarks/group.jsx | 2 +- src/components/bookmarks/item.jsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/bookmarks/group.jsx b/src/components/bookmarks/group.jsx index b13aeac1..3a9f8323 100644 --- a/src/components/bookmarks/group.jsx +++ b/src/components/bookmarks/group.jsx @@ -20,7 +20,7 @@ export default function BookmarksGroup({ bookmarks, layout, disableCollapse, gro className={classNames( "bookmark-group", layout?.style === "row" ? "basis-full" : "basis-full md:basis-1/4 lg:basis-1/5 xl:basis-1/6", - layout?.header === false ? "flex-1 px-1 -my-1" : "flex-1 p-1", + layout?.header === false ? "flex-1 px-1 -my-1 overflow-hidden" : "flex-1 p-1 overflow-hidden", )} > diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index dcfcd43f..69097089 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -29,9 +29,9 @@ export default function Item({ bookmark }) { )} {!bookmark.icon && bookmark.abbr} -
-
{bookmark.name}
-
+
+
{bookmark.name}
+
{description}
From 3736c1fcababba4ccb43e2c02aba1523d52b70a9 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:20:08 -0700 Subject: [PATCH 020/195] Fix: use same unit default for openmeteo widget and api (#4227) --- src/pages/api/widgets/openmeteo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/widgets/openmeteo.js b/src/pages/api/widgets/openmeteo.js index b34938b1..e63847b4 100644 --- a/src/pages/api/widgets/openmeteo.js +++ b/src/pages/api/widgets/openmeteo.js @@ -2,7 +2,7 @@ import cachedFetch from "utils/proxy/cached-fetch"; export default async function handler(req, res) { const { latitude, longitude, units, cache, timezone } = req.query; - const degrees = units === "imperial" ? "fahrenheit" : "celsius"; + const degrees = units === "metric" ? "celsius" : "fahrenheit"; const timezeone = timezone ?? "auto"; const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset¤t_weather=true&temperature_unit=${degrees}&timezone=${timezeone}`; return res.send(await cachedFetch(apiUrl, cache)); From e6cf86ed4a06a48bfb34d7c0217325395f754bad Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:09:23 -0700 Subject: [PATCH 021/195] Enhancement: use duration for audiobookshelf tottal, refactor uptime (#4229) --- docs/widgets/authoring/translations.md | 2 +- next-i18next.config.js | 14 +++++++------- public/locales/en/common.json | 2 +- src/components/widgets/resources/uptime.jsx | 2 +- src/widgets/audiobookshelf/component.jsx | 7 ++----- src/widgets/frigate/component.jsx | 2 +- src/widgets/fritzbox/component.jsx | 2 +- src/widgets/openwrt/methods/system.jsx | 2 +- src/widgets/stash/component.jsx | 4 ++-- src/widgets/truenas/component.jsx | 2 +- src/widgets/uptimerobot/component.jsx | 4 ++-- 11 files changed, 20 insertions(+), 23 deletions(-) diff --git a/docs/widgets/authoring/translations.md b/docs/widgets/authoring/translations.md index 13473f05..77ad0703 100644 --- a/docs/widgets/authoring/translations.md +++ b/docs/widgets/authoring/translations.md @@ -71,7 +71,7 @@ Homepage provides a set of common translations that you can use in your widgets. | `common.ms` | `1,000 ms` | Format a number in milliseconds. | | `common.date` | `2024-01-01` | Format a date. | | `common.relativeDate` | `1 day ago` | Format a relative date. | -| `common.uptime` | `1 day, 1 hour` | Format an uptime. | +| `common.duration` | `1 day, 1 hour` | Format an duration. | ### Text diff --git a/next-i18next.config.js b/next-i18next.config.js index 96483cc3..a1b5c7b3 100644 --- a/next-i18next.config.js +++ b/next-i18next.config.js @@ -84,12 +84,12 @@ function prettyBytes(number, options) { return `${prefix + numberString} ${unit}`; } -function uptime(uptimeInSeconds, i18next) { - const mo = Math.floor(uptimeInSeconds / (3600 * 24 * 31)); - const d = Math.floor((uptimeInSeconds % (3600 * 24 * 31)) / (3600 * 24)); - const h = Math.floor((uptimeInSeconds % (3600 * 24)) / 3600); - const m = Math.floor((uptimeInSeconds % 3600) / 60); - const s = Math.floor(uptimeInSeconds % 60); +function duration(durationInSeconds, i18next) { + const mo = Math.floor(durationInSeconds / (3600 * 24 * 31)); + const d = Math.floor((durationInSeconds % (3600 * 24 * 31)) / (3600 * 24)); + const h = Math.floor((durationInSeconds % (3600 * 24)) / 3600); + const m = Math.floor((durationInSeconds % 3600) / 60); + const s = Math.floor(durationInSeconds % 60); const moDisplay = mo > 0 ? mo + i18next.t("common.months") : ""; const dDisplay = d > 0 ? d + i18next.t("common.days") : ""; @@ -156,7 +156,7 @@ module.exports = { i18next.services.formatter.add("relativeDate", (value, lng, options) => relativeDate(new Date(value), new Intl.RelativeTimeFormat(lng, { ...options })), ); - i18next.services.formatter.add("uptime", (value, lng) => uptime(value, i18next)); + i18next.services.formatter.add("duration", (value, lng) => duration(value, i18next)); }, type: "3rdParty", }, diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 384f44be..fd52aef4 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", diff --git a/src/components/widgets/resources/uptime.jsx b/src/components/widgets/resources/uptime.jsx index 025d1aa5..2fac0fcd 100644 --- a/src/components/widgets/resources/uptime.jsx +++ b/src/components/widgets/resources/uptime.jsx @@ -25,7 +25,7 @@ export default function Uptime({ refresh = 1500 }) { return ( diff --git a/src/widgets/audiobookshelf/component.jsx b/src/widgets/audiobookshelf/component.jsx index 406a81c2..6eb78638 100755 --- a/src/widgets/audiobookshelf/component.jsx +++ b/src/widgets/audiobookshelf/component.jsx @@ -39,11 +39,8 @@ export default function Component({ service }) { diff --git a/src/widgets/frigate/component.jsx b/src/widgets/frigate/component.jsx index 43b566e8..ac77be51 100644 --- a/src/widgets/frigate/component.jsx +++ b/src/widgets/frigate/component.jsx @@ -40,7 +40,7 @@ export default function Component({ service }) { /> diff --git a/src/widgets/fritzbox/component.jsx b/src/widgets/fritzbox/component.jsx index 5939f9ed..c3ea1bf6 100644 --- a/src/widgets/fritzbox/component.jsx +++ b/src/widgets/fritzbox/component.jsx @@ -44,7 +44,7 @@ export default function Component({ service }) { return ( - + diff --git a/src/widgets/openwrt/methods/system.jsx b/src/widgets/openwrt/methods/system.jsx index 7be8aa29..0a8146cc 100644 --- a/src/widgets/openwrt/methods/system.jsx +++ b/src/widgets/openwrt/methods/system.jsx @@ -20,7 +20,7 @@ export default function Component({ service }) { return ( - + ); diff --git a/src/widgets/stash/component.jsx b/src/widgets/stash/component.jsx index 3d64c490..b7c259a8 100644 --- a/src/widgets/stash/component.jsx +++ b/src/widgets/stash/component.jsx @@ -46,12 +46,12 @@ export default function Component({ service }) { - + - + - + {enablePools && diff --git a/src/widgets/uptimerobot/component.jsx b/src/widgets/uptimerobot/component.jsx index 27485401..b2027a6f 100644 --- a/src/widgets/uptimerobot/component.jsx +++ b/src/widgets/uptimerobot/component.jsx @@ -58,7 +58,7 @@ export default function Component({ service }) { break; case 2: status = t("uptimerobot.up"); - uptime = t("common.uptime", { value: monitor.logs[0].duration }); + uptime = t("common.duration", { value: monitor.logs[0].duration }); logIndex = 1; break; case 8: @@ -73,7 +73,7 @@ export default function Component({ service }) { } const lastDown = new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString(); - const downDuration = t("common.uptime", { value: monitor.logs[logIndex].duration }); + const downDuration = t("common.duration", { value: monitor.logs[logIndex].duration }); const hideDown = logIndex === 1 && monitor.logs[logIndex].type !== 1; return ( From b7ca6244ddb0e0e01538a2e8f8136ce0a6ee3958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:25:25 -0700 Subject: [PATCH 022/195] Chore(deps-dev): Bump tailwindcss from 3.4.13 to 3.4.14 (#4232) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- pnpm-lock.yaml | 23 ++++++++++++----------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 000b9b1a..83c37baf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "postcss": "^8.4.47", "prettier": "^3.2.5", "tailwind-scrollbar": "^3.0.5", - "tailwindcss": "^3.4.13", + "tailwindcss": "^3.4.14", "typescript": "^5.6.2" }, "optionalDependencies": { @@ -7713,9 +7713,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", - "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", diff --git a/package.json b/package.json index 6f6c09f2..09edba3d 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "postcss": "^8.4.47", "prettier": "^3.2.5", "tailwind-scrollbar": "^3.0.5", - "tailwindcss": "^3.4.13", + "tailwindcss": "^3.4.14", "typescript": "^5.6.2" }, "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eed63f8..9587852c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,7 +108,7 @@ importers: devDependencies: '@tailwindcss/forms': specifier: ^0.5.8 - version: 0.5.9(tailwindcss@3.4.13) + version: 0.5.9(tailwindcss@3.4.14) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) @@ -147,10 +147,10 @@ importers: version: 3.3.3 tailwind-scrollbar: specifier: ^3.0.5 - version: 3.1.0(tailwindcss@3.4.13) + version: 3.1.0(tailwindcss@3.4.14) tailwindcss: - specifier: ^3.4.13 - version: 3.4.13 + specifier: ^3.4.14 + version: 3.4.14 typescript: specifier: ^5.6.2 version: 5.6.2 @@ -1151,6 +1151,7 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -2510,8 +2511,8 @@ packages: peerDependencies: tailwindcss: 3.x - tailwindcss@3.4.13: - resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} hasBin: true @@ -2956,10 +2957,10 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13)': + '@tailwindcss/forms@0.5.9(tailwindcss@3.4.14)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.13 + tailwindcss: 3.4.14 '@tanstack/react-virtual@3.10.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -5355,11 +5356,11 @@ snapshots: systeminformation@5.23.5: {} - tailwind-scrollbar@3.1.0(tailwindcss@3.4.13): + tailwind-scrollbar@3.1.0(tailwindcss@3.4.14): dependencies: - tailwindcss: 3.4.13 + tailwindcss: 3.4.14 - tailwindcss@3.4.13: + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 From be4da9d010d588d80eceaca43ff474e1eed64319 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:25:47 -0700 Subject: [PATCH 023/195] Chore(deps): Bump urbackup-server-api from 0.52.0 to 0.52.1 (#4233) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 9 ++++----- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83c37baf..089bb564 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "swr": "^1.3.0", "systeminformation": "^5.23.2", "tough-cookie": "^4.1.3", - "urbackup-server-api": "^0.52.0", + "urbackup-server-api": "^0.52.1", "winston": "^3.11.0", "xml-js": "^1.6.11" }, @@ -8186,10 +8186,9 @@ } }, "node_modules/urbackup-server-api": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/urbackup-server-api/-/urbackup-server-api-0.52.0.tgz", - "integrity": "sha512-KfroCFZEWCuCkWye1F1JwI2fkO1za/Mf1a8TNGTujzxU0ZGzDqhA1zCOcvV97q7nH1TKFNpw5tMZ06fSCKv2UA==", - "license": "MIT", + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/urbackup-server-api/-/urbackup-server-api-0.52.1.tgz", + "integrity": "sha512-gAxF9MdXxnceqUr/1Uj2LuGZQb/bvZ3Ply9zH/UTSWGkwKL5C0qMPrBvKRyTHbPMG/NBuHF6BzavkF7GNvOLew==", "dependencies": { "async-mutex": "^0.5.0", "node-fetch": "^2.7.0" diff --git a/package.json b/package.json index 09edba3d..492c0eba 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "swr": "^1.3.0", "systeminformation": "^5.23.2", "tough-cookie": "^4.1.3", - "urbackup-server-api": "^0.52.0", + "urbackup-server-api": "^0.52.1", "winston": "^3.11.0", "xml-js": "^1.6.11" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9587852c..5b2bdb8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,8 +93,8 @@ importers: specifier: ^4.1.3 version: 4.1.4 urbackup-server-api: - specifier: ^0.52.0 - version: 0.52.0 + specifier: ^0.52.1 + version: 0.52.1 winston: specifier: ^3.11.0 version: 3.14.2 @@ -2650,8 +2650,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - urbackup-server-api@0.52.0: - resolution: {integrity: sha512-KfroCFZEWCuCkWye1F1JwI2fkO1za/Mf1a8TNGTujzxU0ZGzDqhA1zCOcvV97q7nH1TKFNpw5tMZ06fSCKv2UA==} + urbackup-server-api@0.52.1: + resolution: {integrity: sha512-gAxF9MdXxnceqUr/1Uj2LuGZQb/bvZ3Ply9zH/UTSWGkwKL5C0qMPrBvKRyTHbPMG/NBuHF6BzavkF7GNvOLew==} uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5537,7 +5537,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.0 - urbackup-server-api@0.52.0: + urbackup-server-api@0.52.1: dependencies: async-mutex: 0.5.0 node-fetch: 2.7.0 From bf0a7663028647a127758821a75e82132c5ae946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:28:10 -0700 Subject: [PATCH 024/195] Chore(deps-dev): Bump typescript from 5.6.2 to 5.6.3 (#4234) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++-- package.json | 2 +- pnpm-lock.yaml | 98 +++++++++++++++++++++++------------------------ 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 089bb564..d776bec0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "prettier": "^3.2.5", "tailwind-scrollbar": "^3.0.5", "tailwindcss": "^3.4.14", - "typescript": "^5.6.2" + "typescript": "^5.6.3" }, "optionalDependencies": { "osx-temperature-sensor": "^1.0.8" @@ -8102,9 +8102,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 492c0eba..d6cb010e 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "prettier": "^3.2.5", "tailwind-scrollbar": "^3.0.5", "tailwindcss": "^3.4.14", - "typescript": "^5.6.2" + "typescript": "^5.6.3" }, "optionalDependencies": { "osx-temperature-sensor": "^1.0.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b2bdb8a..cb94df4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,16 +117,16 @@ importers: version: 8.57.1 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) + version: 19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) eslint-config-next: specifier: ^14.2.3 - version: 14.2.8(eslint@8.57.1)(typescript@5.6.2) + version: 14.2.8(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) eslint-plugin-import: specifier: ^2.29.1 - version: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + version: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: specifier: ^6.8.0 version: 6.10.0(eslint@8.57.1) @@ -152,8 +152,8 @@ importers: specifier: ^3.4.14 version: 3.4.14 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages: @@ -2625,8 +2625,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -3016,13 +3016,13 @@ snapshots: '@types/triple-beam@1.3.5': {} - '@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.1)(typescript@5.6.2) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.6 eslint: 8.57.1 @@ -3030,22 +3030,22 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2)': + '@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.2.0 '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.2.0 debug: 4.3.6 eslint: 8.57.1 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -3054,21 +3054,21 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - '@typescript-eslint/type-utils@7.2.0(eslint@8.57.1)(typescript@5.6.2)': + '@typescript-eslint/type-utils@7.2.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.2) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.3) + '@typescript-eslint/utils': 7.2.0(eslint@8.57.1)(typescript@5.6.3) debug: 4.3.6 eslint: 8.57.1 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.2.0': {} - '@typescript-eslint/typescript-estree@7.2.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@7.2.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 @@ -3077,20 +3077,20 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.2.0(eslint@8.57.1)(typescript@5.6.2)': + '@typescript-eslint/utils@7.2.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 7.2.0 '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.6.3) eslint: 8.57.1 semver: 7.6.3 transitivePeerDependencies: @@ -3760,41 +3760,41 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): dependencies: eslint: 8.57.1 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) object.assign: 4.1.5 object.entries: 1.1.8 - eslint-config-next@14.2.8(eslint@8.57.1)(typescript@5.6.2): + eslint-config-next@14.2.8(eslint@8.57.1)(typescript@5.6.3): dependencies: '@next/eslint-plugin-next': 14.2.8 '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -3812,37 +3812,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -3853,7 +3853,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -3864,7 +3864,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5455,9 +5455,9 @@ snapshots: triple-beam@1.4.1: {} - ts-api-utils@1.3.0(typescript@5.6.2): + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: - typescript: 5.6.2 + typescript: 5.6.3 ts-interface-checker@0.1.13: {} @@ -5516,7 +5516,7 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - typescript@5.6.2: {} + typescript@5.6.3: {} unbox-primitive@1.0.2: dependencies: From 6fd2b6b6dc990368bb83b8625fc66856e699a604 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:38:19 -0700 Subject: [PATCH 025/195] Chore(deps-dev): Bump eslint-plugin-import from 2.30.0 to 2.31.0 (#4236) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 21 +++++++------- package.json | 2 +- pnpm-lock.yaml | 73 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index d776bec0..14b7d054 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "^14.2.3", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.1", @@ -3135,11 +3135,10 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.9.0.tgz", - "integrity": "sha512-McVbYmwA3NEKwRQY5g4aWMdcZE5xZxV8i8l7CqJSrameuGSQJtSWaL/LxTEzSKKaCcOhlpDR8XEfYXWPrdo/ZQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -3163,11 +3162,10 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, - "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -3177,7 +3175,7 @@ "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", + "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", @@ -3186,13 +3184,14 @@ "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/debug": { diff --git a/package.json b/package.json index d6cb010e..8be8d362 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "^14.2.3", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb94df4a..d73d36c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,7 +117,7 @@ importers: version: 8.57.1 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) + version: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) eslint-config-next: specifier: ^14.2.3 version: 14.2.8(eslint@8.57.1)(typescript@5.6.3) @@ -125,8 +125,8 @@ importers: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) eslint-plugin-import: - specifier: ^2.29.1 - version: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + specifier: ^2.31.0 + version: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: specifier: ^6.8.0 version: 6.10.0(eslint@8.57.1) @@ -1077,6 +1077,27 @@ packages: eslint-plugin-import-x: optional: true + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + eslint-module-utils@2.9.0: resolution: {integrity: sha512-McVbYmwA3NEKwRQY5g4aWMdcZE5xZxV8i8l7CqJSrameuGSQJtSWaL/LxTEzSKKaCcOhlpDR8XEfYXWPrdo/ZQ==} engines: {node: '>=4'} @@ -1098,12 +1119,12 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.30.0: - resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 peerDependenciesMeta: '@typescript-eslint/parser': optional: true @@ -3760,20 +3781,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): dependencies: eslint: 8.57.1 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -3788,8 +3809,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -3812,37 +3833,48 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -3853,7 +3885,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -3862,6 +3894,7 @@ snapshots: object.groupby: 1.0.3 object.values: 1.2.0 semver: 6.3.1 + string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.1)(typescript@5.6.3) From c12a5c01f61bf488959ecb6fcd6b30469c26f998 Mon Sep 17 00:00:00 2001 From: erelender <20099086+erelender@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:02:33 +0300 Subject: [PATCH 026/195] Feature: Headscale Service Widget (#4247) --- docs/widgets/services/headscale.md | 19 +++++++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 8 +++++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/components.js | 1 + src/widgets/headscale/component.jsx | 43 ++++++++++++++++++++++++ src/widgets/headscale/widget.js | 14 ++++++++ src/widgets/widgets.js | 2 ++ 9 files changed, 90 insertions(+) create mode 100644 docs/widgets/services/headscale.md create mode 100644 src/widgets/headscale/component.jsx create mode 100644 src/widgets/headscale/widget.js diff --git a/docs/widgets/services/headscale.md b/docs/widgets/services/headscale.md new file mode 100644 index 00000000..c6da54f5 --- /dev/null +++ b/docs/widgets/services/headscale.md @@ -0,0 +1,19 @@ +--- +title: Headscale +description: Headscale Widget Configuration +--- + +Learn more about [Headscale](https://headscale.net/). + +You will need to generate an API access token from the [command line](https://headscale.net/ref/remote-cli/#create-an-api-key) using `headscale apikeys create` command. + +To find your node ID, you can use `headscale nodes list` command. + +Allowed fields: `["name", "address", "last_seen", "status"]`. + +```yaml +widget: + type: headscale + nodeId: nodeid + key: headscaleapiaccesstoken +``` diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 91f91d38..3dee2a05 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -44,6 +44,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Gotify](gotify.md) - [Grafana](grafana.md) - [HDHomeRun](hdhomerun.md) +- [Headscale](headscale.md) - [Healthchecks](healthchecks.md) - [Home Assistant](homeassistant.md) - [HomeBox](homebox.md) diff --git a/mkdocs.yml b/mkdocs.yml index 0667e5ea..94af034d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ nav: - widgets/services/gotify.md - widgets/services/grafana.md - widgets/services/hdhomerun.md + - widgets/services/headscale.md - widgets/services/healthchecks.md - widgets/services/homeassistant.md - widgets/services/homebox.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index fd52aef4..38ee3e85 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -959,5 +959,13 @@ "tasks7d": "Tasks Due This Week", "tasksOverdue": "Overdue Tasks", "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Online", + "offline": "Offline" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index b1b9922c..eb2aab69 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -39,6 +39,7 @@ export default async function credentialedProxyHandler(req, res, map) { "authentik", "cloudflared", "ghostfolio", + "headscale", "linkwarden", "mealie", "netalertx", diff --git a/src/widgets/components.js b/src/widgets/components.js index 62bd479f..8453e527 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -41,6 +41,7 @@ const components = { gotify: dynamic(() => import("./gotify/component")), grafana: dynamic(() => import("./grafana/component")), hdhomerun: dynamic(() => import("./hdhomerun/component")), + headscale: dynamic(() => import("./headscale/component")), peanut: dynamic(() => import("./peanut/component")), homeassistant: dynamic(() => import("./homeassistant/component")), homebox: dynamic(() => import("./homebox/component")), diff --git a/src/widgets/headscale/component.jsx b/src/widgets/headscale/component.jsx new file mode 100644 index 00000000..361aa756 --- /dev/null +++ b/src/widgets/headscale/component.jsx @@ -0,0 +1,43 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: nodeData, error: nodeError } = useWidgetAPI(widget, "node"); + + if (nodeError) { + return ; + } + + if (!nodeData) { + return ( + + + + + + + ); + } + + const { + givenName, + ipAddresses: [address], + lastSeen, + online, + } = nodeData.node; + + return ( + + + + + + + ); +} diff --git a/src/widgets/headscale/widget.js b/src/widgets/headscale/widget.js new file mode 100644 index 00000000..a05059fb --- /dev/null +++ b/src/widgets/headscale/widget.js @@ -0,0 +1,14 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/v1/{endpoint}/{nodeId}", + proxyHandler: credentialedProxyHandler, + + mappings: { + node: { + endpoint: "node", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index faff57eb..a1af3661 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -35,6 +35,7 @@ import gluetun from "./gluetun/widget"; import gotify from "./gotify/widget"; import grafana from "./grafana/widget"; import hdhomerun from "./hdhomerun/widget"; +import headscale from "./headscale/widget"; import homeassistant from "./homeassistant/widget"; import homebox from "./homebox/widget"; import homebridge from "./homebridge/widget"; @@ -161,6 +162,7 @@ const widgets = { gotify, grafana, hdhomerun, + headscale, homeassistant, homebox, homebridge, From 7c3dcf20ef00357c64d311d6426e29868fde37fc Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:11:01 -0800 Subject: [PATCH 027/195] Create reaction-comments.yml --- .github/workflows/reaction-comments.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/reaction-comments.yml diff --git a/.github/workflows/reaction-comments.yml b/.github/workflows/reaction-comments.yml new file mode 100644 index 00000000..f3a0e67d --- /dev/null +++ b/.github/workflows/reaction-comments.yml @@ -0,0 +1,20 @@ +name: 'Reaction Comments' + +on: + issue_comment: + types: [created, edited] + pull_request_review_comment: + types: [created, edited] + schedule: + - cron: '0 0 * * *' + +permissions: + actions: write + issues: write + pull-requests: write + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/reaction-comments@v4 From 912ae0adfc4fd5e78a05e91b8321aae70161ad6d Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:39:41 -0800 Subject: [PATCH 028/195] Feature: Beszel service widget (#4251) --- docs/widgets/services/beszel.md | 20 ++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 11 ++++ src/utils/config/service-helpers.js | 7 ++ src/widgets/beszel/component.jsx | 60 +++++++++++++++++ src/widgets/beszel/proxy.js | 99 +++++++++++++++++++++++++++++ src/widgets/beszel/widget.js | 14 ++++ src/widgets/components.js | 1 + src/widgets/widgets.js | 2 + 10 files changed, 216 insertions(+) create mode 100644 docs/widgets/services/beszel.md create mode 100644 src/widgets/beszel/component.jsx create mode 100644 src/widgets/beszel/proxy.js create mode 100644 src/widgets/beszel/widget.js diff --git a/docs/widgets/services/beszel.md b/docs/widgets/services/beszel.md new file mode 100644 index 00000000..3cc828b9 --- /dev/null +++ b/docs/widgets/services/beszel.md @@ -0,0 +1,20 @@ +--- +title: Beszel +description: Beszel Widget Configuration +--- + +Learn more about [Beszel]() + +The widget has two modes, a single system with detailed info if `systemId` is provided, or an overview of all systems if `systemId` is not provided. + +Allowed fields for 'overview' mode: `["systems", "up"]` +Allowed fields for a single system: `["name", "status", "updated", "cpu", "memory", "disk", "network"]` + +```yaml +widget: + type: beszel + url: http://beszel.host.or.ip + username: username # email + password: password + systemId: systemId # optional +``` diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 3dee2a05..83ad0fed 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -14,6 +14,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Autobrr](autobrr.md) - [Azure DevOps](azuredevops.md) - [Bazarr](bazarr.md) +- [Beszel](beszel.md) - [Caddy](caddy.md) - [Calendar](calendar.md) - [Calibre-Web](calibre-web.md) diff --git a/mkdocs.yml b/mkdocs.yml index 94af034d..cd466892 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - widgets/services/autobrr.md - widgets/services/azuredevops.md - widgets/services/bazarr.md + - widgets/services/beszel.md - widgets/services/caddy.md - widgets/services/calendar.md - widgets/services/calibre-web.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 38ee3e85..bdde0a34 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -967,5 +967,16 @@ "status": "Status", "online": "Online", "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Status", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 9d55ce43..63dfb608 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -368,6 +368,9 @@ export function cleanServiceGroups(groups) { repositoryId, userEmail, + // beszel + systemId, + // calendar firstDayInWeek, integrations, @@ -511,6 +514,10 @@ export function cleanServiceGroups(groups) { if (repositoryId) cleanedService.widget.repositoryId = repositoryId; } + if (type === "beszel") { + if (systemId) cleanedService.widget.systemId = systemId; + } + if (type === "coinmarketcap") { if (currency) cleanedService.widget.currency = currency; if (symbols) cleanedService.widget.symbols = symbols; diff --git a/src/widgets/beszel/component.jsx b/src/widgets/beszel/component.jsx new file mode 100644 index 00000000..1d35b6e9 --- /dev/null +++ b/src/widgets/beszel/component.jsx @@ -0,0 +1,60 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + const { systemId } = widget; + + const { data: systems, error: systemsError } = useWidgetAPI(widget, "systems"); + + const MAX_ALLOWED_FIELDS = 4; + if (!widget.fields?.length > 0) { + widget.fields = systemId ? ["name", "status", "cpu", "memory"] : ["systems", "up"]; + } + if (widget.fields?.length > MAX_ALLOWED_FIELDS) { + widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS); + } + + if (systemsError) { + return ; + } + + if (!systems) { + return ( + + + + + ); + } + + if (systemId) { + const system = systems.items.find((item) => item.id === systemId); + + return ( + + + + + + + + + + ); + } + + const upTotal = systems.items.filter((item) => item.status === "up").length; + + return ( + + + + + ); +} diff --git a/src/widgets/beszel/proxy.js b/src/widgets/beszel/proxy.js new file mode 100644 index 00000000..5f70a10d --- /dev/null +++ b/src/widgets/beszel/proxy.js @@ -0,0 +1,99 @@ +import cache from "memory-cache"; + +import getServiceWidget from "utils/config/service-helpers"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import widgets from "widgets/widgets"; +import createLogger from "utils/logger"; + +const proxyName = "beszelProxyHandler"; +const tokenCacheKey = `${proxyName}__token`; +const logger = createLogger(proxyName); + +async function login(loginUrl, username, password, service) { + const authResponse = await httpProxy(loginUrl, { + method: "POST", + body: JSON.stringify({ identity: username, password }), + headers: { + "Content-Type": "application/json", + }, + }); + + const status = authResponse[0]; + let data = authResponse[2]; + try { + data = JSON.parse(Buffer.from(authResponse[2]).toString()); + + if (status === 200) { + cache.put(`${tokenCacheKey}.${service}`, data.token); + } + } catch (e) { + logger.error(`Error ${status} logging into beszel`, JSON.stringify(authResponse[2])); + } + return [status, data.token ?? data]; +} + +export default async function beszelProxyHandler(req, res) { + const { group, service, endpoint } = req.query; + + if (group && service) { + const widget = await getServiceWidget(group, service); + + if (!widgets?.[widget.type]?.api) { + return res.status(403).json({ error: "Service does not support API calls" }); + } + + if (widget) { + const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); + const loginUrl = formatApiCall(widgets[widget.type].api, { endpoint: "admins/auth-with-password", ...widget }); + + let status; + let data; + + let token = cache.get(`${tokenCacheKey}.${service}`); + if (!token) { + [status, token] = await login(loginUrl, widget.username, widget.password, service); + if (status !== 200) { + logger.debug(`HTTTP ${status} logging into npm api: ${token}`); + return res.status(status).send(token); + } + } + + [status, , data] = await httpProxy(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (status === 403) { + logger.debug(`HTTTP ${status} retrieving data from npm api, logging in and trying again.`); + cache.del(`${tokenCacheKey}.${service}`); + [status, token] = await login(loginUrl, widget.username, widget.password, service); + + if (status !== 200) { + logger.debug(`HTTTP ${status} logging into npm api: ${data}`); + return res.status(status).send(data); + } + + // eslint-disable-next-line no-unused-vars + [status, , data] = await httpProxy(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + } + + if (status !== 200) { + return res.status(status).send(data); + } + + return res.send(data); + } + } + + return res.status(400).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/beszel/widget.js b/src/widgets/beszel/widget.js new file mode 100644 index 00000000..508c1deb --- /dev/null +++ b/src/widgets/beszel/widget.js @@ -0,0 +1,14 @@ +import beszelProxyHandler from "./proxy"; + +const widget = { + api: "{url}/api/{endpoint}", + proxyHandler: beszelProxyHandler, + + mappings: { + systems: { + endpoint: "collections/systems/records?page=1&perPage=500&sort=%2Bcreated", + }, + }, +}; + +export default widget; diff --git a/src/widgets/components.js b/src/widgets/components.js index 8453e527..50564e49 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -8,6 +8,7 @@ const components = { autobrr: dynamic(() => import("./autobrr/component")), azuredevops: dynamic(() => import("./azuredevops/component")), bazarr: dynamic(() => import("./bazarr/component")), + beszel: dynamic(() => import("./beszel/component")), caddy: dynamic(() => import("./caddy/component")), calendar: dynamic(() => import("./calendar/component")), calibreweb: dynamic(() => import("./calibreweb/component")), diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index a1af3661..40b121ae 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -5,6 +5,7 @@ import authentik from "./authentik/widget"; import autobrr from "./autobrr/widget"; import azuredevops from "./azuredevops/widget"; import bazarr from "./bazarr/widget"; +import beszel from "./beszel/widget"; import caddy from "./caddy/widget"; import calendar from "./calendar/widget"; import calibreweb from "./calibreweb/widget"; @@ -133,6 +134,7 @@ const widgets = { autobrr, azuredevops, bazarr, + beszel, caddy, calibreweb, changedetectionio, From 794ec127cd2d27a35666f54f67cf3966893f32a6 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:24:09 -0800 Subject: [PATCH 029/195] Enhancement: quicklaunch fill search suggestion on arrowright (#4256) --- src/components/quicklaunch.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index f2089065..9f55f973 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -98,6 +98,12 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea } else if (event.key === "ArrowUp" && currentItemIndex > 0) { setCurrentItemIndex(currentItemIndex - 1); event.preventDefault(); + } else if ( + event.key === "ArrowRight" && + results[currentItemIndex] && + results[currentItemIndex].type === "searchSuggestion" + ) { + setSearchString(results[currentItemIndex].name); } } From 85637f29792176d3fb3f09b4f1cf4789d3f2e3f7 Mon Sep 17 00:00:00 2001 From: ChainSauz <22598783+ChainSauz@users.noreply.github.com> Date: Sat, 9 Nov 2024 07:15:22 -0500 Subject: [PATCH 030/195] Documentation: Update opnsense.md privileges section for OPNSense 24.7.x (#4264) --- docs/widgets/services/opnsense.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/opnsense.md b/docs/widgets/services/opnsense.md index ddda62ff..faaba6b8 100644 --- a/docs/widgets/services/opnsense.md +++ b/docs/widgets/services/opnsense.md @@ -8,7 +8,7 @@ Learn more about [OPNSense](https://opnsense.org/). The API key & secret can be generated via the webui by creating a new user at _System/Access/Users_. Ensure "Generate a scrambled password to prevent local database logins for this user" is checked and then edit the effective privileges selecting **only**: - Diagnostics: System Activity -- Status: Traffic Graph +- Status: Traffic Graph / Reporting: Traffic (OPNSENSE 24.7.x) Finally, create a new API key which will download an `apikey.txt` file with your key and secret in it. Use the values as the username and password fields, respectively, in your homepage config. From ef9068c5bbd95952d7d7d48a084365d5f46bbc7a Mon Sep 17 00:00:00 2001 From: Carlo <138704619+wan0v@users.noreply.github.com> Date: Mon, 11 Nov 2024 04:26:40 +0100 Subject: [PATCH 031/195] Documentation: change authentik port to generic placeholder (#4272) --- docs/widgets/services/authentik.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/services/authentik.md b/docs/widgets/services/authentik.md index 8968f4bf..f4dafd40 100644 --- a/docs/widgets/services/authentik.md +++ b/docs/widgets/services/authentik.md @@ -20,6 +20,6 @@ Allowed fields: `["users", "loginsLast24H", "failedLoginsLast24H"]`. ```yaml widget: type: authentik - url: http://authentik.host.or.ip:22070 + url: http://authentik.host.or.ip:port key: api_token ``` From e938c3ac1e6933d78459b370b088948ad554331f Mon Sep 17 00:00:00 2001 From: Felix Cornelius <9767036+fcornelius@users.noreply.github.com> Date: Tue, 12 Nov 2024 01:42:14 +0100 Subject: [PATCH 032/195] Feature: Prometheus Metric service widget (#4269) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/index.md | 1 + docs/widgets/services/prometheusmetric.md | 67 ++++++++++++ mkdocs.yml | 1 + src/utils/config/service-helpers.js | 9 +- src/widgets/components.js | 1 + src/widgets/prometheusmetric/component.jsx | 115 +++++++++++++++++++++ src/widgets/prometheusmetric/widget.js | 16 +++ src/widgets/widgets.js | 2 + 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 docs/widgets/services/prometheusmetric.md create mode 100644 src/widgets/prometheusmetric/component.jsx create mode 100644 src/widgets/prometheusmetric/widget.js diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 83ad0fed..8ea2e933 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -98,6 +98,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Plex](plex.md) - [Portainer](portainer.md) - [Prometheus](prometheus.md) +- [Prometheus Metric](prometheusmetric.md) - [Prowlarr](prowlarr.md) - [Proxmox](proxmox.md) - [Proxmox Backup Server](proxmoxbackupserver.md) diff --git a/docs/widgets/services/prometheusmetric.md b/docs/widgets/services/prometheusmetric.md new file mode 100644 index 00000000..19397aa7 --- /dev/null +++ b/docs/widgets/services/prometheusmetric.md @@ -0,0 +1,67 @@ +--- +title: Prometheus Metric +description: Prometheus Metric Widget Configuration +--- + +Learn more about [Querying Prometheus](https://prometheus.io/docs/prometheus/latest/querying/basics/). + +This widget can show metrics for your service defined by PromQL queries which are requested from a running Prometheus instance. + +Quries can be defined in the `metrics` array of the widget along with a label to be used to present the metric value. You can optionally specify a global `refreshInterval` in milliseconds and/or define the `refreshInterval` per metric. Inside the optional `format` object of a metric various formatting styles and transformations can be applied (see below). + +```yaml +widget: + type: prometheusmetric + url: https://prometheus.host.or.ip + refreshInterval: 10000 # optional - in milliseconds, defaults to 10s + metrics: + - label: Metric 1 + query: alertmanager_alerts{state="active"} + - label: Metric 2 + query: apiserver_storage_size_bytes{node="mynode"} + format: + type: bytes + - label: Metric 3 + query: avg(prometheus_notifications_latency_seconds) + format: + type: number + suffix: s + options: + maximumFractionDigits: 4 + - label: Metric 4 + query: time() + refreshInterval: 1000 # will override global refreshInterval + format: + type: date + scale: 1000 + options: + timeStyle: medium +``` + +## Formatting + +Supported values for `format.type` are `text`, `number`, `percent`, `bytes`, `bits`, `bbytes`, `bbits`, `byterate`, `bibyterate`, `bitrate`, `bibitrate`, `date`, `duration`, `relativeDate`, and `text` which is the default. + +The `dateStyle` and `timeStyle` options of the `date` format are passed directly to [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) and the `style` and `numeric` options of `relativeDate` are passed to [Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat). For the `number` format, options of [Intl.NumberFormat](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) can be used, e.g. `maximumFractionDigits` or `minimumFractionDigits`. + +### Data Transformation + +You can manipulate your metric value with the following tools: `scale`, `prefix` and `suffix`, for example: + +```yaml +- query: my_custom_metric{} + label: Metric 1 + format: + type: number + scale: 1000 # multiplies value by a number or fraction string e.g. 1/16 +- query: my_custom_metric{} + label: Metric 2 + format: + type: number + prefix: "$" # prefixes value with given string +- query: my_custom_metric{} + label: Metric 3 + format: + type: number + suffix: "€" # suffixes value with given string +``` diff --git a/mkdocs.yml b/mkdocs.yml index cd466892..42abce30 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -121,6 +121,7 @@ nav: - widgets/services/plex.md - widgets/services/portainer.md - widgets/services/prometheus.md + - widgets/services/prometheusmetric.md - widgets/services/prowlarr.md - widgets/services/proxmox.md - widgets/services/proxmoxbackupserver.md diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 63dfb608..1566a135 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -418,7 +418,7 @@ export function cleanServiceGroups(groups) { pointsLimit, diskUnits, - // glances, customapi, iframe + // glances, customapi, iframe, prometheusmetric refreshInterval, // hdhomerun @@ -461,6 +461,9 @@ export function cleanServiceGroups(groups) { // opnsense, pfsense wan, + // prometheusmetric + metrics, + // proxmox node, @@ -646,6 +649,10 @@ export function cleanServiceGroups(groups) { if (type === "vikunja") { if (enableTaskList !== undefined) cleanedService.widget.enableTaskList = !!enableTaskList; } + if (type === "prometheusmetric") { + if (metrics) cleanedService.widget.metrics = metrics; + if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; + } } return cleanedService; diff --git a/src/widgets/components.js b/src/widgets/components.js index 50564e49..7e6b68e1 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -95,6 +95,7 @@ const components = { plex: dynamic(() => import("./plex/component")), portainer: dynamic(() => import("./portainer/component")), prometheus: dynamic(() => import("./prometheus/component")), + prometheusmetric: dynamic(() => import("./prometheusmetric/component")), prowlarr: dynamic(() => import("./prowlarr/component")), proxmox: dynamic(() => import("./proxmox/component")), pterodactyl: dynamic(() => import("./pterodactyl/component")), diff --git a/src/widgets/prometheusmetric/component.jsx b/src/widgets/prometheusmetric/component.jsx new file mode 100644 index 00000000..347aaa0c --- /dev/null +++ b/src/widgets/prometheusmetric/component.jsx @@ -0,0 +1,115 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +function formatValue(t, metric, rawValue) { + if (!rawValue) return "-"; + + let value = rawValue; + + // Scale the value. Accepts either a number to multiply by or a string + // like "12/345". + const scale = metric?.format?.scale; + if (typeof scale === "number") { + value *= scale; + } else if (typeof scale === "string" && scale.includes("/")) { + const parts = scale.split("/"); + const numerator = parts[0] ? parseFloat(parts[0]) : 1; + const denominator = parts[1] ? parseFloat(parts[1]) : 1; + value = (value * numerator) / denominator; + } else { + value = parseFloat(value); + } + + // Format the value using a known type and optional options. + switch (metric?.format?.type) { + case "text": + break; + default: + value = t(`common.${metric.format.type}`, { value, ...metric.format?.options }); + } + + // Apply fixed prefix. + const prefix = metric?.format?.prefix; + if (prefix) { + value = `${prefix}${value}`; + } + + // Apply fixed suffix. + const suffix = metric?.format?.suffix; + if (suffix) { + value = `${value}${suffix}`; + } + + return value; +} + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { metrics = [], refreshInterval = 10000 } = widget; + + let prometheusmetricError; + + const prometheusmetricData = new Map( + metrics.slice(0, 4).map((metric) => { + // disable the rule that hooks should not be called from a callback, + // because we don't need a strong guarantee of hook execution order here. + // eslint-disable-next-line react-hooks/rules-of-hooks + const { data: resultData, error: resultError } = useWidgetAPI(widget, "query", { + query: metric.query, + refreshInterval: Math.max(1000, metric.refreshInterval ?? refreshInterval), + }); + if (resultError) { + prometheusmetricError = resultError; + } + return [metric.key ?? metric.label, resultData]; + }), + ); + + if (prometheusmetricError) { + return ; + } + + if (!prometheusmetricData) { + return ( + + {metrics.slice(0, 4).map((item) => ( + + ))} + + ); + } + + function getResultValue(data) { + // Fetches the first metric result from the Prometheus query result data. + // The first element in the result value is the timestamp which is ignored here. + const resultType = data?.data?.resultType; + const result = data?.data?.result; + + switch (resultType) { + case "vector": + return result?.[0]?.value?.[1]; + case "scalar": + return result?.[1]; + default: + return ""; + } + } + + return ( + + {metrics.map((metric) => ( + + ))} + + ); +} diff --git a/src/widgets/prometheusmetric/widget.js b/src/widgets/prometheusmetric/widget.js new file mode 100644 index 00000000..22137b0d --- /dev/null +++ b/src/widgets/prometheusmetric/widget.js @@ -0,0 +1,16 @@ +import genericProxyHandler from "utils/proxy/handlers/generic"; + +const widget = { + api: "{url}/api/v1/{endpoint}", + proxyHandler: genericProxyHandler, + + mappings: { + query: { + method: "GET", + endpoint: "query", + params: ["query"], + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 40b121ae..4d76fa07 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -87,6 +87,7 @@ import plantit from "./plantit/widget"; import plex from "./plex/widget"; import portainer from "./portainer/widget"; import prometheus from "./prometheus/widget"; +import prometheusmetric from "./prometheusmetric/widget"; import prowlarr from "./prowlarr/widget"; import proxmox from "./proxmox/widget"; import pterodactyl from "./pterodactyl/widget"; @@ -218,6 +219,7 @@ const widgets = { plex, portainer, prometheus, + prometheusmetric, prowlarr, proxmox, pterodactyl, From 1a22065c3a08b0b1bd0e807aeb3f0040fe618f49 Mon Sep 17 00:00:00 2001 From: John <34685272+Johnomated@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:15:26 -0600 Subject: [PATCH 033/195] Fix: use session_key instead of Id in tautulli component to resolve unique key warning (#4278) --- src/widgets/tautulli/component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/tautulli/component.jsx b/src/widgets/tautulli/component.jsx index b540c6d7..ba94f143 100644 --- a/src/widgets/tautulli/component.jsx +++ b/src/widgets/tautulli/component.jsx @@ -205,7 +205,7 @@ export default function Component({ service }) {
{playing.map((session) => ( Date: Tue, 12 Nov 2024 01:52:51 +0000 Subject: [PATCH 034/195] Feature: suwayomi Service Widget (#4273) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/suwayomi.md | 20 ++++ public/locales/en/common.json | 10 ++ src/widgets/components.js | 1 + src/widgets/suwayomi/component.jsx | 40 +++++++ src/widgets/suwayomi/proxy.js | 175 +++++++++++++++++++++++++++++ src/widgets/suwayomi/widget.js | 8 ++ src/widgets/widgets.js | 2 + 7 files changed, 256 insertions(+) create mode 100644 docs/widgets/services/suwayomi.md create mode 100644 src/widgets/suwayomi/component.jsx create mode 100644 src/widgets/suwayomi/proxy.js create mode 100644 src/widgets/suwayomi/widget.js diff --git a/docs/widgets/services/suwayomi.md b/docs/widgets/services/suwayomi.md new file mode 100644 index 00000000..21672598 --- /dev/null +++ b/docs/widgets/services/suwayomi.md @@ -0,0 +1,20 @@ +--- +title: Suwayomi +description: Suwayomi Widget Configuration +--- + +Learn more about [Suwayomi](https://github.com/Suwayomi/Suwayomi-Server). + +Allowed fields: ["download", "nondownload", "read", "unread", "downloadedread", "downloadedunread", "nondownloadedread", "nondownloadedunread"] + +The widget defaults to the first four above. If more than four fields are provided, only the first 4 are displayed. +Category IDs can be obtained from the url when navigating to it, `?tab={categoryID}`. + +```yaml +widget: + type: suwayomi + url: http://suwayomi.host.or.ip + username: username #optional + password: password #optional + category: 0 #optional, defaults to all categories +``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index bdde0a34..81576984 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Total" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", diff --git a/src/widgets/components.js b/src/widgets/components.js index 7e6b68e1..3cba84d2 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -114,6 +114,7 @@ const components = { stocks: dynamic(() => import("./stocks/component")), strelaysrv: dynamic(() => import("./strelaysrv/component")), swagdashboard: dynamic(() => import("./swagdashboard/component")), + suwayomi: dynamic(() => import("./suwayomi/component")), tailscale: dynamic(() => import("./tailscale/component")), tandoor: dynamic(() => import("./tandoor/component")), tautulli: dynamic(() => import("./tautulli/component")), diff --git a/src/widgets/suwayomi/component.jsx b/src/widgets/suwayomi/component.jsx new file mode 100644 index 00000000..b7c34820 --- /dev/null +++ b/src/widgets/suwayomi/component.jsx @@ -0,0 +1,40 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data: suwayomiData, error: suwayomiError } = useWidgetAPI(widget); + + if (suwayomiError) { + return ; + } + + if (!suwayomiData) { + if (!widget.fields || widget.fields.length === 0) { + widget.fields = ["download", "nondownload", "read", "unread"]; + } else if (widget.fields.length > 4) { + widget.fields = widget.fields.slice(0, 4); + } + return ( + + {widget.fields.map((field) => ( + + ))} + + ); + } + + return ( + + {suwayomiData.map((data) => ( + + ))} + + ); +} diff --git a/src/widgets/suwayomi/proxy.js b/src/widgets/suwayomi/proxy.js new file mode 100644 index 00000000..d4d71675 --- /dev/null +++ b/src/widgets/suwayomi/proxy.js @@ -0,0 +1,175 @@ +import { httpProxy } from "utils/proxy/http"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const proxyName = "suwayomiProxyHandler"; +const logger = createLogger(proxyName); + +const countsToExtract = { + download: { + condition: (c) => c.isDownloaded, + gqlCondition: "isDownloaded: true", + }, + nondownload: { + condition: (c) => !c.isDownloaded, + gqlCondition: "isDownloaded: false", + }, + read: { + condition: (c) => c.isRead, + gqlCondition: "isRead: true", + }, + unread: { + condition: (c) => !c.isRead, + gqlCondition: "isRead: false", + }, + downloadedread: { + condition: (c) => c.isDownloaded && c.isRead, + gqlCondition: "isDownloaded: true, isRead: true", + }, + downloadedunread: { + condition: (c) => c.isDownloaded && !c.isRead, + gqlCondition: "isDownloaded: true, isRead: false", + }, + nondownloadedread: { + condition: (c) => !c.isDownloaded && c.isRead, + gqlCondition: "isDownloaded: false, isRead: true", + }, + nondownloadedunread: { + condition: (c) => !c.isDownloaded && !c.isRead, + gqlCondition: "isDownloaded: false, isRead: false", + }, +}; + +function makeBody(fields, category = "all") { + if (Number.isNaN(Number(category))) { + let query = ""; + fields.forEach((field) => { + query += ` + ${field}: chapters( + condition: {${countsToExtract[field].gqlCondition}} + filter: {inLibrary: {equalTo: true}} + ) { + totalCount + }`; + }); + return JSON.stringify({ + operationName: "Counts", + query: ` + query Counts { + ${query} + }`, + }); + } + + return JSON.stringify({ + operationName: "category", + query: ` + query category($id: Int!) { + category(id: $id) { + # name + mangas { + nodes { + chapters { + nodes { + isRead + isDownloaded + } + } + } + } + } + }`, + variables: { + id: Number(category), + }, + }); +} + +function extractCounts(responseJSON, fields) { + if (!("category" in responseJSON.data)) { + return fields.map((field) => ({ + count: responseJSON.data[field].totalCount, + label: `suwayomi.${field}`, + })); + } + const tmp = responseJSON.data.category.mangas.nodes.reduce( + (accumulator, manga) => { + manga.chapters.nodes.forEach((chapter) => { + fields.forEach((field, i) => { + if (countsToExtract[field].condition(chapter)) { + accumulator[i] += 1; + } + }); + }); + return accumulator; + }, + [0, 0, 0, 0], + ); + return fields.map((field, i) => ({ + count: tmp[i], + label: `suwayomi.${field}`, + })); +} + +export default async function suwayomiProxyHandler(req, res) { + const { group, service, endpoint } = req.query; + + if (!group || !service) { + logger.debug("Invalid or missing service '%s' or group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const widget = await getServiceWidget(group, service); + + if (!widget) { + logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + if (!widget.fields || widget.fields.length === 0) { + widget.fields = ["download", "nondownload", "read", "unread"]; + } else if (widget.fields.length > 4) { + widget.fields = widget.fields.slice(0, 4); + } + + const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); + + const body = makeBody(widget.fields, widget.category); + + const headers = { + "Content-Type": "application/json", + }; + + if (widget.username && widget.password) { + headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; + } + + const [status, contentType, data] = await httpProxy(url, { + method: "POST", + body, + headers, + }); + + if (status === 401) { + logger.error("Invalid or missing username or password for service '%s' in group '%s'", service, group); + return res.status(status).send({ error: { message: "401: unauthorized, username or password is incorrect." } }); + } + + if (status !== 200) { + logger.error( + "Error getting data from Suwayomi for service '%s' in group '%s': %d. Data: %s", + service, + group, + status, + data, + ); + return res.status(status).send({ error: { message: "Error getting data. body: %s, data: %s", body, data } }); + } + + const returnData = extractCounts(JSON.parse(data), widget.fields); + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(returnData); +} diff --git a/src/widgets/suwayomi/widget.js b/src/widgets/suwayomi/widget.js new file mode 100644 index 00000000..e2ed509c --- /dev/null +++ b/src/widgets/suwayomi/widget.js @@ -0,0 +1,8 @@ +import suwayomiProxyHandler from "./proxy"; + +const widget = { + api: "{url}/api/graphql", + proxyHandler: suwayomiProxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 4d76fa07..79110378 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -105,6 +105,7 @@ import stash from "./stash/widget"; import stocks from "./stocks/widget"; import strelaysrv from "./strelaysrv/widget"; import swagdashboard from "./swagdashboard/widget"; +import suwayomi from "./suwayomi/widget"; import tailscale from "./tailscale/widget"; import tandoor from "./tandoor/widget"; import tautulli from "./tautulli/widget"; @@ -238,6 +239,7 @@ const widgets = { stocks, strelaysrv, swagdashboard, + suwayomi, tailscale, tandoor, tautulli, From d87d347aa3d95abf1e24eb9691e34da1243af508 Mon Sep 17 00:00:00 2001 From: Abey Thomas Date: Tue, 12 Nov 2024 06:19:04 -0800 Subject: [PATCH 035/195] Documentation: corrections Beszel widget docs (#4282) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/beszel.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/widgets/services/beszel.md b/docs/widgets/services/beszel.md index 3cc828b9..6a5cc269 100644 --- a/docs/widgets/services/beszel.md +++ b/docs/widgets/services/beszel.md @@ -3,10 +3,12 @@ title: Beszel description: Beszel Widget Configuration --- -Learn more about [Beszel]() +Learn more about [Beszel](https://github.com/henrygd/beszel) The widget has two modes, a single system with detailed info if `systemId` is provided, or an overview of all systems if `systemId` is not provided. +The `systemID` in the `id` field on the collections page of Beszel. + Allowed fields for 'overview' mode: `["systems", "up"]` Allowed fields for a single system: `["name", "status", "updated", "cpu", "memory", "disk", "network"]` From 535be37befafc5d9e3636d60492a7efc241981eb Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:31:36 -0800 Subject: [PATCH 036/195] Fix: fix some instances of HTTTP --- src/widgets/beszel/proxy.js | 6 +++--- src/widgets/calendar/proxy.js | 2 +- src/widgets/npm/proxy.js | 6 +++--- src/widgets/omada/proxy.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/widgets/beszel/proxy.js b/src/widgets/beszel/proxy.js index 5f70a10d..04083e42 100644 --- a/src/widgets/beszel/proxy.js +++ b/src/widgets/beszel/proxy.js @@ -54,7 +54,7 @@ export default async function beszelProxyHandler(req, res) { if (!token) { [status, token] = await login(loginUrl, widget.username, widget.password, service); if (status !== 200) { - logger.debug(`HTTTP ${status} logging into npm api: ${token}`); + logger.debug(`HTTP ${status} logging into npm api: ${token}`); return res.status(status).send(token); } } @@ -68,12 +68,12 @@ export default async function beszelProxyHandler(req, res) { }); if (status === 403) { - logger.debug(`HTTTP ${status} retrieving data from npm api, logging in and trying again.`); + logger.debug(`HTTP ${status} retrieving data from npm api, logging in and trying again.`); cache.del(`${tokenCacheKey}.${service}`); [status, token] = await login(loginUrl, widget.username, widget.password, service); if (status !== 200) { - logger.debug(`HTTTP ${status} logging into npm api: ${data}`); + logger.debug(`HTTP ${status} logging into npm api: ${data}`); return res.status(status).send(data); } diff --git a/src/widgets/calendar/proxy.js b/src/widgets/calendar/proxy.js index 996ea324..cf754424 100644 --- a/src/widgets/calendar/proxy.js +++ b/src/widgets/calendar/proxy.js @@ -21,7 +21,7 @@ export default async function calendarProxyHandler(req, res) { if (contentType) res.setHeader("Content-Type", contentType); if (status !== 200) { - logger.debug(`HTTTP ${status} retrieving data from integration URL ${integration.url} : ${data}`); + logger.debug(`HTTP ${status} retrieving data from integration URL ${integration.url} : ${data}`); return res.status(status).send(data); } diff --git a/src/widgets/npm/proxy.js b/src/widgets/npm/proxy.js index 3f1e3495..978254f8 100644 --- a/src/widgets/npm/proxy.js +++ b/src/widgets/npm/proxy.js @@ -56,7 +56,7 @@ export default async function npmProxyHandler(req, res) { if (!token) { [status, token] = await login(loginUrl, widget.username, widget.password, service); if (status !== 200) { - logger.debug(`HTTTP ${status} logging into npm api: ${token}`); + logger.debug(`HTTP ${status} logging into npm api: ${token}`); return res.status(status).send(token); } } @@ -70,12 +70,12 @@ export default async function npmProxyHandler(req, res) { }); if (status === 403) { - logger.debug(`HTTTP ${status} retrieving data from npm api, logging in and trying again.`); + logger.debug(`HTTP ${status} retrieving data from npm api, logging in and trying again.`); cache.del(`${tokenCacheKey}.${service}`); [status, token] = await login(loginUrl, widget.username, widget.password, service); if (status !== 200) { - logger.debug(`HTTTP ${status} logging into npm api: ${data}`); + logger.debug(`HTTP ${status} logging into npm api: ${data}`); return res.status(status).send(data); } diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index ac3453a8..8e8994a5 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -138,7 +138,7 @@ export default async function omadaProxyHandler(req, res) { const sitesResponseData = JSON.parse(data); if (status !== 200 || sitesResponseData.errorCode > 0) { - logger.debug(`HTTTP ${status} getting sites list: ${sitesResponseData.msg}`); + logger.debug(`HTTP ${status} getting sites list: ${sitesResponseData.msg}`); return res .status(status) .json({ error: { message: "Error getting sites list", url, data: sitesResponseData } }); From d82fbc3026b1dd1c50d8d75f6d73e598b3dc0f41 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:34:26 -0800 Subject: [PATCH 037/195] Enhancement: allow widgets to specify default headers, fix buffer error output (#4287) --- src/utils/proxy/handlers/generic.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index e4717469..c6b9236b 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -23,7 +23,7 @@ export default async function genericProxyHandler(req, res, map) { formatApiCall(widgets[widget.type].api, { endpoint, ...widget }).replace(/(?<=\?.*)\?/g, "&"), ); - const headers = req.extraHeaders ?? widget.headers ?? {}; + const headers = req.extraHeaders ?? widget.headers ?? widgets[widget.type].headers ?? {}; if (widget.username && widget.password) { headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; @@ -75,7 +75,13 @@ export default async function genericProxyHandler(req, res, map) { url.port ? `:${url.port}` : "", url.pathname, ); - return res.status(status).json({ error: { message: "HTTP Error", url: sanitizeErrorURL(url), resultData } }); + return res.status(status).json({ + error: { + message: "HTTP Error", + url: sanitizeErrorURL(url), + resultData: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData, + }, + }); } return res.status(status).send(resultData); From e730a0ceb0467dcca7a54e6ef3b352ee214f0f92 Mon Sep 17 00:00:00 2001 From: dhenry437 Date: Thu, 14 Nov 2024 01:57:03 +1100 Subject: [PATCH 038/195] Documentation: Highlight Unifi widget needs local account (#4290) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/info/unifi_controller.md | 6 +++++- docs/widgets/services/unifi-controller.md | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/widgets/info/unifi_controller.md b/docs/widgets/info/unifi_controller.md index 420029f5..b77d8ed0 100644 --- a/docs/widgets/info/unifi_controller.md +++ b/docs/widgets/info/unifi_controller.md @@ -5,7 +5,11 @@ description: Unifi Controller Information Widget Configuration _(Find the Unifi Controller service widget [here](../services/unifi-controller.md))_ -You can display general connectivity status from your Unifi (Network) Controller. When authenticating you will want to use a local account that has at least read privileges. +You can display general connectivity status from your Unifi (Network) Controller. + +!!! + + When authenticating you will want to use a local account that has at least read privileges. An optional 'site' parameter can be supplied, if it is not the widget will use the default site for the controller. diff --git a/docs/widgets/services/unifi-controller.md b/docs/widgets/services/unifi-controller.md index 3e0b3af5..d137c2a9 100644 --- a/docs/widgets/services/unifi-controller.md +++ b/docs/widgets/services/unifi-controller.md @@ -7,7 +7,11 @@ Learn more about [Unifi Controller](https://ui.com/). _(Find the Unifi Controller information widget [here](../info/unifi_controller.md))_ -You can display general connectivity status from your Unifi (Network) Controller. When authenticating you will want to use a local account that has at least read privileges. +You can display general connectivity status from your Unifi (Network) Controller. + +!!! + + When authenticating you will want to use a local account that has at least read privileges. An optional 'site' parameter can be supplied, if it is not the widget will use the default site for the controller. From 250351f735d33cfdbffce9e80be4f8de4a93aac7 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:16:18 -0800 Subject: [PATCH 039/195] Fix: use duration for audiobookshelf books too See #4228 --- src/widgets/audiobookshelf/component.jsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/widgets/audiobookshelf/component.jsx b/src/widgets/audiobookshelf/component.jsx index 6eb78638..06439e8f 100755 --- a/src/widgets/audiobookshelf/component.jsx +++ b/src/widgets/audiobookshelf/component.jsx @@ -46,11 +46,8 @@ export default function Component({ service }) { From adde687331294185a652202e99ef8fa2e5351e80 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 18 Nov 2024 07:31:05 -0800 Subject: [PATCH 040/195] Try publish to docker hub --- .github/workflows/docker-publish.yml | 35 ++++++++-------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 368040cc..2ac8b3e8 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -26,8 +26,6 @@ on: merge_group: env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io # github.repository as / IMAGE_NAME: ${{ github.repository }} @@ -66,14 +64,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # Install the cosign tool except on PR - # https://github.com/sigstore/cosign-installer - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@main - with: - cosign-release: 'v1.13.1' # optional - # Setup QEMU # https://github.com/marketplace/actions/docker-setup-buildx#with-qemu - name: Setup QEMU @@ -99,9 +89,15 @@ jobs: if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} # Extract metadata (tags, labels) for Docker # https://github.com/docker/metadata-action @@ -109,7 +105,9 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: | + ${{ env.IMAGE_NAME }} + ghcr.io/${{ env.IMAGE_NAME }} flavor: | latest=auto @@ -133,19 +131,6 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. - # https://github.com/sigstore/cosign -# - name: Sign the published Docker image -# if: ${{ github.event_name != 'pull_request' }} -# env: -# COSIGN_EXPERIMENTAL: "true" -# # This step uses the identity token to provision an ephemeral certificate -# # against the sigstore community Fulcio instance. -# run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }} - # Temp fix # https://github.com/docker/build-push-action/issues/252 # https://github.com/moby/buildkit/issues/1896 From 4a3a4c846e122707e6bb6eacd5805be33e5236bf Mon Sep 17 00:00:00 2001 From: Felix Cornelius <9767036+fcornelius@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:59:52 +0100 Subject: [PATCH 041/195] Feature: Add ArgoCD widget (#4305) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/argocd.md | 33 ++++++++++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 10 +++++ src/utils/proxy/handlers/credentialed.js | 1 + src/widgets/argocd/component.jsx | 52 ++++++++++++++++++++++ src/widgets/argocd/widget.js | 14 ++++++ src/widgets/components.js | 1 + src/widgets/prometheusmetric/component.jsx | 1 + src/widgets/widgets.js | 2 + 10 files changed, 116 insertions(+) create mode 100644 docs/widgets/services/argocd.md create mode 100644 src/widgets/argocd/component.jsx create mode 100644 src/widgets/argocd/widget.js diff --git a/docs/widgets/services/argocd.md b/docs/widgets/services/argocd.md new file mode 100644 index 00000000..6a81b8db --- /dev/null +++ b/docs/widgets/services/argocd.md @@ -0,0 +1,33 @@ +--- +title: ArgoCD +description: ArgoCD Widget Configuration +--- + +Learn more about [ArgoCD](https://argo-cd.readthedocs.io/en/stable/). + +Allowed fields (limited to a max of 4): `["apps", "synced", "outOfSync", "healthy", "progressing", "degraded", "suspended", "missing"]` + +```yaml +widget: + type: argocd + url: http://argocd.host.or.ip:port + key: argocdapikey +``` + +You can generate an API key either by creating a bearer token for an existing account, see [Authorization](https://argo-cd.readthedocs.io/en/latest/developer-guide/api-docs/#authorization) (not recommended) or create a new local user account with limited privileges and generate an authentication token for this account. To do this the steps are: + +- [Create a new local user](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/#create-new-user) and give it the `apiKey` capability +- Setup [RBAC configuration](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/#rbac-configuration) for your the user and give it readonly access to your ArgoCD resources, e.g. by giving it the `role:readonly` role. +- In your ArgoCD project under _Settings / Accounts_ open the newly created account and in the _Tokens_ section click on _Generate New_ to generate an access token, optionally specifying an expiry date. + +If you installed ArgoCD via the official Helm chart, the account creation and rbac config can be achived by overriding these helm values: + +```yaml +configs: + cm: + accounts.readonly: apiKey + rbac: + policy.csv: "g, readonly, role:readonly" +``` + +This creates a new account called `readonly` and attaches the `role:readonly` role to it. diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 8ea2e933..ae506f08 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -8,6 +8,7 @@ search: You can also find a list of all available service widgets in the sidebar navigation. - [Adguard Home](adguard-home.md) +- [ArgoCD](argocd.md) - [Atsumeru](atsumeru.md) - [Audiobookshelf](audiobookshelf.md) - [Authentik](authentik.md) diff --git a/mkdocs.yml b/mkdocs.yml index 42abce30..1e9d59cc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ nav: - "Service Widgets": - widgets/services/index.md - widgets/services/adguard-home.md + - widgets/services/argocd.md - widgets/services/atsumeru.md - widgets/services/audiobookshelf.md - widgets/services/authentik.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 81576984..ab7dcfc9 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -988,5 +988,15 @@ "memory": "MEM", "disk": "Disk", "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index eb2aab69..8d4340c2 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -36,6 +36,7 @@ export default async function credentialedProxyHandler(req, res, map) { headers["X-gotify-Key"] = `${widget.key}`; } else if ( [ + "argocd", "authentik", "cloudflared", "ghostfolio", diff --git a/src/widgets/argocd/component.jsx b/src/widgets/argocd/component.jsx new file mode 100644 index 00000000..d3b51936 --- /dev/null +++ b/src/widgets/argocd/component.jsx @@ -0,0 +1,52 @@ +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { widget } = service; + + if (!widget.fields) { + widget.fields = ["apps", "synced", "outOfSync", "healthy"]; + } + + const MAX_ALLOWED_FIELDS = 4; + if (widget.fields.length > MAX_ALLOWED_FIELDS) { + widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS); + } + + const { data: appsData, error: appsError } = useWidgetAPI(widget, "applications"); + + const appCounts = widget.fields.map((status) => { + if (status === "apps") { + return { status, count: appsData?.items?.length }; + } + const count = appsData?.items?.filter( + (item) => + item.status?.sync?.status.toLowerCase() === status.toLowerCase() || + item.status?.health?.status.toLowerCase() === status.toLowerCase(), + ).length; + return { status, count }; + }); + + if (appsError) { + return ; + } + + if (!appsData) { + return ( + + {appCounts.map((a) => ( + + ))} + + ); + } + + return ( + + {appCounts.map((a) => ( + + ))} + + ); +} diff --git a/src/widgets/argocd/widget.js b/src/widgets/argocd/widget.js new file mode 100644 index 00000000..5030adaa --- /dev/null +++ b/src/widgets/argocd/widget.js @@ -0,0 +1,14 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/v1/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + applications: { + endpoint: "applications", + }, + }, +}; + +export default widget; diff --git a/src/widgets/components.js b/src/widgets/components.js index 3cba84d2..aa476c46 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -2,6 +2,7 @@ import dynamic from "next/dynamic"; const components = { adguard: dynamic(() => import("./adguard/component")), + argocd: dynamic(() => import("./argocd/component")), atsumeru: dynamic(() => import("./atsumeru/component")), audiobookshelf: dynamic(() => import("./audiobookshelf/component")), authentik: dynamic(() => import("./authentik/component")), diff --git a/src/widgets/prometheusmetric/component.jsx b/src/widgets/prometheusmetric/component.jsx index 347aaa0c..350a6b7d 100644 --- a/src/widgets/prometheusmetric/component.jsx +++ b/src/widgets/prometheusmetric/component.jsx @@ -5,6 +5,7 @@ import Block from "components/services/widget/block"; import useWidgetAPI from "utils/proxy/use-widget-api"; function formatValue(t, metric, rawValue) { + if (!metric?.format) return rawValue; if (!rawValue) return "-"; let value = rawValue; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 79110378..0cad5346 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -1,4 +1,5 @@ import adguard from "./adguard/widget"; +import argocd from "./argocd/widget"; import atsumeru from "./atsumeru/widget"; import audiobookshelf from "./audiobookshelf/widget"; import authentik from "./authentik/widget"; @@ -130,6 +131,7 @@ import zabbix from "./zabbix/widget"; const widgets = { adguard, + argocd, atsumeru, audiobookshelf, authentik, From 94bbcbe1fb868f8b60cefe5331d581760fecde32 Mon Sep 17 00:00:00 2001 From: Florian Geckeler <43751896+fgeck@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:32:04 +0100 Subject: [PATCH 042/195] Feature: Spoolman Widget (#3959) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/spoolman.md | 15 +++++++ mkdocs.yml | 1 + public/locales/en/common.json | 3 ++ src/utils/config/service-helpers.js | 6 +++ src/widgets/components.js | 1 + src/widgets/spoolman/component.jsx | 63 +++++++++++++++++++++++++++++ src/widgets/spoolman/widget.js | 14 +++++++ src/widgets/widgets.js | 2 + 8 files changed, 105 insertions(+) create mode 100644 docs/widgets/services/spoolman.md create mode 100644 src/widgets/spoolman/component.jsx create mode 100644 src/widgets/spoolman/widget.js diff --git a/docs/widgets/services/spoolman.md b/docs/widgets/services/spoolman.md new file mode 100644 index 00000000..5baa9268 --- /dev/null +++ b/docs/widgets/services/spoolman.md @@ -0,0 +1,15 @@ +--- +title: Spoolman +description: Spoolman Widget Configuration +--- + +Learn more about [Spoolman](https://github.com/Donkie/Spoolman). + +4 spools are displayed by default. If more than 4 spools are configured in spoolman you can use the spoolIds configuration option to control which are displayed. + +```yaml +widget: + type: spoolman + url: http://spoolman.host.or.ip + spoolIds: [1, 2, 3, 4] # optional +``` diff --git a/mkdocs.yml b/mkdocs.yml index 1e9d59cc..5b350d71 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -138,6 +138,7 @@ nav: - widgets/services/scrutiny.md - widgets/services/sonarr.md - widgets/services/speedtest-tracker.md + - widgets/services/spoolman.md - widgets/services/stash.md - widgets/services/stocks.md - widgets/services/swagdashboard.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index ab7dcfc9..5abb9a4b 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -998,5 +998,8 @@ "progressing": "Progressing", "missing": "Missing", "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 1566a135..ea82c735 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -492,6 +492,9 @@ export function cleanServiceGroups(groups) { // technitium range, + + // spoolman + spoolIds, } = cleanedService.widget; let fieldsList = fields; @@ -653,6 +656,9 @@ export function cleanServiceGroups(groups) { if (metrics) cleanedService.widget.metrics = metrics; if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; } + if (type === "spoolman") { + if (spoolIds !== undefined) cleanedService.widget.spoolIds = spoolIds; + } } return cleanedService; diff --git a/src/widgets/components.js b/src/widgets/components.js index aa476c46..bea37cf2 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -111,6 +111,7 @@ const components = { scrutiny: dynamic(() => import("./scrutiny/component")), sonarr: dynamic(() => import("./sonarr/component")), speedtest: dynamic(() => import("./speedtest/component")), + spoolman: dynamic(() => import("./spoolman/component")), stash: dynamic(() => import("./stash/component")), stocks: dynamic(() => import("./stocks/component")), strelaysrv: dynamic(() => import("./strelaysrv/component")), diff --git a/src/widgets/spoolman/component.jsx b/src/widgets/spoolman/component.jsx new file mode 100644 index 00000000..523ecea7 --- /dev/null +++ b/src/widgets/spoolman/component.jsx @@ -0,0 +1,63 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + // eslint-disable-next-line prefer-const + let { data: spoolData, error: spoolError } = useWidgetAPI(widget, "spools"); + + if (spoolError) { + return ; + } + + if (!spoolData) { + const nBlocksGuess = widget.spoolIds?.length ?? 4; + return ( + + {[...Array(nBlocksGuess)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ); + } + + if (spoolData.error || spoolData.message) { + return ; + } + + if (spoolData.length === 0) { + return ( + + + + ); + } + + if (widget.spoolIds?.length) { + spoolData = spoolData.filter((spool) => widget.spoolIds.includes(spool.id)); + } + + if (spoolData.length > 4) { + spoolData = spoolData.slice(0, 4); + } + + return ( + + {spoolData.map((spool) => ( + + ))} + + ); +} diff --git a/src/widgets/spoolman/widget.js b/src/widgets/spoolman/widget.js new file mode 100644 index 00000000..2c8a3475 --- /dev/null +++ b/src/widgets/spoolman/widget.js @@ -0,0 +1,14 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/v1/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + spools: { + endpoint: "spool", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 0cad5346..8eb3f51f 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -102,6 +102,7 @@ import sabnzbd from "./sabnzbd/widget"; import scrutiny from "./scrutiny/widget"; import sonarr from "./sonarr/widget"; import speedtest from "./speedtest/widget"; +import spoolman from "./spoolman/widget"; import stash from "./stash/widget"; import stocks from "./stocks/widget"; import strelaysrv from "./strelaysrv/widget"; @@ -237,6 +238,7 @@ const widgets = { scrutiny, sonarr, speedtest, + spoolman, stash, stocks, strelaysrv, From 2b8647b2ef61d6eab8cfc34a629506131ca9df44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urs=20Kr=C3=B6ll?= <109229014+UrsKroell@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:07:17 +0100 Subject: [PATCH 043/195] Feature: gitlab service widget (#4317) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/gitlab.md | 20 +++++++++++++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 6 ++++ src/utils/proxy/handlers/credentialed.js | 2 ++ src/widgets/components.js | 1 + src/widgets/gitlab/component.jsx | 36 ++++++++++++++++++++++++ src/widgets/gitlab/widget.js | 13 +++++++++ src/widgets/widgets.js | 2 ++ 9 files changed, 82 insertions(+) create mode 100644 docs/widgets/services/gitlab.md create mode 100644 src/widgets/gitlab/component.jsx create mode 100644 src/widgets/gitlab/widget.js diff --git a/docs/widgets/services/gitlab.md b/docs/widgets/services/gitlab.md new file mode 100644 index 00000000..a92434d8 --- /dev/null +++ b/docs/widgets/services/gitlab.md @@ -0,0 +1,20 @@ +--- +title: Gitlab +description: Gitlab Widget Configuration +--- + +Learn more about [Gitlab](https://gitlab.com). + +API requires a personal access token with either `read_api` or `api` permission. See the [gitlab documentation](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token) for details on generating one. + +Your Gitlab user ID can be found on [your profile page](https://support.circleci.com/hc/en-us/articles/20761157174043-How-to-find-your-GitLab-User-ID). + +Allowed fields: `["events", "issues", "merges", "projects"]`. + +```yaml +widget: + type: gitlab + url: http://gitlab.host.or.ip:port + key: personal-access-token + user_id: 123456 +``` diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index ae506f08..894a31f6 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -41,6 +41,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Gatus](gatus.md) - [Ghostfolio](ghostfolio.md) - [Gitea](gitea.md) +- [Gitlab](gitlab.md) - [Glances](glances.md) - [Gluetun](gluetun.md) - [Gotify](gotify.md) diff --git a/mkdocs.yml b/mkdocs.yml index 5b350d71..a19d3b83 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - widgets/services/gatus.md - widgets/services/ghostfolio.md - widgets/services/gitea.md + - widgets/services/gitlab.md - widgets/services/glances.md - widgets/services/gluetun.md - widgets/services/gotify.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 5abb9a4b..484f76b5 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1001,5 +1001,11 @@ }, "spoolman": { "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 8d4340c2..cbe0422a 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -94,6 +94,8 @@ export default async function credentialedProxyHandler(req, res, map) { } } else if (widget.type === "wgeasy") { headers.Authorization = widget.password; + } else if (widget.type === "gitlab") { + headers["PRIVATE-TOKEN"] = widget.key; } else { headers["X-API-Key"] = `${widget.key}`; } diff --git a/src/widgets/components.js b/src/widgets/components.js index bea37cf2..19f41d4a 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -38,6 +38,7 @@ const components = { gatus: dynamic(() => import("./gatus/component")), ghostfolio: dynamic(() => import("./ghostfolio/component")), gitea: dynamic(() => import("./gitea/component")), + gitlab: dynamic(() => import("./gitlab/component")), glances: dynamic(() => import("./glances/component")), gluetun: dynamic(() => import("./gluetun/component")), gotify: dynamic(() => import("./gotify/component")), diff --git a/src/widgets/gitlab/component.jsx b/src/widgets/gitlab/component.jsx new file mode 100644 index 00000000..fb6f898f --- /dev/null +++ b/src/widgets/gitlab/component.jsx @@ -0,0 +1,36 @@ +import { useTranslation } from "next-i18next"; + +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: gitlabCounts, error: gitlabCountsError } = useWidgetAPI(widget, "counts"); + + if (gitlabCountsError) { + return ; + } + + if (!gitlabCounts) { + return ( + + + + + + + ); + } + + return ( + + + + + + + ); +} diff --git a/src/widgets/gitlab/widget.js b/src/widgets/gitlab/widget.js new file mode 100644 index 00000000..26f77a77 --- /dev/null +++ b/src/widgets/gitlab/widget.js @@ -0,0 +1,13 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/api/v4/{endpoint}", + proxyHandler: credentialedProxyHandler, + mappings: { + counts: { + endpoint: "users/{user_id}/associations_count", + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 8eb3f51f..9d4bb935 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -32,6 +32,7 @@ import gamedig from "./gamedig/widget"; import gatus from "./gatus/widget"; import ghostfolio from "./ghostfolio/widget"; import gitea from "./gitea/widget"; +import gitlab from "./gitlab/widget"; import glances from "./glances/widget"; import gluetun from "./gluetun/widget"; import gotify from "./gotify/widget"; @@ -164,6 +165,7 @@ const widgets = { gatus, ghostfolio, gitea, + gitlab, glances, gluetun, gotify, From 56972535c7a38e2e03cca4ae61443ac2e52d9f34 Mon Sep 17 00:00:00 2001 From: Simon Emms Date: Fri, 22 Nov 2024 15:41:48 +0000 Subject: [PATCH 044/195] Documentation: additional explainer for the pod-selector (#4316) --- docs/configs/kubernetes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configs/kubernetes.md b/docs/configs/kubernetes.md index 06685f3a..29ab6b38 100644 --- a/docs/configs/kubernetes.md +++ b/docs/configs/kubernetes.md @@ -100,6 +100,8 @@ If you are using multiple instances of homepage, an `instance` annotation can be If you have a single service that needs to be shown on multiple specific instances of homepage (but not on all of them), the service can be annotated by multiple `instance.name` annotations, where `name` can be the names of your specific multiple homepage instances. For example, a service that is annotated with `gethomepage.dev/instance.public: ""` and `gethomepage.dev/instance.internal: ""` will be shown on `public` and `internal` homepage instances. +Use the `gethomepage.dev/pod-selector` selector to specify the pod used for the health check. For example, a service that is annotated with `gethomepage.dev/pod-selector: app.kubernetes.io/name=deployment` would link to a pod with the label `app.kubernetes.io/name: deployment`. + ### Traefik IngressRoute support Homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set: From ba5e6dec077a440e6dec5c17d119da8edcc01cba Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:23:07 -0800 Subject: [PATCH 045/195] Documentation: Add missing wan option for opnsense widget --- docs/widgets/services/opnsense.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/widgets/services/opnsense.md b/docs/widgets/services/opnsense.md index faaba6b8..eb6c7c1c 100644 --- a/docs/widgets/services/opnsense.md +++ b/docs/widgets/services/opnsense.md @@ -20,4 +20,5 @@ widget: url: http://opnsense.host.or.ip username: key password: secret + wan: opt1 # optional, defaults to wan ``` From cb45d89163e8448e3285f9afea5dbb6a9342f7ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 09:01:47 -0800 Subject: [PATCH 046/195] New Crowdin translations by GitHub Action (#4053) Co-authored-by: Crowdin Bot --- public/locales/af/common.json | 68 ++++++- public/locales/ar/common.json | 56 +++++- public/locales/bg/common.json | 56 +++++- public/locales/ca/common.json | 112 ++++++++--- public/locales/cs/common.json | 56 +++++- public/locales/da/common.json | 56 +++++- public/locales/de/common.json | 58 +++++- public/locales/el/common.json | 56 +++++- public/locales/eo/common.json | 56 +++++- public/locales/es/common.json | 56 +++++- public/locales/eu/common.json | 56 +++++- public/locales/fi/common.json | 56 +++++- public/locales/fr/common.json | 128 +++++++++---- public/locales/he/common.json | 56 +++++- public/locales/hi/common.json | 56 +++++- public/locales/hr/common.json | 74 +++++++- public/locales/hu/common.json | 56 +++++- public/locales/id/common.json | 56 +++++- public/locales/it/common.json | 56 +++++- public/locales/ja/common.json | 56 +++++- public/locales/ko/common.json | 56 +++++- public/locales/lv/common.json | 56 +++++- public/locales/ms/common.json | 56 +++++- public/locales/nl/common.json | 70 ++++++- public/locales/no/common.json | 56 +++++- public/locales/pl/common.json | 96 +++++++--- public/locales/pt/common.json | 290 +++++++++++++++++------------ public/locales/pt_BR/common.json | 56 +++++- public/locales/ro/common.json | 56 +++++- public/locales/ru/common.json | 140 +++++++++----- public/locales/sk/common.json | 56 +++++- public/locales/sl/common.json | 56 +++++- public/locales/sr/common.json | 56 +++++- public/locales/sv/common.json | 56 +++++- public/locales/te/common.json | 56 +++++- public/locales/th/common.json | 56 +++++- public/locales/tr/common.json | 58 +++++- public/locales/uk/common.json | 254 +++++++++++++++---------- public/locales/vi/common.json | 56 +++++- public/locales/yue/common.json | 56 +++++- public/locales/zh-Hans/common.json | 118 ++++++++---- public/locales/zh-Hant/common.json | 56 +++++- 42 files changed, 2707 insertions(+), 439 deletions(-) diff --git a/public/locales/af/common.json b/public/locales/af/common.json index ef835d72..5f5f1883 100644 --- a/public/locales/af/common.json +++ b/public/locales/af/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "ma", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Gestop", "total": "Totaal" }, + "suwayomi": { + "download": "Afgelaai", + "nondownload": "Nie-Afgelaai", + "read": "Gelees", + "unread": "Ongelees", + "downloadedread": "Afgelaai & Gelees", + "downloadedunread": "Afgelaai en Ongelees", + "nondownloadedread": "Nie-Afgelaai & Gelees", + "nondownloadedunread": "Nie-Afgelaai & Ongelees" + }, "tailscale": { "address": "Adres", "expires": "Verval", @@ -947,11 +957,55 @@ "disaster": "Ramp" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Voertuig", + "vehicles": "Voertuie", + "serviceRecords": "Diensrekords", + "reminders": "Herinneringe", + "nextReminder": "Volgende Herinnering", + "none": "Geen" + }, + "vikunja": { + "projects": "Aktiewe Projekte", + "tasks7d": "Take Hierdie week", + "tasksOverdue": "Agterstallige Take", + "tasksInProgress": "Take Aan Die Gang" + }, + "headscale": { + "name": "Naam", + "address": "Adres", + "last_seen": "Laaste Gesien", + "status": "Status", + "online": "Aanlyn", + "offline": "Vanlyn" + }, + "beszel": { + "name": "Naam", + "systems": "Stelsels", + "up": "Op", + "status": "Status", + "updated": "Opgedateer", + "cpu": "SVE", + "memory": "GEH", + "disk": "Skyf", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Gesond", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Vermis", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Laai" + }, + "gitlab": { + "groups": "Groups", + "issues": "Kwessies", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json index 305a01f0..558de975 100644 --- a/public/locales/ar/common.json +++ b/public/locales/ar/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "ش", "days": "ي", "hours": "س", @@ -309,6 +309,16 @@ "stopped": "متوقف", "total": "المجموع" }, + "suwayomi": { + "download": "مُنزل", + "nondownload": "Non-Downloaded", + "read": "قراءة", + "unread": "غير مقروءة", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "عنوان", "expires": "تنتهي", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "الاسم", + "address": "عنوان", + "last_seen": "آخر ظهور", + "status": "الحالة", + "online": "مُتّصل", + "offline": "غير متصل" + }, + "beszel": { + "name": "الاسم", + "systems": "Systems", + "up": "يعمل", + "status": "الحالة", + "updated": "محدث", + "cpu": "المعالج", + "memory": "الذاكرة", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "سليم", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "مفقود", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "تحميل" + }, + "gitlab": { + "groups": "Groups", + "issues": "المُشكِلات", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json index 325e5583..15c38b14 100644 --- a/public/locales/bg/common.json +++ b/public/locales/bg/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "ч", @@ -309,6 +309,16 @@ "stopped": "Спрян", "total": "Общо" }, + "suwayomi": { + "download": "Изтеглени", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Последно видян", + "status": "Статус", + "online": "Online", + "offline": "Изключен" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Статус", + "updated": "Updated", + "cpu": "Процесор", + "memory": "Памет", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Липсващи", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json index 46340e4a..9eb7e447 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mes", "days": "d", "hours": "h", @@ -227,8 +227,8 @@ "seed": "Llavors" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "Bytes trobats a la memòria cau", + "cachemissbytes": "Bytes no trobats a la memòria cau" }, "downloadstation": { "download": "Descarregar", @@ -309,6 +309,16 @@ "stopped": "Aturat", "total": "Total" }, + "suwayomi": { + "download": "Descarregat", + "nondownload": "No descarregat", + "read": "Llegit", + "unread": "Sense llegir", + "downloadedread": "Descarregat i llegit", + "downloadedunread": "Descarregat i per llegir", + "nondownloadedread": "No descarregat i llegit", + "nondownloadedunread": "No descarregat i per llegir" + }, "tailscale": { "address": "Adreça", "expires": "Caduca", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Consultes", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Èxits", + "totalServerFailure": "Fallades", + "totalNxDomain": "Dominis NX", + "totalRefused": "Rebutjat", + "totalAuthoritative": "Autoritatiu", + "totalRecursive": "Recursiu", + "totalCached": "A la memòria cau", "totalBlocked": "Bloquejat", - "totalDropped": "Dropped", + "totalDropped": "Abandonat", "totalClients": "Clients" }, "tdarr": { @@ -844,16 +854,16 @@ }, "romm": { "platforms": "Plataformes", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalRoms": "Jocs", + "saves": "Partides desades", + "states": "Estats", + "screenshots": "Captures de pantalla", + "totalfilesize": "Tamany total" }, "mailcow": { "domains": "Dominis", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Bústies", + "mails": "Correus", "storage": "Emmagatzematge" }, "netdata": { @@ -934,24 +944,68 @@ "version": "Versió" }, "linkwarden": { - "links": "Links", - "collections": "Collections", + "links": "Enllaços", + "collections": "Col·leccions", "tags": "Etiquetes" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "No classificat", "information": "Informació", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Avís", + "average": "Mitjana", + "high": "Alt", + "disaster": "Desastre" }, "lubelogger": { "vehicle": "Vehicle", "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "serviceRecords": "Constàncies de manteniment", + "reminders": "Recordatoris", + "nextReminder": "Proper recordatori", + "none": "Cap" + }, + "vikunja": { + "projects": "Projectes actius", + "tasks7d": "Tasques a completar aquesta setmana", + "tasksOverdue": "Tasques vençudes", + "tasksInProgress": "Tasques en marxa" + }, + "headscale": { + "name": "Nom", + "address": "Adreça", + "last_seen": "Vist per darrer cop", + "status": "Estat", + "online": "En línia", + "offline": "Fora de línia" + }, + "beszel": { + "name": "Nom", + "systems": "Sistemes", + "up": "Actiu", + "status": "Estat", + "updated": "Actualitzat", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disc", + "network": "XARXA" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Saludable", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Falten", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Carregant" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemes", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json index 3cf6aa3b..983beafe 100644 --- a/public/locales/cs/common.json +++ b/public/locales/cs/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "měs.", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Zastaveno", "total": "Celkem" }, + "suwayomi": { + "download": "Staženo", + "nondownload": "Non-Downloaded", + "read": "Přečteno", + "unread": "Nepřečteno", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adresa", "expires": "Vyprší", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Adresa", + "last_seen": "Naposledy viděno", + "status": "Stav", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Stav", + "updated": "Aktualizováno", + "cpu": "CPU", + "memory": "RAM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Zdravý", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Chybějící", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problémy", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/da/common.json b/public/locales/da/common.json index 6c6fd9b2..de447397 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mnd", "days": "d", "hours": "t", @@ -309,6 +309,16 @@ "stopped": "Stoppede", "total": "Total" }, + "suwayomi": { + "download": "Hentet", + "nondownload": "Non-Downloaded", + "read": "Læst", + "unread": "Ulæst", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adresse", "expires": "Udløber", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Navn", + "address": "Adresse", + "last_seen": "Sidst Set", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Navn", + "systems": "Systems", + "up": "Op", + "status": "Status", + "updated": "Opdateret", + "cpu": "CPU", + "memory": "RAM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sund", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Mangler", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemer", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 254d696d..647e7d8a 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "Mo.", "days": "d", "hours": "h", @@ -305,10 +305,20 @@ "ping": "Ping" }, "portainer": { - "running": "Werden ausgeführt", + "running": "Wird ausgeführt", "stopped": "Gestoppt", "total": "Gesamt" }, + "suwayomi": { + "download": "Heruntergeladen", + "nondownload": "Nicht heruntergeladen", + "read": "Gelesen", + "unread": "Ungelesen", + "downloadedread": "Heruntergeladen & gelesen", + "downloadedunread": "Heruntergeladen & ungelesen", + "nondownloadedread": "Nicht heruntergeladen & gelesen", + "nondownloadedunread": "Nicht heruntergeladen & ungelesen" + }, "tailscale": { "address": "Adresse", "expires": "Läuft ab", @@ -953,5 +963,49 @@ "reminders": "Erinnerungen", "nextReminder": "Nächste Erinnerung", "none": "Keine" + }, + "vikunja": { + "projects": "Aktive Projekte", + "tasks7d": "Diese Woche fällige Aufgaben", + "tasksOverdue": "Überfällige Aufgaben", + "tasksInProgress": "Aufgaben in Arbeit" + }, + "headscale": { + "name": "Name", + "address": "Adresse", + "last_seen": "Zuletzt gesehen", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systeme", + "up": "Senden", + "status": "Status", + "updated": "Aktualisiert", + "cpu": "CPU", + "memory": "RAM", + "disk": "Festplatte", + "network": "NET" + }, + "argocd": { + "apps": "Anwendungen", + "synced": "Synchronisiert", + "outOfSync": "Nicht mehr synchronisiert", + "healthy": "Fehlerfrei", + "degraded": "Beeinträchtigt", + "progressing": "Fortschritt", + "missing": "Fehlend", + "suspended": "Unterbrochen" + }, + "spoolman": { + "loading": "Wird geladen" + }, + "gitlab": { + "groups": "Groups", + "issues": "Probleme", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/el/common.json b/public/locales/el/common.json index 08c91246..8ce8a7e0 100644 --- a/public/locales/el/common.json +++ b/public/locales/el/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Σταματημένο", "total": "Σύνολο" }, + "suwayomi": { + "download": "Κατεβασμένο", + "nondownload": "Non-Downloaded", + "read": "Διαβάστηκε", + "unread": "Μη Διαβασμένο", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Διεύθυνση", "expires": "Λήγει", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Όνομα", + "address": "Διεύθυνση", + "last_seen": "Τελευταία Σύνδεση", + "status": "Κατάσταση", + "online": "Συνδεδεμένοι", + "offline": "Εκτός σύνδεσης" + }, + "beszel": { + "name": "Όνομα", + "systems": "Systems", + "up": "Ping up", + "status": "Κατάσταση", + "updated": "Ενημερώθηκε", + "cpu": "Επεξεργαστής", + "memory": "Μνήμη", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Υγειές", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Απουσιάζει", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/eo/common.json b/public/locales/eo/common.json index 8deaa62f..fd21b925 100644 --- a/public/locales/eo/common.json +++ b/public/locales/eo/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Totalo" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Stato", + "online": "Online", + "offline": "Malkonekta" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Stato", + "updated": "Updated", + "cpu": "Ĉefprocesoro", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sana", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 4f9affdf..299307d0 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "me", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Detenido", "total": "Total" }, + "suwayomi": { + "download": "Descargado", + "nondownload": "Non-Downloaded", + "read": "Leer", + "unread": "Sin leer", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Dirección", "expires": "Caduca en", @@ -953,5 +963,49 @@ "reminders": "Recordatorios", "nextReminder": "Siguiente recordatorio", "none": "Nada" + }, + "vikunja": { + "projects": "Proyectos activos", + "tasks7d": "Tareas que vencen esta semana", + "tasksOverdue": "Tareas vencidas", + "tasksInProgress": "Tareas en progreso" + }, + "headscale": { + "name": "Nombre", + "address": "Dirección", + "last_seen": "Visto por última vez", + "status": "Estado", + "online": "En línea", + "offline": "Desconectado" + }, + "beszel": { + "name": "Nombre", + "systems": "Systems", + "up": "Activo", + "status": "Estado", + "updated": "Actualizado", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Saludable", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Faltantes", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Cargando" + }, + "gitlab": { + "groups": "Groups", + "issues": "Números", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/eu/common.json b/public/locales/eu/common.json index e96ef729..ef3c5262 100644 --- a/public/locales/eu/common.json +++ b/public/locales/eu/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Guztira" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Status", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Osasuntsu", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/fi/common.json b/public/locales/fi/common.json index f5724790..402aa8d3 100644 --- a/public/locales/fi/common.json +++ b/public/locales/fi/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Pysäytetty", "total": "Yhteensä" }, + "suwayomi": { + "download": "Ladattu", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Tila", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Tila", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 20b76e93..2f27665d 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "j", "hours": "h", @@ -227,8 +227,8 @@ "seed": "Seed" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "Octets de la mémoire cache", + "cachemissbytes": "Octets manquants du cache" }, "downloadstation": { "download": "Récep.", @@ -309,6 +309,16 @@ "stopped": "Arrêté", "total": "Total" }, + "suwayomi": { + "download": "Téléchargé", + "nondownload": "Non téléchargé", + "read": "Lu", + "unread": "Non lu", + "downloadedread": "Téléchargé et lu", + "downloadedunread": "Téléchargé et non lu", + "nondownloadedread": "Non téléchargé et lu", + "nondownloadedunread": "Non téléchargé et non lu" + }, "tailscale": { "address": "Adresse", "expires": "Expire", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Requêtes", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Effectué avec succès", + "totalServerFailure": "Échecs", + "totalNxDomain": "Domaines NX", + "totalRefused": "Refusés", + "totalAuthoritative": "Autoritaire", + "totalRecursive": "Récursif", + "totalCached": "Mis en cache", "totalBlocked": "Bloqué", - "totalDropped": "Dropped", + "totalDropped": "Abandonné", "totalClients": "Clients" }, "tdarr": { @@ -756,7 +766,7 @@ "books": "Livres", "authors": "Auteurs", "categories": "Catégories", - "series": "Séries" + "series": "Séries TV" }, "jdownloader": { "downloadCount": "En attente", @@ -844,16 +854,16 @@ }, "romm": { "platforms": "Plateformes", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalRoms": "Jeux", + "saves": "Sauvegardes", + "states": "États", + "screenshots": "Captures d'écran", + "totalfilesize": "Taille totale" }, "mailcow": { "domains": "Domaines", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Boites mail", + "mails": "Courriels", "storage": "Stockage" }, "netdata": { @@ -922,36 +932,80 @@ "upload": "Envoi" }, "stocks": { - "stocks": "Stocks", - "loading": "Loading", - "open": "Open - US Market", - "closed": "Closed - US Market", - "invalidConfiguration": "Invalid Configuration" + "stocks": "Actions", + "loading": "Chargement", + "open": "Ouvert - Marché américain", + "closed": "Fermé - marché américain", + "invalidConfiguration": "Configuration invalide" }, "frigate": { - "cameras": "Cameras", + "cameras": "Caméras", "uptime": "Démarré depuis", "version": "Version" }, "linkwarden": { - "links": "Links", + "links": "Liens", "collections": "Collections", "tags": "Étiquettes" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "Non classé", "information": "Informations", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Attention", + "average": "Moyenne", + "high": "Élevé", + "disaster": "" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Véhicule", + "vehicles": "Véhicules", + "serviceRecords": "Service d'enregistrements", + "reminders": "Rappels", + "nextReminder": "Prochain rappel", + "none": "Aucun" + }, + "vikunja": { + "projects": "Projets actifs", + "tasks7d": "Tâches à faire cette semaine", + "tasksOverdue": "Tâches en retard", + "tasksInProgress": "Tâche en cours" + }, + "headscale": { + "name": "Nom", + "address": "Adresse", + "last_seen": "Vu pour la dernière fois", + "status": "Statut", + "online": "En ligne", + "offline": "Hors ligne" + }, + "beszel": { + "name": "Nom", + "systems": "Systèmes", + "up": "Up", + "status": "Statut", + "updated": "Mis à jour", + "cpu": "CPU", + "memory": "MÉM", + "disk": "Disque", + "network": "Réseau" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Fonctionnel", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Manquant", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Chargement" + }, + "gitlab": { + "groups": "Groups", + "issues": "Anomalies", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/he/common.json b/public/locales/he/common.json index 2f2fdbd8..0b888396 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "נעצר", "total": "סה\"כ" }, + "suwayomi": { + "download": "הורד", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "סטטוס", + "online": "Online", + "offline": "כבוי" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "סטטוס", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index c2b9bb70..2076bce7 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{value, date}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "माह", "days": "d", "hours": "घं.", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Total" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Status", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index e8963189..35ae8dab 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mj", "days": "dan(a)", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Prekinuto", "total": "Ukupno" }, + "suwayomi": { + "download": "Preuzeto", + "nondownload": "Non-Downloaded", + "read": "Pročitano", + "unread": "Nepročitano", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adresa", "expires": "Isteče", @@ -844,11 +854,11 @@ }, "romm": { "platforms": "Platforme", - "totalRoms": "Games", + "totalRoms": "Igre", "saves": "Saves", "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "screenshots": "Snimke ekrana", + "totalfilesize": "Ukupna veličina" }, "mailcow": { "domains": "Domene", @@ -929,19 +939,19 @@ "invalidConfiguration": "Invalid Configuration" }, "frigate": { - "cameras": "Cameras", + "cameras": "Kamere", "uptime": "Vrijeme rada", "version": "Verzija" }, "linkwarden": { - "links": "Links", - "collections": "Collections", + "links": "Poveznice", + "collections": "Zbirke", "tags": "Oznake" }, "zabbix": { "unclassified": "Not classified", "information": "Informacije", - "warning": "Warning", + "warning": "Upozorenje", "average": "Average", "high": "High", "disaster": "Disaster" @@ -950,8 +960,52 @@ "vehicle": "Vehicle", "vehicles": "Vehicles", "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", + "reminders": "Podsjetnici", + "nextReminder": "Sljedeći podsjetnik", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Ime", + "address": "Adresa", + "last_seen": "Zadnje viđeno", + "status": "Stanje", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Ime", + "systems": "Systems", + "up": "Dostupno", + "status": "Stanje", + "updated": "Aktualizirano", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Funkcionalno", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Nedostaje", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemi", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index 5dd30479..abe09fde 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "hó", "days": "n", "hours": "ó", @@ -309,6 +309,16 @@ "stopped": "Megállított", "total": "Összes" }, + "suwayomi": { + "download": "Letöltött", + "nondownload": "Non-Downloaded", + "read": "Olvasott", + "unread": "Olvasatlan", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Cím", "expires": "Lejár", @@ -953,5 +963,49 @@ "reminders": "Emlékeztetők", "nextReminder": "Következő emlékeztető", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Név", + "address": "Cím", + "last_seen": "Utoljára látott", + "status": "Státusz", + "online": "Csatlakozva", + "offline": "Nem elérhető" + }, + "beszel": { + "name": "Név", + "systems": "Systems", + "up": "Fel", + "status": "Státusz", + "updated": "Frissített", + "cpu": "Processzor", + "memory": "RAM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Egészséges", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Hiányzik", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problémák", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/id/common.json b/public/locales/id/common.json index 25ff4526..9d65cbdf 100644 --- a/public/locales/id/common.json +++ b/public/locales/id/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "bulan", "days": "h", "hours": "j", @@ -309,6 +309,16 @@ "stopped": "Terhenti", "total": "Total" }, + "suwayomi": { + "download": "Terunduh", + "nondownload": "Non-Downloaded", + "read": "Baca", + "unread": "Belum Dibaca", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Alamat", "expires": "Kadaluarsa", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nama", + "address": "Alamat", + "last_seen": "Terakhir terlihat", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Nama", + "systems": "Systems", + "up": "Hidup", + "status": "Status", + "updated": "Terbarui", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Lancar", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Tidak Ditemukan", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Isu", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 77e09d1e..eca25f3f 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "g", "hours": "o", @@ -309,6 +309,16 @@ "stopped": "Fermati", "total": "Totale" }, + "suwayomi": { + "download": "Scaricato", + "nondownload": "Non-Downloaded", + "read": "Letti", + "unread": "Non letto", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Indirizzo", "expires": "Scade", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nome", + "address": "Indirizzo", + "last_seen": "Ultima visualizzazione", + "status": "Stato", + "online": "Online", + "offline": "Non in linea" + }, + "beszel": { + "name": "Nome", + "systems": "Systems", + "up": "Up", + "status": "Stato", + "updated": "Aggiornato", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sano", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Mancanti", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemi", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 0734f105..e12ccadf 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "月", "days": "日", "hours": "時間", @@ -309,6 +309,16 @@ "stopped": "停止中", "total": "合計" }, + "suwayomi": { + "download": "ダウンロード", + "nondownload": "Non-Downloaded", + "read": "既読", + "unread": "未読", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "アドレス", "expires": "失効", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "名前", + "address": "アドレス", + "last_seen": "最終日時", + "status": "状態", + "online": "オンライン", + "offline": "オフライン" + }, + "beszel": { + "name": "名前", + "systems": "Systems", + "up": "稼働", + "status": "状態", + "updated": "更新済", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "正常", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "不明", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "読み込み中" + }, + "gitlab": { + "groups": "Groups", + "issues": "課題", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json index 245510f1..c760fe30 100644 --- a/public/locales/ko/common.json +++ b/public/locales/ko/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "중지", "total": "총합" }, + "suwayomi": { + "download": "다운로드됨", + "nondownload": "Non-Downloaded", + "read": "읽음", + "unread": "미열람", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "주소", "expires": "만료", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "이름", + "address": "주소", + "last_seen": "마지막 접속", + "status": "상태", + "online": "온라인", + "offline": "중지" + }, + "beszel": { + "name": "이름", + "systems": "Systems", + "up": "Up", + "status": "상태", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "좋음", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "빠짐", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "로드 중" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/lv/common.json b/public/locales/lv/common.json index db1fb11b..e15ccc08 100644 --- a/public/locales/lv/common.json +++ b/public/locales/lv/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Kopā" }, + "suwayomi": { + "download": "Lejupielādēts", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Statuss", + "online": "Online", + "offline": "Bezsaistē" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Statuss", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ms/common.json b/public/locales/ms/common.json index 08f078ca..f1c4407b 100644 --- a/public/locales/ms/common.json +++ b/public/locales/ms/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "bln", "days": "h", "hours": "j", @@ -309,6 +309,16 @@ "stopped": "Terhenti", "total": "Jumlah" }, + "suwayomi": { + "download": "Telah Muat Turun", + "nondownload": "Non-Downloaded", + "read": "Baca", + "unread": "Belum dibaca", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nama", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Dalam Talian", + "offline": "Luar talian" + }, + "beszel": { + "name": "Nama", + "systems": "Systems", + "up": "Hidup", + "status": "Status", + "updated": "Dikemaskini", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sihat", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Hilang", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index e80031cd..60face07 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mnd", "days": "d", "hours": "u", @@ -309,6 +309,16 @@ "stopped": "Gestopt", "total": "Totaal" }, + "suwayomi": { + "download": "Gedownload", + "nondownload": "Non-Downloaded", + "read": "Gelezen", + "unread": "Ongelezen", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adres", "expires": "Verloopt", @@ -852,7 +862,7 @@ }, "mailcow": { "domains": "Domeinen", - "mailboxes": "Mailboxes", + "mailboxes": "Mailboxen", "mails": "Mails", "storage": "Opslag" }, @@ -947,11 +957,55 @@ "disaster": "Disaster" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Voertuig", + "vehicles": "Voertuigen", + "serviceRecords": "Service Historie", + "reminders": "Herinneringen", + "nextReminder": "Volgende Herinnering", + "none": "Geen" + }, + "vikunja": { + "projects": "Actieve Projecten", + "tasks7d": "Taken Die Deze Week Af Moeten Zijn", + "tasksOverdue": "Achterstallige Taken", + "tasksInProgress": "Taken In Uitvoering" + }, + "headscale": { + "name": "Naam", + "address": "Adres", + "last_seen": "Laatst Gezien", + "status": "Status", + "online": "Bereikbaar", + "offline": "Offline" + }, + "beszel": { + "name": "Naam", + "systems": "Systems", + "up": "Online", + "status": "Status", + "updated": "Bijgewerkt", + "cpu": "CPU", + "memory": "GEH", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Gezond", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Ontbreekt", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Laden" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemen", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/no/common.json b/public/locales/no/common.json index f6091b60..f987aadb 100644 --- a/public/locales/no/common.json +++ b/public/locales/no/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mnd", "days": "d", "hours": "t", @@ -309,6 +309,16 @@ "stopped": "Stoppet", "total": "Totalt" }, + "suwayomi": { + "download": "Nedlastede", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Ulest", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adresse", "expires": "Utgår", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Navn", + "address": "Adresse", + "last_seen": "Sist sett", + "status": "Status", + "online": "På nett", + "offline": "Frakoblet" + }, + "beszel": { + "name": "Navn", + "systems": "Systems", + "up": "Oppe", + "status": "Status", + "updated": "Oppdatert", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Friskt", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Mangler", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index d762743f..0d14325c 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mc", "days": "d", "hours": "g", @@ -309,6 +309,16 @@ "stopped": "Zatrzymane", "total": "Całkowite" }, + "suwayomi": { + "download": "Pobrano", + "nondownload": "Non-Downloaded", + "read": "Przeczytane", + "unread": "Nieprzeczytane", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adres", "expires": "Wygasa za", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Zapytania", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Sukces", + "totalServerFailure": "Porażki", + "totalNxDomain": "Domeny NX", + "totalRefused": "Odrzucone", + "totalAuthoritative": "Autorytatywne", + "totalRecursive": "Rekursywne", + "totalCached": "Zbuforowane", "totalBlocked": "Zablokowane", - "totalDropped": "Dropped", + "totalDropped": "Upuszczone", "totalClients": "Klienci" }, "tdarr": { @@ -844,16 +854,16 @@ }, "romm": { "platforms": "Platformy", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalRoms": "Gry", + "saves": "Zapisy", + "states": "Stany", + "screenshots": "Screeny", + "totalfilesize": "Rozmiar całkowity" }, "mailcow": { "domains": "Domeny", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Skrzynki", + "mails": "Poczta", "storage": "Pamięć" }, "netdata": { @@ -939,12 +949,12 @@ "tags": "Tagi" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "Niezaklasyfikowane", "information": "Informacje", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Ostrzeżenie", + "average": "Średnia", + "high": "Wysokie", + "disaster": "Katastrofa" }, "lubelogger": { "vehicle": "Vehicle", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nazwa", + "address": "Adres", + "last_seen": "Ostatnio dostępny", + "status": "Stan", + "online": "Dostępny", + "offline": "Nieosiągalny" + }, + "beszel": { + "name": "Nazwa", + "systems": "Systems", + "up": "Dostępny", + "status": "Stan", + "updated": "Zaktualizowane", + "cpu": "Procesor", + "memory": "RAM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Zdrowy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Brakujące", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Wczytywanie" + }, + "gitlab": { + "groups": "Groups", + "issues": "Zgłoszenia", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 264cf3d3..3d3de0a8 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mês", "days": "d", "hours": "h", @@ -30,9 +30,9 @@ "response_data": "Dados da Resposta" }, "weather": { - "current": "Localização atual", + "current": "Localização actual", "allow": "Clique para permitir", - "updating": "Atualizando", + "updating": "A actualizar", "wait": "Por favor aguarde" }, "search": { @@ -79,7 +79,7 @@ "starting": "A iniciar", "unhealthy": "Não-saudável", "not_found": "Não Encontrado", - "exited": "Encerrado", + "exited": "Saiu", "partial": "Parcial" }, "ping": { @@ -117,10 +117,10 @@ "evcc": { "pv_power": "Produção", "battery_soc": "Bateria", - "grid_power": "Grade", + "grid_power": "Grelha", "home_power": "Consumo", "charge_power": "Carregador", - "watt_hour": "Kw" + "watt_hour": "Wh" }, "flood": { "download": "Descarregar", @@ -129,16 +129,16 @@ "seed": "Semente" }, "freshrss": { - "subscriptions": "Assinaturas", + "subscriptions": "Subscrições", "unread": "Não lida" }, "fritzbox": { "connectionStatus": "Estado", - "connectionStatusUnconfigured": "Não configurado", + "connectionStatusUnconfigured": "Desconfigurado", "connectionStatusConnecting": "A conectar", - "connectionStatusAuthenticating": "Autenticando", + "connectionStatusAuthenticating": "A Autenticar", "connectionStatusPendingDisconnect": "Desconexão pendente", - "connectionStatusDisconnecting": "Desconectando", + "connectionStatusDisconnecting": "A Desconectar", "connectionStatusDisconnected": "Desconectado", "connectionStatusConnected": "Conectado", "uptime": "Ligado", @@ -148,19 +148,19 @@ "up": "Up", "received": "Recebido", "sent": "Enviado", - "externalIPAddress": "Endereço IP externo" + "externalIPAddress": "Endereço IP Externo" }, "caddy": { "upstreams": "Upstreams", - "requests": "Solicitações atuais", - "requests_failed": "Solicitações com falha" + "requests": "Solicitações actuais", + "requests_failed": "Solicitações falhadas" }, "changedetectionio": { "totalObserved": "Total Observado", - "diffsDetected": "Diferenças Detetadas" + "diffsDetected": "Diferenças Detectadas" }, "channelsdvrserver": { - "shows": "Shows", + "shows": "Séries", "recordings": "Gravações", "scheduled": "Agendado", "passes": "Passes" @@ -170,7 +170,7 @@ "transcoding": "Transcodificação", "bitrate": "Taxa de bits", "no_active": "Sem streams ativas", - "plex_connection_error": "Verifique a conexão Plex" + "plex_connection_error": "Verifique a conexão do Plex" }, "omada": { "connectedAp": "APs Ligados", @@ -182,10 +182,10 @@ "nzbget": { "rate": "Taxa", "remaining": "Restante", - "downloaded": "Baixado" + "downloaded": "Descarregado" }, "plex": { - "streams": "Streams Ativas", + "streams": "Streams Activas", "albums": "Álbuns", "movies": "Filmes", "tv": "Series de TV" @@ -193,10 +193,10 @@ "sabnzbd": { "rate": "Taxa", "queue": "Fila", - "timeleft": "Tempo restante" + "timeleft": "Tempo Restante" }, "rutorrent": { - "active": "Ativo", + "active": "Activo", "upload": "Carregar", "download": "Descarregar" }, @@ -214,8 +214,8 @@ }, "qnap": { "cpuUsage": "Utilização do CPU", - "memUsage": "Utilização de memória", - "systemTempC": "Temperatura do sistema", + "memUsage": "Utilização de Memória", + "systemTempC": "Temperatura do Sistema", "poolUsage": "Uso de Banco", "volumeUsage": "Uso do Volume", "invalid": "Inválido" @@ -227,8 +227,8 @@ "seed": "Semente" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "‘Bytes’ de Acerto na Memória transitória", + "cachemissbytes": "‘Bytes’ de Falha de Memória transitória" }, "downloadstation": { "download": "Descarregar", @@ -237,54 +237,54 @@ "seed": "Semente" }, "sonarr": { - "wanted": "Desejada", - "queued": "Em fila", + "wanted": "Desejados", + "queued": "Em fila de espera", "series": "Séries", "queue": "Fila", "unknown": "Desconhecido" }, "radarr": { - "wanted": "Desejada", - "missing": "Faltando", - "queued": "Em fila", + "wanted": "Desejados", + "missing": "Em falta", + "queued": "Em fila de espera", "movies": "Filmes", "queue": "Fila", "unknown": "Desconhecido" }, "lidarr": { - "wanted": "Desejada", - "queued": "Em fila", + "wanted": "Desejados", + "queued": "Em fila de espera", "artists": "Artistas" }, "readarr": { - "wanted": "Desejada", - "queued": "Em fila", + "wanted": "Desejados", + "queued": "Em fila de espera", "books": "Livros" }, "bazarr": { - "missingEpisodes": "Episódios Faltantes", - "missingMovies": "Filmes Faltantes" + "missingEpisodes": "Episódios em Falta", + "missingMovies": "Filmes em Falta" }, "ombi": { "pending": "Pendente", - "approved": "Aprovada", + "approved": "Aprovado", "available": "Disponível" }, "jellyseerr": { "pending": "Pendente", - "approved": "Aprovada", + "approved": "Aprovado", "available": "Disponível" }, "overseerr": { "pending": "Pendente", - "processing": "Processando", - "approved": "Aprovada", + "processing": "A Processar", + "approved": "Aprovado", "available": "Disponível" }, "netalertx": { "total": "Total", "connected": "Conectado", - "new_devices": "Novos dispositivos", + "new_devices": "Novos Dispositivos", "down_alerts": "Alertas de Falha" }, "pihole": { @@ -309,11 +309,21 @@ "stopped": "Parado", "total": "Total" }, + "suwayomi": { + "download": "Descarregado", + "nondownload": "Non-Downloaded", + "read": "Lido", + "unread": "Não lida", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Endereço", "expires": "Expira", "never": "Nunca", - "last_seen": "Última vez visto", + "last_seen": "Última Vez Visto", "now": "Agora", "years": "{{number}}y", "weeks": "{{number}}w", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Consultas", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Sucesso", + "totalServerFailure": "Falhas", + "totalNxDomain": "Domínios NX", + "totalRefused": "Recusado", + "totalAuthoritative": "Autoritário", + "totalRecursive": "Recursivo", + "totalCached": "Em Memória transitória", "totalBlocked": "Bloqueado", - "totalDropped": "Dropped", + "totalDropped": "Perdidos", "totalClients": "Clientes" }, "tdarr": { @@ -345,19 +355,19 @@ "traefik": { "routers": "Roteadores", "services": "Serviços", - "middleware": "Software Middleware" + "middleware": "Middleware" }, "navidrome": { "nothing_streaming": "Sem streams ativas", - "please_wait": "Por favor aguarde" + "please_wait": "Por Favor, Aguarde" }, "npm": { - "enabled": "Ativo", + "enabled": "Activo", "disabled": "Desabilitado", "total": "Total" }, "coinmarketcap": { - "configure": "Configurar uma ou mais moedas", + "configure": "Configure uma ou mais criptomoedas para rastrear", "1hour": "1 Hora", "1day": "1 Dia", "7days": "7 Dias", @@ -391,8 +401,8 @@ "domain_count": "Domínios" }, "medusa": { - "wanted": "Desejada", - "queued": "Em fila", + "wanted": "Desejados", + "queued": "Em fila de espera", "series": "Séries" }, "minecraft": { @@ -408,8 +418,8 @@ }, "authentik": { "users": "Utilizadores", - "loginsLast24H": "Inícios de sessão (24h)", - "failedLoginsLast24H": "Inícios de sessão falhados (24h)" + "loginsLast24H": "Inícios de Sessão (24h)", + "failedLoginsLast24H": "Inícios de Sessão Falhados (24h)" }, "proxmox": { "mem": "MEM", @@ -440,7 +450,7 @@ "quicklaunch": { "bookmark": "Marcador", "service": "Serviço", - "search": "Busca", + "search": "Pesquisa", "custom": "Personalizado", "visit": "Visitar", "url": "Endereço URL", @@ -449,7 +459,7 @@ "wmo": { "0-day": "Solarengo", "0-night": "Limpo", - "1-day": "Maioritariamente ensolarado", + "1-day": "Maioritariamente Solarengo", "1-night": "Maioritariamente Limpo", "2-day": "Parcialmente Nublado", "2-night": "Parcialmente Nublado", @@ -487,16 +497,16 @@ "75-night": "Neve forte", "77-day": "Grãos de Neve", "77-night": "Grãos de Neve", - "80-day": "Neve fraca", - "80-night": "Neve fraca", + "80-day": "Chuviscos Leves", + "80-night": "Chuviscos Leves", "81-day": "Chuviscos", "81-night": "Chuviscos", "82-day": "Chuviscos fortes", "82-night": "Chuviscos fortes", - "85-day": "Precipitação de Neve", - "85-night": "Precipitação de Neve", - "86-day": "Precipitação de Neve", - "86-night": "Precipitação de Neve", + "85-day": "Chuva de Neve", + "85-night": "Chuva de Neve", + "86-day": "Chuva de Neve", + "86-night": "Chuva de Neve", "95-day": "Trovoada", "95-night": "Trovoada", "96-day": "Trovoada com granizo", @@ -506,10 +516,10 @@ }, "homebridge": { "available_update": "Sistema", - "updates": "Atualizações", + "updates": "Actualizações", "update_available": "Atualização disponível", "up_to_date": "Atualizado", - "child_bridges": "Pontes Filhas", + "child_bridges": "Child Bridges", "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pendente", @@ -518,12 +528,12 @@ "healthchecks": { "new": "Novo", "up": "Up", - "grace": "Em Período Gratuito", + "grace": "Em Período de Graça", "down": "Down", - "paused": "Pausado", + "paused": "Pausa", "status": "Estado", "last_ping": "Ultimo Ping", - "never": "Nenhum ping ainda" + "never": "Nenhum Ping ainda" }, "watchtower": { "containers_scanned": "Verificado", @@ -531,7 +541,7 @@ "containers_failed": "Falhou" }, "autobrr": { - "approvedPushes": "Aprovada", + "approvedPushes": "Aprovado", "rejectedPushes": "Rejeitado", "filters": "Filtros", "indexers": "Indexadores" @@ -549,7 +559,7 @@ }, "pyload": { "speed": "Velocidade", - "active": "Ativo", + "active": "Activo", "queue": "Fila", "total": "Total" }, @@ -588,8 +598,8 @@ "low_battery": "Bateria Fraca" }, "nextdns": { - "wait": "Por favor aguarde", - "no_devices": "Nenhum dado do dispositivo recebido" + "wait": "Por Favor, Aguarde", + "no_devices": "Nenhum Dado do Dispositivo Recebido" }, "mikrotik": { "cpuLoad": "Carga do CPU", @@ -599,7 +609,7 @@ }, "xteve": { "streams_all": "Todos os Streams", - "streams_active": "Streams Ativas", + "streams_active": "Streams Activas", "streams_xepg": "Canais XEPG" }, "opendtu": { @@ -637,7 +647,7 @@ "up": "Up", "down": "Down", "temp": "Temp", - "disk": "Utilização", + "disk": "Utilização do Disco", "wanIP": "WAN IP" }, "proxmoxbackupserver": { @@ -678,17 +688,17 @@ "mylar": { "series": "Séries", "issues": "Problemas", - "wanted": "Desejada" + "wanted": "Desejados" }, "photoprism": { "albums": "Álbuns", "photos": "Fotos", "videos": "Vídeos", - "people": "Pessoa" + "people": "Pessoas" }, "fileflows": { "queue": "Fila", - "processing": "Processando", + "processing": "A Processar", "processed": "Processado", "time": "Hora" }, @@ -696,13 +706,13 @@ "dashboards": "Painéis", "datasources": "Origem de Dados", "totalalerts": "Total Alertas", - "alertstriggered": "Alertas Disparados" + "alertstriggered": "Alertas Desencadeados" }, "nextcloud": { "cpuload": "Carga de CPU", "memoryusage": "Memória Utilizada", "freespace": "Espaço Livre", - "activeusers": "Utilizadores Ativos", + "activeusers": "Utilizadores Activos", "numfiles": "Ficheiros", "numshares": "Itens partilhados" }, @@ -724,7 +734,7 @@ }, "prometheus": { "targets_up": "Alvo ativo", - "targets_down": "Alvo inativo", + "targets_down": "Alvo Inactivo", "targets_total": "Total de Alvos" }, "gatus": { @@ -735,7 +745,7 @@ "ghostfolio": { "gross_percent_today": "Hoje", "gross_percent_1y": "Um ano", - "gross_percent_max": "Todo o tempo" + "gross_percent_max": "Desde Sempre" }, "audiobookshelf": { "podcasts": "Podcasts", @@ -750,7 +760,7 @@ }, "whatsupdocker": { "monitoring": "A monitorizar", - "updates": "Atualizações" + "updates": "Actualizações" }, "calibreweb": { "books": "Livros", @@ -772,14 +782,14 @@ "result": "Resultado", "status": "Estado", "buildId": "ID da compilação", - "succeeded": "Com êxito", + "succeeded": "Bem sucedido", "notStarted": "Não Iniciado", "failed": "Falhou", "canceled": "Cancelado", "inProgress": "Em progresso", "totalPrs": "Total de PRs", - "myPrs": "Meus PRs", - "approved": "Aprovada" + "myPrs": "Os Meus PRs", + "approved": "Aprovado" }, "gamedig": { "status": "Estado", @@ -787,7 +797,7 @@ "offline": "Desligado", "name": "Nome", "map": "Mapa", - "currentPlayers": "Jogadores atuais", + "currentPlayers": "Jogadores actuais", "players": "Reprodutores", "maxPlayers": "Máximo de Jogadores", "bots": "Bots", @@ -796,7 +806,7 @@ "urbackup": { "ok": "Ok", "errored": "Erros", - "noRecent": "Desatualizado", + "noRecent": "Desactualizado", "totalUsed": "Espaço utilizado" }, "mealie": { @@ -806,7 +816,7 @@ "tags": "Etiquetas" }, "openmediavault": { - "downloading": "A transferir", + "downloading": "A descarregar", "total": "Total", "running": "A correr", "stopped": "Parado", @@ -824,14 +834,14 @@ "uptimerobot": { "status": "Estado", "uptime": "Ligado", - "lastDown": "Última inatividade", - "downDuration": "Duração de inatividade", + "lastDown": "Última Inactividade", + "downDuration": "Duração de Inactividade", "sitesUp": "Sites no Ar", "sitesDown": "Sites Fora do Ar", - "paused": "Pausado", + "paused": "Pausa", "notyetchecked": "Ainda não verificado", "up": "Up", - "seemsdown": "Parece Baixo", + "seemsdown": "Parece em Baixo", "down": "Down", "unknown": "Desconhecido" }, @@ -844,16 +854,16 @@ }, "romm": { "platforms": "Plataformas", - "totalRoms": "Games", + "totalRoms": "Jogos", "saves": "Saves", - "states": "States", + "states": "Estados", "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalfilesize": "Tamanho Total" }, "mailcow": { "domains": "Domínios", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Caixas de Correio", + "mails": "E-mails", "storage": "Armazenamento" }, "netdata": { @@ -881,7 +891,7 @@ "images": "Imagens", "imageSize": "Tamanho das imagens", "galleries": "Galerias", - "performers": "Artistas", + "performers": "Artistas de palco", "studios": "Estúdios", "movies": "Filmes", "tags": "Etiquetas", @@ -906,14 +916,14 @@ }, "wgeasy": { "connected": "Conectado", - "enabled": "Ativo", + "enabled": "Activo", "disabled": "Desabilitado", "total": "Total" }, "swagdashboard": { "proxied": "Com proxy", "auth": "Com Autorização", - "outdated": "Desatualizado", + "outdated": "Desactualizado", "banned": "Banido" }, "myspeed": { @@ -922,36 +932,80 @@ "upload": "Carregar" }, "stocks": { - "stocks": "Ações", - "loading": "Carregando", + "stocks": "Acções", + "loading": "A carregar", "open": "Aberto - Mercado dos EUA", "closed": "Fechado - Mercado dos EUA", - "invalidConfiguration": "Configuração inválida" + "invalidConfiguration": "Configuração Inválida" }, "frigate": { - "cameras": "Câmaras", + "cameras": "Câmeras", "uptime": "Ligado", "version": "Versão" }, "linkwarden": { "links": "Links", - "collections": "Collections", + "collections": "Colecções", "tags": "Etiquetas" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "Não Classificados", "information": "Informação", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Avisos", + "average": "Média", + "high": "Elevado", + "disaster": "Desastre" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Veículo", + "vehicles": "Veículos", + "serviceRecords": "Registros de Serviço", + "reminders": "Lembretes", + "nextReminder": "Próximo Lembrete", + "none": "Nenhum" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nome", + "address": "Endereço", + "last_seen": "Última Vez Visto", + "status": "Estado", + "online": "Online", + "offline": "Desligado" + }, + "beszel": { + "name": "Nome", + "systems": "Systems", + "up": "Up", + "status": "Estado", + "updated": "Atualizado", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Saudável", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Em falta", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "A carregar" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemas", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/pt_BR/common.json b/public/locales/pt_BR/common.json index cf977b45..c2a2330f 100644 --- a/public/locales/pt_BR/common.json +++ b/public/locales/pt_BR/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "M", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Parado", "total": "Total" }, + "suwayomi": { + "download": "Baixado", + "nondownload": "Non-Downloaded", + "read": "Lido", + "unread": "Não lida", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Endereço", "expires": "Expira em", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Nome", + "address": "Endereço", + "last_seen": "Visto por último", + "status": "Status", + "online": "Disponível", + "offline": "Offline" + }, + "beszel": { + "name": "Nome", + "systems": "Systems", + "up": "Ativo", + "status": "Status", + "updated": "Atualizado", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Saudável", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Faltando", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Carregando" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problemas", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ro/common.json b/public/locales/ro/common.json index 5be3a551..80b6e542 100644 --- a/public/locales/ro/common.json +++ b/public/locales/ro/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Oprit", "total": "Total" }, + "suwayomi": { + "download": "Descărcat", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Necitit", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Stare", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Sus", + "status": "Stare", + "updated": "Updated", + "cpu": "Procesor", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sănătos", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index d84fbb73..35894d0c 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "мес", "days": "дней", "hours": "час", @@ -227,8 +227,8 @@ "seed": "Сид" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "Хит байты кэша", + "cachemissbytes": "Мисс байты кэша" }, "downloadstation": { "download": "Скачивание", @@ -284,7 +284,7 @@ "netalertx": { "total": "Всего", "connected": "Подключено", - "new_devices": "Новое устройство", + "new_devices": "Новые устройства", "down_alerts": "Оповещение о недоступности" }, "pihole": { @@ -309,6 +309,16 @@ "stopped": "Остановлено", "total": "Всего" }, + "suwayomi": { + "download": "Загружено", + "nondownload": "Незагруженные", + "read": "Прочитано", + "unread": "Не прочитано", + "downloadedread": "Загруженные и прочитанные", + "downloadedunread": "Загруженные и непрочитанные", + "nondownloadedread": "Незагруженные и прочитанные", + "nondownloadedunread": "Незагруженные и непрочитанные" + }, "tailscale": { "address": "Адрес", "expires": "Истекает", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Запросы", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Успешные", + "totalServerFailure": "Ошибки", + "totalNxDomain": "NX домены", + "totalRefused": "Отказано", + "totalAuthoritative": "Авторитетные", + "totalRecursive": "Рекурсивные", + "totalCached": "Кэш", "totalBlocked": "Заблокировано", - "totalDropped": "Dropped", + "totalDropped": "Отброшенные", "totalClients": "Клиенты" }, "tdarr": { @@ -844,16 +854,16 @@ }, "romm": { "platforms": "Платформы", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalRoms": "Игры", + "saves": "Сейвы", + "states": "Состояния", + "screenshots": "Скриншоты", + "totalfilesize": "Общий объем" }, "mailcow": { "domains": "Домены", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Почтовые ящики", + "mails": "Письма", "storage": "Хранилище" }, "netdata": { @@ -902,7 +912,7 @@ }, "crowdsec": { "alerts": "Предупреждения", - "bans": "Запреты" + "bans": "Блокировки" }, "wgeasy": { "connected": "Подключено", @@ -911,10 +921,10 @@ "total": "Всего" }, "swagdashboard": { - "proxied": "Proxied", - "auth": "With Auth", - "outdated": "Outdated", - "banned": "Banned" + "proxied": "Прокси", + "auth": "С Авторизацией", + "outdated": "Устаревшие", + "banned": "Заблокированные" }, "myspeed": { "ping": "Пинг", @@ -922,36 +932,80 @@ "upload": "Загрузка" }, "stocks": { - "stocks": "Stocks", - "loading": "Loading", - "open": "Open - US Market", - "closed": "Closed - US Market", - "invalidConfiguration": "Invalid Configuration" + "stocks": "Акции", + "loading": "Загрузка", + "open": "Открыто - Рынок США", + "closed": "Закрыто - рынок США", + "invalidConfiguration": "Неверная конфигурация" }, "frigate": { - "cameras": "Cameras", + "cameras": "Камеры", "uptime": "Время работы", "version": "Версия" }, "linkwarden": { - "links": "Links", - "collections": "Collections", + "links": "Ссылки", + "collections": "Коллекции", "tags": "Теги" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "Не классифицировано", "information": "Информация", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Предупреждение", + "average": "Средняя", + "high": "Высокая", + "disaster": "Чрезвычайная" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Автомобиль", + "vehicles": "Автомобили", + "serviceRecords": "Сервисные работы", + "reminders": "Напоминания", + "nextReminder": "Следующее напоминание", + "none": "Нет" + }, + "vikunja": { + "projects": "Активные Проекты", + "tasks7d": "Задачи на этой неделе", + "tasksOverdue": "Просроченные задачи", + "tasksInProgress": "Задачи в процессе" + }, + "headscale": { + "name": "Имя", + "address": "Адрес", + "last_seen": "Последнее посещение", + "status": "Статус", + "online": "В сети", + "offline": "Не в сети" + }, + "beszel": { + "name": "Имя", + "systems": "Системы", + "up": "Онлайн", + "status": "Статус", + "updated": "Обновленно", + "cpu": "ЦП", + "memory": "ОЗУ", + "disk": "Диск", + "network": "Сеть" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Здоровый", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Отсутствует", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Загрузка" + }, + "gitlab": { + "groups": "Groups", + "issues": "Вопросы", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json index 4975958e..e93dd657 100644 --- a/public/locales/sk/common.json +++ b/public/locales/sk/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mes", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Zastavené", "total": "Celkovo" }, + "suwayomi": { + "download": "Stiahnuté", + "nondownload": "Non-Downloaded", + "read": "Prečítané", + "unread": "Neprečítané", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adresa", "expires": "Vyprší", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Meno", + "address": "Adresa", + "last_seen": "Naposledy videné", + "status": "Stav", + "online": "Online", + "offline": "Nedostupný" + }, + "beszel": { + "name": "Meno", + "systems": "Systems", + "up": "Nahrávanie", + "status": "Stav", + "updated": "Aktualizované", + "cpu": "CPU", + "memory": "RAM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Zdravý", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Chýbajúce", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Problémy", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json index 312f9609..17298acb 100644 --- a/public/locales/sl/common.json +++ b/public/locales/sl/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mes", "days": "d", "hours": "u", @@ -309,6 +309,16 @@ "stopped": "Ustavljen", "total": "Skupaj" }, + "suwayomi": { + "download": "Preneseno", + "nondownload": "Nepreneseno", + "read": "Prebrano", + "unread": "Neprebrano", + "downloadedread": "Preneseno in prebrano", + "downloadedunread": "Preneseno in neprebrano", + "nondownloadedread": "Nepreneseno in prebrano", + "nondownloadedunread": "Nepreneseno in neprebrano" + }, "tailscale": { "address": "Naslov", "expires": "Poteče", @@ -953,5 +963,49 @@ "reminders": "Opomniki", "nextReminder": "Naslednji opomnik", "none": "Brez" + }, + "vikunja": { + "projects": "Aktivni projekti", + "tasks7d": "Potekla opravila tega tedna", + "tasksOverdue": "Potekla opravila", + "tasksInProgress": "Tekoča opravila" + }, + "headscale": { + "name": "Naziv", + "address": "Naslov", + "last_seen": "Viden", + "status": "Stanje", + "online": "Na spletu", + "offline": "Ni povezan" + }, + "beszel": { + "name": "Naziv", + "systems": "Sistemi", + "up": "Povezan", + "status": "Stanje", + "updated": "Posodobljen", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "Mreža" + }, + "argocd": { + "apps": "Aplikacije", + "synced": "Sinhro", + "outOfSync": "Ni sinhro", + "healthy": "Zdrav", + "degraded": "Degragirano", + "progressing": "V teku", + "missing": "Manjka", + "suspended": "Prekinjeno" + }, + "spoolman": { + "loading": "Nalaganje" + }, + "gitlab": { + "groups": "Groups", + "issues": "Težave", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/sr/common.json b/public/locales/sr/common.json index 4cda2296..135d5040 100644 --- a/public/locales/sr/common.json +++ b/public/locales/sr/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Total" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Status", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index 5cecb7fe..ea10eadb 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mån", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stoppade", "total": "Total" }, + "suwayomi": { + "download": "Nedladdat", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Status", + "online": "Online", + "offline": "Offline" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Status", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/te/common.json b/public/locales/te/common.json index d922e8f8..f480451b 100644 --- a/public/locales/te/common.json +++ b/public/locales/te/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "ఆగిపోయినవి", "total": "మొత్తం" }, + "suwayomi": { + "download": "డౌన్‌లోడ్ చేయబడింది", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "హోదా", + "online": "Online", + "offline": "ఆఫ్‌లైన్" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "హోదా", + "updated": "నవీకరించబడింది", + "cpu": "సీపియూ", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "మిస్సింగ్", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/th/common.json b/public/locales/th/common.json index 6a73d2de..67c5804b 100644 --- a/public/locales/th/common.json +++ b/public/locales/th/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "ทั้งหมด" }, + "suwayomi": { + "download": "Downloaded", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "สถานะ", + "online": "Online", + "offline": "ออฟไลน์" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "สถานะ", + "updated": "Updated", + "cpu": "ซีพียู", + "memory": "เมม", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index 6c447f81..e0b25666 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "ay", "days": "g", "hours": "sa", @@ -309,6 +309,16 @@ "stopped": "Durduruldu", "total": "Toplam" }, + "suwayomi": { + "download": "İndirilen", + "nondownload": "Non-Downloaded", + "read": "Okunan", + "unread": "Okunmamış", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Adres", "expires": "Geciken", @@ -853,7 +863,7 @@ "mailcow": { "domains": "Etki Alanları", "mailboxes": "Mailboxes", - "mails": "Mails", + "mails": "Postalar", "storage": "Depo" }, "netdata": { @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Bitişi Bu Hafta Olan Görevler", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "İsim", + "address": "Adres", + "last_seen": "Son Görülme", + "status": "Durum", + "online": "Çevrimiçi", + "offline": "Çevrimdışı" + }, + "beszel": { + "name": "İsim", + "systems": "Systems", + "up": "Yükleme", + "status": "Durum", + "updated": "Güncellendi", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Sağlıklı", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Eksik", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Yükleniyor" + }, + "gitlab": { + "groups": "Groups", + "issues": "Sorunlar", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json index 6f3d45d5..6297e570 100644 --- a/public/locales/uk/common.json +++ b/public/locales/uk/common.json @@ -13,10 +13,10 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "міс", - "days": "д", - "hours": "г", + "days": "днів", + "hours": "год", "minutes": "хв", "seconds": "с" }, @@ -47,7 +47,7 @@ "load": "Завантаження", "temp": "Температура", "max": "Макс.", - "uptime": "Відправка" + "uptime": "Онлайн" }, "unifi": { "users": "Користувачі", @@ -61,7 +61,7 @@ "wlan_devices": "WLAN пристрої", "lan_users": "LAN користувачі", "wlan_users": "WLAN користувачі", - "up": "Відправка", + "up": "Онлайн", "down": "Завантаження", "wait": "Будь ласка, зачекайте", "empty_data": "Статус підсистеми невідомий" @@ -75,7 +75,7 @@ "offline": "Офлайн", "error": "Помилка", "unknown": "Невідомий", - "healthy": "Здоров'я", + "healthy": "Здоровий", "starting": "Запуск", "unhealthy": "Нездоровий", "not_found": "Не знайдено", @@ -99,7 +99,7 @@ }, "emby": { "playing": "Відтворення", - "transcoding": "Перекодування", + "transcoding": "Транскодування", "bitrate": "Бітрейт", "no_active": "Немає активних потоків", "movies": "Фільми", @@ -123,8 +123,8 @@ "watt_hour": "Вт/год" }, "flood": { - "download": "Завантаження", - "upload": "Відправлення", + "download": "Завантажено", + "upload": "Відправлено", "leech": "Ліч", "seed": "Сід" }, @@ -142,13 +142,13 @@ "connectionStatusDisconnected": "Відключено", "connectionStatusConnected": "З'єднано", "uptime": "Час роботи", - "maxDown": "Макс. зав", + "maxDown": "Макс. завантаження", "maxUp": "Макс. віддача", "down": "Офлайн", "up": "Онлайн", "received": "Отримано", - "sent": "Надісл.", - "externalIPAddress": "Зовн. IP" + "sent": "Надіслано", + "externalIPAddress": "Зовнішній IP" }, "caddy": { "upstreams": "Потоки", @@ -163,11 +163,11 @@ "shows": "Вистави", "recordings": "Записи", "scheduled": "Заплановано", - "passes": "Перепустки" + "passes": "Пропуски" }, "tautulli": { "playing": "Відтворення", - "transcoding": "Перекодування", + "transcoding": "Транскодування", "bitrate": "Бітрейт", "no_active": "Немає активних потоків", "plex_connection_error": "Перевірте з'єднання Plex" @@ -197,18 +197,18 @@ }, "rutorrent": { "active": "Активний", - "upload": "Відправлення", - "download": "Завантаження" + "upload": "Відправлено", + "download": "Завантажено" }, "transmission": { - "download": "Завантаження", - "upload": "Відправлення", + "download": "Завантажено", + "upload": "Відправлено", "leech": "Ліч", "seed": "Сід" }, "qbittorrent": { - "download": "Завантаження", - "upload": "Відправлення", + "download": "Завантажено", + "upload": "Відправлено", "leech": "Ліч", "seed": "Сід" }, @@ -221,18 +221,18 @@ "invalid": "Недійсний" }, "deluge": { - "download": "Завантаження", - "upload": "Відправлення", + "download": "Завантажено", + "upload": "Відправлено", "leech": "Ліч", "seed": "Сід" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "Кеш-хіт байт", + "cachemissbytes": "Кеш-міс байт" }, "downloadstation": { - "download": "Завантаження", - "upload": "Відправлення", + "download": "Завантажено", + "upload": "Відправлено", "leech": "Ліч", "seed": "Сід" }, @@ -300,8 +300,8 @@ "latency": "Затримка" }, "speedtest": { - "upload": "Відправлення", - "download": "Завантаження", + "upload": "Відправлено", + "download": "Завантажено", "ping": "Пінг" }, "portainer": { @@ -309,6 +309,16 @@ "stopped": "Зупинено", "total": "Усього" }, + "suwayomi": { + "download": "Завантажено", + "nondownload": "Non-Downloaded", + "read": "Прочитано", + "unread": "Не прочитано", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Адреса", "expires": "Дійсний до", @@ -325,15 +335,15 @@ }, "technitium": { "totalQueries": "Запити", - "totalNoError": "Success", - "totalServerFailure": "Failures", - "totalNxDomain": "NX Domains", - "totalRefused": "Refused", - "totalAuthoritative": "Authoritative", - "totalRecursive": "Recursive", - "totalCached": "Cached", + "totalNoError": "Успішно", + "totalServerFailure": "Помилки", + "totalNxDomain": "NX Домени", + "totalRefused": "Відмовлено", + "totalAuthoritative": "Авторитетні", + "totalRecursive": "Рекурсивні", + "totalCached": "Кешовані", "totalBlocked": "Заблоковано", - "totalDropped": "Dropped", + "totalDropped": "Видалені", "totalClients": "Клієнти" }, "tdarr": { @@ -424,12 +434,12 @@ "temp": "Температура", "_temp": "Темп.", "warn": "Увага", - "uptime": "Відправка", + "uptime": "Онлайн", "total": "Усього", "free": "Вільно", "used": "Використано", - "days": "д", - "hours": "г", + "days": "днів", + "hours": "год", "crit": "Крит", "read": "Прочитано", "write": "Написати", @@ -815,77 +825,77 @@ }, "openwrt": { "uptime": "Час роботи", - "cpuLoad": "CPU Load Avg (5m)", + "cpuLoad": "Сер. навантаження ЦП (5 хв)", "up": "Онлайн", "down": "Офлайн", - "bytesTx": "Transmitted", + "bytesTx": "Передано", "bytesRx": "Отримано" }, "uptimerobot": { "status": "Стан", "uptime": "Час роботи", - "lastDown": "Last Downtime", - "downDuration": "Downtime Duration", + "lastDown": "Останній час простою", + "downDuration": "Тривалість простою", "sitesUp": "Активні сайти", "sitesDown": "Неактивні сайти", "paused": "Призупинено", - "notyetchecked": "Not Yet Checked", + "notyetchecked": "Ще не перевірено", "up": "Онлайн", - "seemsdown": "Seems Down", + "seemsdown": "Вірогідно в простої", "down": "Офлайн", "unknown": "Невідомий" }, "calendar": { - "inCinemas": "In cinemas", - "physicalRelease": "Physical release", - "digitalRelease": "Digital release", - "noEventsToday": "No events for today!", - "noEventsFound": "No events found" + "inCinemas": "У кінотеатрах", + "physicalRelease": "Фізичний реліз", + "digitalRelease": "Цифровий реліз", + "noEventsToday": "Події на сьогодні відсутні", + "noEventsFound": "Події не знайдено" }, "romm": { - "platforms": "Platforms", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "platforms": "Платформи", + "totalRoms": "Ігри", + "saves": "Збереження", + "states": "Штати", + "screenshots": "Знімки екрану", + "totalfilesize": "Загальний обсяг" }, "mailcow": { "domains": "Домени", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "Пошта", + "mails": "Листи", "storage": "Сховище" }, "netdata": { "warnings": "Попередження", - "criticals": "Criticals" + "criticals": "Критичні" }, "plantit": { - "events": "Events", - "plants": "Plants", + "events": "Події", + "plants": "Рослини", "photos": "Фотографії", - "species": "Species" + "species": "Види" }, "gitea": { "notifications": "Сповіщення", "issues": "Питання", - "pulls": "Pull Requests" + "pulls": "Pull-запити" }, "stash": { - "scenes": "Scenes", - "scenesPlayed": "Scenes Played", - "playCount": "Total Plays", - "playDuration": "Time Watched", - "sceneSize": "Scenes Size", - "sceneDuration": "Scenes Duration", - "images": "Images", - "imageSize": "Images Size", - "galleries": "Galleries", + "scenes": "Сцени", + "scenesPlayed": "Зіграні сцени", + "playCount": "Всього п'єс", + "playDuration": "Переглянуто", + "sceneSize": "Розміри сцен", + "sceneDuration": "Тривалість сцен", + "images": "Зображення", + "imageSize": "Розміри зображень", + "galleries": "Галереї", "performers": "Виконавці", - "studios": "Studios", + "studios": "Студії", "movies": "Фільми", "tags": "Теги", - "oCount": "O Count" + "oCount": "Кількість O" }, "tandoor": { "users": "Користувачі", @@ -893,16 +903,16 @@ "keywords": "Ключові слова" }, "homebox": { - "items": "Items", - "totalWithWarranty": "With Warranty", + "items": "Речі", + "totalWithWarranty": "З гарантією", "locations": "Місцезнаходження", "labels": "Мітки", "users": "Користувачі", - "totalValue": "Total Value" + "totalValue": "Загальне значення" }, "crowdsec": { "alerts": "Оповіщення", - "bans": "Bans" + "bans": "Блокування" }, "wgeasy": { "connected": "З'єднано", @@ -911,21 +921,21 @@ "total": "Усього" }, "swagdashboard": { - "proxied": "Proxied", - "auth": "With Auth", - "outdated": "Outdated", - "banned": "Banned" + "proxied": "Пропущено через проксі", + "auth": "З аутентифікацією", + "outdated": "Застаріле", + "banned": "Заблоковано" }, "myspeed": { "ping": "Пінг", - "download": "Завантаження", - "upload": "Відправлення" + "download": "Завантажено", + "upload": "Відправлено" }, "stocks": { - "stocks": "Stocks", + "stocks": "Акції", "loading": "Завантажую", - "open": "Open - US Market", - "closed": "Closed - US Market", + "open": "Відкрито - ринок США", + "closed": "Закрито - ринок США", "invalidConfiguration": "Неприпустима конфігурація" }, "frigate": { @@ -934,24 +944,68 @@ "version": "Версія" }, "linkwarden": { - "links": "Links", - "collections": "Collections", + "links": "Посилання", + "collections": "Колекції", "tags": "Теги" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "Не визначено", "information": "Інформація", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "Попередження", + "average": "Середнє", + "high": "Високе", + "disaster": "Катастрофа" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "Транспортний засіб", + "vehicles": "Транспортні засоби", + "serviceRecords": "Записи служб", + "reminders": "Нагадування", + "nextReminder": "Наступне нагадування", + "none": "Жодного" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Назва", + "address": "Адреса", + "last_seen": "Востаннє у мережі", + "status": "Стан", + "online": "Онлайн", + "offline": "Офлайн" + }, + "beszel": { + "name": "Назва", + "systems": "Systems", + "up": "Онлайн", + "status": "Стан", + "updated": "Оновлено", + "cpu": "ЦП", + "memory": "ОЗП", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Здоровий", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Відсутній", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Завантажую" + }, + "gitlab": { + "groups": "Groups", + "issues": "Питання", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index d81903c4..8abcdad5 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "mo", "days": "d", "hours": "h", @@ -309,6 +309,16 @@ "stopped": "Stopped", "total": "Tổng" }, + "suwayomi": { + "download": "Đã tải", + "nondownload": "Non-Downloaded", + "read": "Read", + "unread": "Unread", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "Address", "expires": "Expires", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "Name", + "address": "Address", + "last_seen": "Last Seen", + "status": "Trạng thái", + "online": "Online", + "offline": "Ngoại tuyến" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "Trạng thái", + "updated": "Updated", + "cpu": "CPU", + "memory": "MEM", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "Healthy", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "Missing", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "Issues", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/yue/common.json b/public/locales/yue/common.json index 2578eb8f..f10269f6 100644 --- a/public/locales/yue/common.json +++ b/public/locales/yue/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "月", "days": "天", "hours": "小時", @@ -309,6 +309,16 @@ "stopped": "暫停", "total": "全部" }, + "suwayomi": { + "download": "下載咗", + "nondownload": "Non-Downloaded", + "read": "已讀", + "unread": "未讀", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "位址", "expires": "已失效", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "名稱", + "address": "位址", + "last_seen": "上次連線", + "status": "狀況", + "online": "在線", + "offline": "離線" + }, + "beszel": { + "name": "名稱", + "systems": "Systems", + "up": "在線", + "status": "狀況", + "updated": "已更新", + "cpu": "CPU", + "memory": "記憶體", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "健康", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "缺少", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "出版", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/zh-Hans/common.json b/public/locales/zh-Hans/common.json index 6665ed6a..f0d445c2 100644 --- a/public/locales/zh-Hans/common.json +++ b/public/locales/zh-Hans/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "月", "days": "日", "hours": "时", @@ -61,7 +61,7 @@ "wlan_devices": "无线局域网设备", "lan_users": "局域网用户", "wlan_users": "无线局域网用户", - "up": "在线", + "up": "运行时间", "down": "离线", "wait": "请稍候", "empty_data": "子系统状态未知" @@ -227,8 +227,8 @@ "seed": "做种" }, "develancacheui": { - "cachehitbytes": "Cache Hit Bytes", - "cachemissbytes": "Cache Miss Bytes" + "cachehitbytes": "缓存命中字节", + "cachemissbytes": "缓存Bytes失败" }, "downloadstation": { "download": "下载", @@ -309,6 +309,16 @@ "stopped": "停止", "total": "总计" }, + "suwayomi": { + "download": "下载", + "nondownload": "Non-Downloaded", + "read": "已读", + "unread": "未读", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "地址", "expires": "失效", @@ -327,7 +337,7 @@ "totalQueries": "查询", "totalNoError": "成功", "totalServerFailure": "失败", - "totalNxDomain": "NX Domains", + "totalNxDomain": "域", "totalRefused": "已拒绝", "totalAuthoritative": "权威", "totalRecursive": "递归", @@ -724,7 +734,7 @@ }, "prometheus": { "targets_up": "目标上线", - "targets_down": "离线目标", + "targets_down": "目标离线", "targets_total": "总目标" }, "gatus": { @@ -844,16 +854,16 @@ }, "romm": { "platforms": "平台", - "totalRoms": "Games", - "saves": "Saves", - "states": "States", - "screenshots": "Screenshots", - "totalfilesize": "Total Size" + "totalRoms": "游戏数", + "saves": "已保存", + "states": "状态", + "screenshots": "屏幕截图", + "totalfilesize": "总大小" }, "mailcow": { "domains": "域", - "mailboxes": "Mailboxes", - "mails": "Mails", + "mailboxes": "邮箱", + "mails": "邮件", "storage": "储存空间" }, "netdata": { @@ -922,36 +932,80 @@ "upload": "上传速率" }, "stocks": { - "stocks": "Stocks", - "loading": "Loading", - "open": "Open - US Market", - "closed": "Closed - US Market", - "invalidConfiguration": "Invalid Configuration" + "stocks": "库存", + "loading": "正在加载", + "open": "打開-美国商店", + "closed": "关闭-美国市场", + "invalidConfiguration": "无效配置" }, "frigate": { - "cameras": "Cameras", + "cameras": "摄像头", "uptime": "运行时间", "version": "版本" }, "linkwarden": { - "links": "Links", - "collections": "Collections", + "links": "链接", + "collections": "收藏", "tags": "Tags" }, "zabbix": { - "unclassified": "Not classified", + "unclassified": "未分类", "information": "信息", - "warning": "Warning", - "average": "Average", - "high": "High", - "disaster": "Disaster" + "warning": "警告", + "average": "平均红包", + "high": "高", + "disaster": "灾难" }, "lubelogger": { - "vehicle": "Vehicle", - "vehicles": "Vehicles", - "serviceRecords": "Service Records", - "reminders": "Reminders", - "nextReminder": "Next Reminder", - "none": "None" + "vehicle": "载具", + "vehicles": "交通工具", + "serviceRecords": "保养记录", + "reminders": "提示", + "nextReminder": "下次提醒", + "none": "空" + }, + "vikunja": { + "projects": "积极的项目", + "tasks7d": "本周到期的任务", + "tasksOverdue": "过期的任务", + "tasksInProgress": "正在处理的任务" + }, + "headscale": { + "name": "Name", + "address": "地址", + "last_seen": "最后上线", + "status": "状态", + "online": "在线的", + "offline": "离线" + }, + "beszel": { + "name": "Name", + "systems": "Systems", + "up": "Up", + "status": "状态", + "updated": "已升级", + "cpu": "CPU", + "memory": "内存", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "应用程序", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "健康", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "丢失", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "正在加载" + }, + "gitlab": { + "groups": "Groups", + "issues": "问题", + "merges": "Merge Requests", + "projects": "Projects" } } diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index 4eb68d91..1a497aab 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -13,7 +13,7 @@ "ms": "{{value, number}}", "date": "{{value, date}}", "relativeDate": "{{value, relativeDate}}", - "uptime": "{{value, uptime}}", + "duration": "{{value, duration}}", "months": "月", "days": "天", "hours": "小時", @@ -309,6 +309,16 @@ "stopped": "已停止", "total": "全部" }, + "suwayomi": { + "download": "已下載", + "nondownload": "Non-Downloaded", + "read": "已讀", + "unread": "未讀", + "downloadedread": "Downloaded & Read", + "downloadedunread": "Downloaded & Unread", + "nondownloadedread": "Non-Downloaded & Read", + "nondownloadedunread": "Non-Downloaded & Unread" + }, "tailscale": { "address": "位址", "expires": "已失效", @@ -953,5 +963,49 @@ "reminders": "Reminders", "nextReminder": "Next Reminder", "none": "None" + }, + "vikunja": { + "projects": "Active Projects", + "tasks7d": "Tasks Due This Week", + "tasksOverdue": "Overdue Tasks", + "tasksInProgress": "Tasks In Progress" + }, + "headscale": { + "name": "名稱", + "address": "位址", + "last_seen": "上次連線", + "status": "狀態", + "online": "在線", + "offline": "離線" + }, + "beszel": { + "name": "名稱", + "systems": "Systems", + "up": "在線", + "status": "狀態", + "updated": "已更新", + "cpu": "CPU", + "memory": "記憶體", + "disk": "Disk", + "network": "NET" + }, + "argocd": { + "apps": "Apps", + "synced": "Synced", + "outOfSync": "Out Of Sync", + "healthy": "健康", + "degraded": "Degraded", + "progressing": "Progressing", + "missing": "缺少", + "suspended": "Suspended" + }, + "spoolman": { + "loading": "Loading" + }, + "gitlab": { + "groups": "Groups", + "issues": "出版", + "merges": "Merge Requests", + "projects": "Projects" } } From 897309a47cb94ff9d2d45eb875e7a09ff291b5dc Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:43:13 -0800 Subject: [PATCH 047/195] Enhancement: resources network widget (#4327) --- docs/installation/k8s.md | 3 +- docs/widgets/info/resources.md | 3 +- src/components/widgets/resources/network.jsx | 47 +++++++++++++++++++ .../widgets/resources/resources.jsx | 2 + src/components/widgets/widget/resource.jsx | 7 ++- src/pages/api/widgets/resources.js | 28 ++++++++++- 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/components/widgets/resources/network.jsx diff --git a/docs/installation/k8s.md b/docs/installation/k8s.md index 6805139b..24be2c34 100644 --- a/docs/installation/k8s.md +++ b/docs/installation/k8s.md @@ -175,6 +175,7 @@ data: expanded: true cpu: true memory: true + network: default - search: provider: duckduckgo target: _blank @@ -370,7 +371,7 @@ prevent unnecessary re-renders on page loads and window / tab focusing. The procedure for enabling sticky sessions depends on your Ingress controller. Below is an example using Traefik as the Ingress controller. -``` +```yaml apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: diff --git a/docs/widgets/info/resources.md b/docs/widgets/info/resources.md index 19323dc3..7fcf9c5c 100644 --- a/docs/widgets/info/resources.md +++ b/docs/widgets/info/resources.md @@ -24,9 +24,10 @@ _Note: unfortunately, the package used for getting CPU temp ([systeminformation] tempmin: 0 # optional, minimum cpu temp tempmax: 100 # optional, maximum cpu temp uptime: true - units: imperial # only used by cpu temp + units: imperial # only used by cpu temp, options: 'imperial' or 'metric' refresh: 3000 # optional, in ms diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk + network: true # optional, uses 'default' if true or specify a network interface name ``` You can also pass a `label` option, which allows you to group resources under named sections, diff --git a/src/components/widgets/resources/network.jsx b/src/components/widgets/resources/network.jsx new file mode 100644 index 00000000..5b5cc004 --- /dev/null +++ b/src/components/widgets/resources/network.jsx @@ -0,0 +1,47 @@ +import useSWR from "swr"; +import { FaNetworkWired } from "react-icons/fa"; +import { useTranslation } from "next-i18next"; + +import Resource from "../widget/resource"; +import Error from "../widget/error"; + +export default function Network({ options, refresh = 1500 }) { + const { t } = useTranslation(); + // eslint-disable-next-line no-param-reassign + if (options.network === true) options.network = "default"; + + const { data, error } = useSWR(`/api/widgets/resources?type=network&interfaceName=${options.network}`, { + refreshInterval: refresh, + }); + + if (error || data?.error) { + return ; + } + + if (!data || !data.network) { + return ( + + ); + } + + return ( + + ); +} diff --git a/src/components/widgets/resources/resources.jsx b/src/components/widgets/resources/resources.jsx index 634e0ff5..db26caa7 100644 --- a/src/components/widgets/resources/resources.jsx +++ b/src/components/widgets/resources/resources.jsx @@ -6,6 +6,7 @@ import Cpu from "./cpu"; import Memory from "./memory"; import CpuTemp from "./cputemp"; import Uptime from "./uptime"; +import Network from "./network"; export default function Resources({ options }) { const { expanded, units, diskUnits, tempmin, tempmax } = options; @@ -23,6 +24,7 @@ export default function Resources({ options }) { )) : options.disk && } + {options.network && } {options.cputemp && ( )} diff --git a/src/components/widgets/widget/resource.jsx b/src/components/widgets/widget/resource.jsx index 8c975928..b1f73740 100644 --- a/src/components/widgets/widget/resource.jsx +++ b/src/components/widgets/widget/resource.jsx @@ -10,6 +10,7 @@ export default function Resource({ percentage, expanded = false, additionalClassNames = "", + wide = false, }) { const Icon = icon; @@ -18,7 +19,11 @@ export default function Resource({ className={`flex-none flex flex-row items-center mr-3 py-1.5 information-widget-resource ${additionalClassNames}`} > -
+
{value}
{label}
diff --git a/src/pages/api/widgets/resources.js b/src/pages/api/widgets/resources.js index 66449bff..4df544e8 100644 --- a/src/pages/api/widgets/resources.js +++ b/src/pages/api/widgets/resources.js @@ -7,7 +7,7 @@ const logger = createLogger("resources"); const si = require("systeminformation"); export default async function handler(req, res) { - const { type, target } = req.query; + const { type, target, interfaceName = "default" } = req.query; if (type === "cpu") { const load = await si.currentLoad(); @@ -57,6 +57,32 @@ export default async function handler(req, res) { }); } + if (type === "network") { + let networkData = await si.networkStats(); + let interfaceDefault; + logger.debug("networkData:", JSON.stringify(networkData)); + if (interfaceName && interfaceName !== "default") { + networkData = networkData.filter((network) => network.iface === interfaceName).at(0); + if (!networkData) { + return res.status(404).json({ + error: "Interface not found", + }); + } + } else { + interfaceDefault = await si.networkInterfaceDefault(); + networkData = networkData.filter((network) => network.iface === interfaceDefault).at(0); + if (!networkData) { + return res.status(404).json({ + error: "Default interface not found", + }); + } + } + return res.status(200).json({ + network: networkData, + interface: interfaceName !== "default" ? interfaceName : interfaceDefault, + }); + } + return res.status(400).json({ error: "invalid type", }); From cbf304a4c81df5bbec1164bae858a3d684867bc8 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:43:17 -0800 Subject: [PATCH 048/195] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f898387..58820942 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Homepage has built-in support for Docker, and can automatically discover and add ## Service Widgets -Homepage also has support for over 100 3rd party services, including all popular starr apps, and most popular self-hosted apps. Some examples include: Radarr, Sonarr, Lidarr, Bazarr, Ombi, Tautulli, Plex, Jellyfin, Emby, Transmission, qBittorrent, Deluge, Jackett, NZBGet, SABnzbd, etc. As well as service integrations, Homepage also has a number of information providers, sourcing information from a variety of external 3rd party APIs. See the [Service](https://gethomepage.dev/widgets/) page for more information. +Homepage also has support for hundreds of 3rd-party services, including all popular \*arr apps, and most popular self-hosted apps. Some examples include: Radarr, Sonarr, Lidarr, Bazarr, Ombi, Tautulli, Plex, Jellyfin, Emby, Transmission, qBittorrent, Deluge, Jackett, NZBGet, SABnzbd, etc. As well as service integrations, Homepage also has a number of information providers, sourcing information from a variety of external 3rd-party APIs. See the [Service](https://gethomepage.dev/widgets/) page for more information. ## Information Widgets From 385511f773daea10bd27c5673231582131f00b86 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 24 Nov 2024 23:11:07 -0800 Subject: [PATCH 049/195] Fix: resources network better startup behavior --- src/components/widgets/resources/network.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/widgets/resources/network.jsx b/src/components/widgets/resources/network.jsx index 5b5cc004..a2a3acac 100644 --- a/src/components/widgets/resources/network.jsx +++ b/src/components/widgets/resources/network.jsx @@ -18,7 +18,7 @@ export default function Network({ options, refresh = 1500 }) { return ; } - if (!data || !data.network) { + if (!data || !data.network || !data.network.rx_sec || !data.network.tx_sec) { return ( Date: Wed, 27 Nov 2024 02:33:40 -0800 Subject: [PATCH 050/195] Enhancement: multiple widgets per service (#4338) --- docs/configs/docker.md | 12 ++ docs/configs/service-widgets.md | 20 ++- docs/widgets/index.md | 11 +- src/components/services/item.jsx | 4 +- src/components/services/widget.jsx | 10 +- src/pages/api/services/proxy.js | 6 +- src/utils/config/service-helpers.js | 159 ++++++++++++----------- src/utils/proxy/api-helpers.js | 1 + src/utils/proxy/handlers/credentialed.js | 4 +- src/utils/proxy/handlers/generic.js | 4 +- src/utils/proxy/handlers/jsonrpc.js | 4 +- src/utils/proxy/handlers/synology.js | 4 +- src/widgets/audiobookshelf/proxy.js | 4 +- src/widgets/beszel/proxy.js | 4 +- src/widgets/calendar/proxy.js | 4 +- src/widgets/crowdsec/proxy.js | 4 +- src/widgets/deluge/proxy.js | 4 +- src/widgets/flood/proxy.js | 4 +- src/widgets/freshrss/proxy.js | 4 +- src/widgets/fritzbox/proxy.js | 4 +- src/widgets/gamedig/proxy.js | 4 +- src/widgets/homeassistant/proxy.js | 4 +- src/widgets/homebox/proxy.js | 4 +- src/widgets/homebridge/proxy.js | 4 +- src/widgets/jackett/proxy.js | 4 +- src/widgets/jdownloader/proxy.js | 4 +- src/widgets/kavita/proxy.js | 4 +- src/widgets/minecraft/proxy.js | 4 +- src/widgets/npm/proxy.js | 4 +- src/widgets/omada/proxy.js | 4 +- src/widgets/openmediavault/proxy.js | 4 +- src/widgets/openwrt/proxy.js | 4 +- src/widgets/photoprism/proxy.js | 4 +- src/widgets/pihole/proxy.js | 4 +- src/widgets/plex/proxy.js | 4 +- src/widgets/pyload/proxy.js | 4 +- src/widgets/qbittorrent/proxy.js | 4 +- src/widgets/qnap/proxy.js | 4 +- src/widgets/rutorrent/proxy.js | 4 +- src/widgets/suwayomi/proxy.js | 4 +- src/widgets/tdarr/proxy.js | 4 +- src/widgets/transmission/proxy.js | 4 +- src/widgets/unifi/proxy.js | 8 +- src/widgets/urbackup/proxy.js | 4 +- src/widgets/watchtower/proxy.js | 4 +- src/widgets/xteve/proxy.js | 4 +- 46 files changed, 210 insertions(+), 169 deletions(-) diff --git a/docs/configs/docker.md b/docs/configs/docker.md index 51f6b523..7cea1fdc 100644 --- a/docs/configs/docker.md +++ b/docs/configs/docker.md @@ -153,6 +153,18 @@ labels: - homepage.widget.fields=["field1","field2"] # optional ``` +Multiple widgets can be specified by incrementing the index, e.g. + +```yaml +labels: ... + - homepage.widget[0].type=emby + - homepage.widget[0].url=http://emby.home + - homepage.widget[0].key=yourembyapikeyhere + - homepage.widget[1].type=uptimekuma + - homepage.widget[1].url=http://uptimekuma.home + - homepage.widget[1].slug=youreventslughere +``` + You can add specify fields for e.g. the [CustomAPI](../widgets/services/customapi.md) widget by using array-style dot notation: ```yaml diff --git a/docs/configs/service-widgets.md b/docs/configs/service-widgets.md index 9c54964e..df696f61 100644 --- a/docs/configs/service-widgets.md +++ b/docs/configs/service-widgets.md @@ -5,7 +5,7 @@ description: Service Widget Configuration Unless otherwise noted, URLs should not end with a `/` or other API path. Each widget will handle the path on its own. -Each service can have one widget attached to it (often matching the service type, but that's not forced). +Each service can have widgets attached to it (often matching the service type, but that's not forced). In addition to the href of the service, you can also specify the target location in which to open that link. See [Link Target](settings.md#link-target) for more details. @@ -22,6 +22,24 @@ Using Emby as an example, this is how you would attach the Emby service widget. key: apikeyapikeyapikeyapikeyapikey ``` +## Multiple Widgets + +Each service can have multiple widgets attached to it, for example: + +```yaml +- Emby: + icon: emby.png + href: http://emby.host.or.ip/ + description: Movies & TV Shows + widgets: + - type: emby + url: http://emby.host.or.ip + key: apikeyapikeyapikeyapikeyapikey + - type: uptimekuma + url: http://uptimekuma.host.or.ip:port + slug: statuspageslug +``` + ## Field Visibility Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields. diff --git a/docs/widgets/index.md b/docs/widgets/index.md index 8b81ee40..4bd45af7 100644 --- a/docs/widgets/index.md +++ b/docs/widgets/index.md @@ -19,10 +19,13 @@ Service widgets are used to display the status of a service, often a web service description: Watch movies and TV shows. server: localhost container: plex - widget: - type: tautulli - url: http://172.16.1.1:8181 - key: aabbccddeeffgghhiijjkkllmmnnoo + widgets: + - type: tautulli + url: http://172.16.1.1:8181 + key: aabbccddeeffgghhiijjkkllmmnnoo + - type: uptimekuma + url: http://172.16.1.2:8080 + slug: aaaaaaabbbbb ``` ## Info Widgets diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index a38dfaa3..54560d6f 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -154,7 +154,9 @@ export default function Item({ service, group, useEqualHeights }) {
)} - {service.widget && } + {service.widgets.map((widget) => ( + + ))}
); diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx index 292b2b1c..61a21a66 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -3,22 +3,24 @@ import { useTranslation } from "next-i18next"; import ErrorBoundary from "components/errorboundry"; import components from "widgets/components"; -export default function Widget({ service }) { +export default function Widget({ widget, service }) { const { t } = useTranslation("common"); - const ServiceWidget = components[service.widget.type]; + const ServiceWidget = components[widget.type]; + const fullService = Object.apply({}, service); + fullService.widget = widget; if (ServiceWidget) { return ( - + ); } return (
-
{t("widget.missing_type", { type: service.widget.type })}
+
{t("widget.missing_type", { type: widget.type })}
); } diff --git a/src/pages/api/services/proxy.js b/src/pages/api/services/proxy.js index 90280c3d..3f8adc88 100644 --- a/src/pages/api/services/proxy.js +++ b/src/pages/api/services/proxy.js @@ -9,8 +9,8 @@ const logger = createLogger("servicesProxy"); export default async function handler(req, res) { try { - const { service, group } = req.query; - const serviceWidget = await getServiceWidget(group, service); + const { service, group, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); let type = serviceWidget?.type; // exceptions @@ -41,7 +41,7 @@ export default async function handler(req, res) { const endpoint = mapping?.endpoint; const endpointProxy = mapping?.proxyHandler || serviceProxyHandler; - if (mapping.method && mapping.method !== req.method) { + if (mapping?.method && mapping.method !== req.method) { logger.debug("Unsupported method: %s", req.method); return res.status(403).json({ error: "Unsupported method" }); } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index ea82c735..e6ef6173 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -354,8 +354,12 @@ export function cleanServiceGroups(groups) { if (typeof cleanedService.weight !== "number") { cleanedService.weight = 0; } - + if (!cleanedService.widgets) cleanedService.widgets = []; if (cleanedService.widget) { + cleanedService.widgets.push(cleanedService.widget); + delete cleanedService.widget; + } + cleanedService.widgets = cleanedService.widgets.map((widgetData, index) => { // whitelisted set of keys to pass to the frontend // alphabetical, grouped by widget(s) const { @@ -495,7 +499,7 @@ export function cleanServiceGroups(groups) { // spoolman spoolIds, - } = cleanedService.widget; + } = widgetData; let fieldsList = fields; if (typeof fields === "string") { @@ -507,160 +511,160 @@ export function cleanServiceGroups(groups) { } } - cleanedService.widget = { + const widget = { type, fields: fieldsList || null, hide_errors: hideErrors || false, service_name: service.name, service_group: serviceGroup.name, + index, }; if (type === "azuredevops") { - if (userEmail) cleanedService.widget.userEmail = userEmail; - if (repositoryId) cleanedService.widget.repositoryId = repositoryId; + if (userEmail) widget.userEmail = userEmail; + if (repositoryId) widget.repositoryId = repositoryId; } if (type === "beszel") { - if (systemId) cleanedService.widget.systemId = systemId; + if (systemId) widget.systemId = systemId; } if (type === "coinmarketcap") { - if (currency) cleanedService.widget.currency = currency; - if (symbols) cleanedService.widget.symbols = symbols; - if (slugs) cleanedService.widget.slugs = slugs; - if (defaultinterval) cleanedService.widget.defaultinterval = defaultinterval; + if (currency) widget.currency = currency; + if (symbols) widget.symbols = symbols; + if (slugs) widget.slugs = slugs; + if (defaultinterval) widget.defaultinterval = defaultinterval; } if (type === "docker") { - if (server) cleanedService.widget.server = server; - if (container) cleanedService.widget.container = container; + if (server) widget.server = server; + if (container) widget.container = container; } if (type === "unifi") { - if (site) cleanedService.widget.site = site; + if (site) widget.site = site; } if (type === "proxmox") { - if (node) cleanedService.widget.node = node; + if (node) widget.node = node; } if (type === "kubernetes") { - if (namespace) cleanedService.widget.namespace = namespace; - if (app) cleanedService.widget.app = app; - if (podSelector) cleanedService.widget.podSelector = podSelector; + if (namespace) widget.namespace = namespace; + if (app) widget.app = app; + if (podSelector) widget.podSelector = podSelector; } if (type === "iframe") { - if (src) cleanedService.widget.src = src; - if (classes) cleanedService.widget.classes = classes; - if (referrerPolicy) cleanedService.widget.referrerPolicy = referrerPolicy; - if (allowPolicy) cleanedService.widget.allowPolicy = allowPolicy; - if (allowFullscreen) cleanedService.widget.allowFullscreen = allowFullscreen; - if (loadingStrategy) cleanedService.widget.loadingStrategy = loadingStrategy; - if (allowScrolling) cleanedService.widget.allowScrolling = allowScrolling; - if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; + if (src) widget.src = src; + if (classes) widget.classes = classes; + if (referrerPolicy) widget.referrerPolicy = referrerPolicy; + if (allowPolicy) widget.allowPolicy = allowPolicy; + if (allowFullscreen) widget.allowFullscreen = allowFullscreen; + if (loadingStrategy) widget.loadingStrategy = loadingStrategy; + if (allowScrolling) widget.allowScrolling = allowScrolling; + if (refreshInterval) widget.refreshInterval = refreshInterval; } if (["opnsense", "pfsense"].includes(type)) { - if (wan) cleanedService.widget.wan = wan; + if (wan) widget.wan = wan; } if (["emby", "jellyfin"].includes(type)) { - if (enableBlocks !== undefined) cleanedService.widget.enableBlocks = JSON.parse(enableBlocks); - if (enableNowPlaying !== undefined) cleanedService.widget.enableNowPlaying = JSON.parse(enableNowPlaying); + if (enableBlocks !== undefined) widget.enableBlocks = JSON.parse(enableBlocks); + if (enableNowPlaying !== undefined) widget.enableNowPlaying = JSON.parse(enableNowPlaying); } if (["emby", "jellyfin", "tautulli"].includes(type)) { if (expandOneStreamToTwoRows !== undefined) - cleanedService.widget.expandOneStreamToTwoRows = !!JSON.parse(expandOneStreamToTwoRows); - if (showEpisodeNumber !== undefined) - cleanedService.widget.showEpisodeNumber = !!JSON.parse(showEpisodeNumber); - if (enableUser !== undefined) cleanedService.widget.enableUser = !!JSON.parse(enableUser); + widget.expandOneStreamToTwoRows = !!JSON.parse(expandOneStreamToTwoRows); + if (showEpisodeNumber !== undefined) widget.showEpisodeNumber = !!JSON.parse(showEpisodeNumber); + if (enableUser !== undefined) widget.enableUser = !!JSON.parse(enableUser); } if (["sonarr", "radarr"].includes(type)) { - if (enableQueue !== undefined) cleanedService.widget.enableQueue = JSON.parse(enableQueue); + if (enableQueue !== undefined) widget.enableQueue = JSON.parse(enableQueue); } if (type === "truenas") { - if (enablePools !== undefined) cleanedService.widget.enablePools = JSON.parse(enablePools); - if (nasType !== undefined) cleanedService.widget.nasType = nasType; + if (enablePools !== undefined) widget.enablePools = JSON.parse(enablePools); + if (nasType !== undefined) widget.nasType = nasType; } if (["diskstation", "qnap"].includes(type)) { - if (volume) cleanedService.widget.volume = volume; + if (volume) widget.volume = volume; } if (type === "kopia") { - if (snapshotHost) cleanedService.widget.snapshotHost = snapshotHost; - if (snapshotPath) cleanedService.widget.snapshotPath = snapshotPath; + if (snapshotHost) widget.snapshotHost = snapshotHost; + if (snapshotPath) widget.snapshotPath = snapshotPath; } if (["glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) { - if (version) cleanedService.widget.version = parseInt(version, 10); + if (version) widget.version = parseInt(version, 10); } if (type === "glances") { - if (metric) cleanedService.widget.metric = metric; + if (metric) widget.metric = metric; if (chart !== undefined) { - cleanedService.widget.chart = chart; + widget.chart = chart; } else { - cleanedService.widget.chart = true; + widget.chart = true; } - if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; - if (pointsLimit) cleanedService.widget.pointsLimit = pointsLimit; - if (diskUnits) cleanedService.widget.diskUnits = diskUnits; + if (refreshInterval) widget.refreshInterval = refreshInterval; + if (pointsLimit) widget.pointsLimit = pointsLimit; + if (diskUnits) widget.diskUnits = diskUnits; } if (type === "mjpeg") { - if (stream) cleanedService.widget.stream = stream; - if (fit) cleanedService.widget.fit = fit; + if (stream) widget.stream = stream; + if (fit) widget.fit = fit; } if (type === "openmediavault") { - if (method) cleanedService.widget.method = method; + if (method) widget.method = method; } if (type === "openwrt") { - if (interfaceName) cleanedService.widget.interfaceName = interfaceName; + if (interfaceName) widget.interfaceName = interfaceName; } if (type === "customapi") { - if (mappings) cleanedService.widget.mappings = mappings; - if (display) cleanedService.widget.display = display; - if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; + if (mappings) widget.mappings = mappings; + if (display) widget.display = display; + if (refreshInterval) widget.refreshInterval = refreshInterval; } if (type === "calendar") { - if (integrations) cleanedService.widget.integrations = integrations; - if (firstDayInWeek) cleanedService.widget.firstDayInWeek = firstDayInWeek; - if (view) cleanedService.widget.view = view; - if (maxEvents) cleanedService.widget.maxEvents = maxEvents; - if (previousDays) cleanedService.widget.previousDays = previousDays; - if (showTime) cleanedService.widget.showTime = showTime; - if (timezone) cleanedService.widget.timezone = timezone; + if (integrations) widget.integrations = integrations; + if (firstDayInWeek) widget.firstDayInWeek = firstDayInWeek; + if (view) widget.view = view; + if (maxEvents) widget.maxEvents = maxEvents; + if (previousDays) widget.previousDays = previousDays; + if (showTime) widget.showTime = showTime; + if (timezone) widget.timezone = timezone; } if (type === "hdhomerun") { - if (tuner !== undefined) cleanedService.widget.tuner = tuner; + if (tuner !== undefined) widget.tuner = tuner; } if (type === "healthchecks") { - if (uuid !== undefined) cleanedService.widget.uuid = uuid; + if (uuid !== undefined) widget.uuid = uuid; } if (type === "speedtest") { if (bitratePrecision !== undefined) { - cleanedService.widget.bitratePrecision = parseInt(bitratePrecision, 10); + widget.bitratePrecision = parseInt(bitratePrecision, 10); } } if (type === "stocks") { - if (watchlist) cleanedService.widget.watchlist = watchlist; - if (showUSMarketStatus) cleanedService.widget.showUSMarketStatus = showUSMarketStatus; + if (watchlist) widget.watchlist = watchlist; + if (showUSMarketStatus) widget.showUSMarketStatus = showUSMarketStatus; } if (type === "wgeasy") { - if (threshold !== undefined) cleanedService.widget.threshold = parseInt(threshold, 10); + if (threshold !== undefined) widget.threshold = parseInt(threshold, 10); } if (type === "frigate") { - if (enableRecentEvents !== undefined) cleanedService.widget.enableRecentEvents = enableRecentEvents; + if (enableRecentEvents !== undefined) widget.enableRecentEvents = enableRecentEvents; } if (type === "technitium") { - if (range !== undefined) cleanedService.widget.range = range; + if (range !== undefined) widget.range = range; } if (type === "lubelogger") { - if (vehicleID !== undefined) cleanedService.widget.vehicleID = parseInt(vehicleID, 10); + if (vehicleID !== undefined) widget.vehicleID = parseInt(vehicleID, 10); } if (type === "vikunja") { - if (enableTaskList !== undefined) cleanedService.widget.enableTaskList = !!enableTaskList; + if (enableTaskList !== undefined) widget.enableTaskList = !!enableTaskList; } if (type === "prometheusmetric") { - if (metrics) cleanedService.widget.metrics = metrics; - if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval; + if (metrics) widget.metrics = metrics; + if (refreshInterval) widget.refreshInterval = refreshInterval; } if (type === "spoolman") { - if (spoolIds !== undefined) cleanedService.widget.spoolIds = spoolIds; + if (spoolIds !== undefined) widget.spoolIds = spoolIds; } - } - + return widget; + }); return cleanedService; }), })); @@ -693,12 +697,11 @@ export async function getServiceItem(group, service) { return false; } -export default async function getServiceWidget(group, service) { +export default async function getServiceWidget(group, service, index) { const serviceItem = await getServiceItem(group, service); if (serviceItem) { - const { widget } = serviceItem; - return widget; + const { widget, widgets } = serviceItem; + return index > -1 && widgets ? widgets[index] : widget; } - return false; } diff --git a/src/utils/proxy/api-helpers.js b/src/utils/proxy/api-helpers.js index 8e0682db..a02ea623 100644 --- a/src/utils/proxy/api-helpers.js +++ b/src/utils/proxy/api-helpers.js @@ -12,6 +12,7 @@ export function getURLSearchParams(widget, endpoint) { const params = new URLSearchParams({ group: widget.service_group, service: widget.service_name, + index: widget.index, }); if (endpoint) { params.append("endpoint", endpoint); diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index cbe0422a..cea95196 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -9,10 +9,10 @@ import widgets from "widgets/widgets"; const logger = createLogger("credentialedProxyHandler"); export default async function credentialedProxyHandler(req, res, map) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index c6b9236b..2e788a98 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -8,10 +8,10 @@ import widgets from "widgets/widgets"; const logger = createLogger("genericProxyHandler"); export default async function genericProxyHandler(req, res, map) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/utils/proxy/handlers/jsonrpc.js b/src/utils/proxy/handlers/jsonrpc.js index 3974dbdc..f9fb1883 100644 --- a/src/utils/proxy/handlers/jsonrpc.js +++ b/src/utils/proxy/handlers/jsonrpc.js @@ -65,10 +65,10 @@ export async function sendJsonRpcRequest(url, method, params, widget) { } export default async function jsonrpcProxyHandler(req, res) { - const { group, service, endpoint: method } = req.query; + const { group, service, endpoint: method, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); const api = widgets?.[widget.type]?.api; const [, mapping] = Object.entries(widgets?.[widget.type]?.mappings).find(([, value]) => value.endpoint === method); diff --git a/src/utils/proxy/handlers/synology.js b/src/utils/proxy/handlers/synology.js index be44e810..030e53ba 100644 --- a/src/utils/proxy/handlers/synology.js +++ b/src/utils/proxy/handlers/synology.js @@ -131,13 +131,13 @@ function toError(url, synologyError) { } export default async function synologyProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const serviceWidget = await getServiceWidget(group, service); + const serviceWidget = await getServiceWidget(group, service, index); const widget = widgets?.[serviceWidget.type]; const mapping = widget?.mappings?.[endpoint]; if (!widget.api || !mapping) { diff --git a/src/widgets/audiobookshelf/proxy.js b/src/widgets/audiobookshelf/proxy.js index 9701c1fe..1a89736b 100644 --- a/src/widgets/audiobookshelf/proxy.js +++ b/src/widgets/audiobookshelf/proxy.js @@ -23,14 +23,14 @@ async function retrieveFromAPI(url, key) { } export default async function audiobookshelfProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/beszel/proxy.js b/src/widgets/beszel/proxy.js index 04083e42..61bc969b 100644 --- a/src/widgets/beszel/proxy.js +++ b/src/widgets/beszel/proxy.js @@ -34,10 +34,10 @@ async function login(loginUrl, username, password, service) { } export default async function beszelProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/widgets/calendar/proxy.js b/src/widgets/calendar/proxy.js index cf754424..d36f30c9 100644 --- a/src/widgets/calendar/proxy.js +++ b/src/widgets/calendar/proxy.js @@ -5,10 +5,10 @@ import createLogger from "utils/logger"; const logger = createLogger("calendarProxyHandler"); export default async function calendarProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); const integration = widget.integrations?.find((i) => i.name === endpoint); if (integration) { diff --git a/src/widgets/crowdsec/proxy.js b/src/widgets/crowdsec/proxy.js index e78fbc5e..85803845 100644 --- a/src/widgets/crowdsec/proxy.js +++ b/src/widgets/crowdsec/proxy.js @@ -35,14 +35,14 @@ async function login(widget, service) { } export default async function crowdsecProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.error("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget || !widgets[widget.type].api) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/deluge/proxy.js b/src/widgets/deluge/proxy.js index b86873a8..61329697 100644 --- a/src/widgets/deluge/proxy.js +++ b/src/widgets/deluge/proxy.js @@ -40,14 +40,14 @@ function login(url, password) { } export default async function delugeProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/flood/proxy.js b/src/widgets/flood/proxy.js index 3345ad7b..e0c10173 100644 --- a/src/widgets/flood/proxy.js +++ b/src/widgets/flood/proxy.js @@ -28,14 +28,14 @@ async function login(widget) { } export default async function floodProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/freshrss/proxy.js b/src/widgets/freshrss/proxy.js index c08e5c87..881094bd 100644 --- a/src/widgets/freshrss/proxy.js +++ b/src/widgets/freshrss/proxy.js @@ -74,14 +74,14 @@ async function apiCall(widget, endpoint, service) { } export default async function freshrssProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/fritzbox/proxy.js b/src/widgets/fritzbox/proxy.js index a0a22d8b..d1a66d97 100644 --- a/src/widgets/fritzbox/proxy.js +++ b/src/widgets/fritzbox/proxy.js @@ -46,8 +46,8 @@ async function requestEndpoint(apiBaseUrl, service, action) { } export default async function fritzboxProxyHandler(req, res) { - const { group, service } = req.query; - const serviceWidget = await getServiceWidget(group, service); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); if (!serviceWidget) { res.status(500).json({ error: { message: "Service widget not found" } }); diff --git a/src/widgets/gamedig/proxy.js b/src/widgets/gamedig/proxy.js index 05fa615c..ecf6e4c6 100644 --- a/src/widgets/gamedig/proxy.js +++ b/src/widgets/gamedig/proxy.js @@ -7,8 +7,8 @@ const proxyName = "gamedigProxyHandler"; const logger = createLogger(proxyName); export default async function gamedigProxyHandler(req, res) { - const { group, service } = req.query; - const serviceWidget = await getServiceWidget(group, service); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const url = new URL(serviceWidget.url); try { diff --git a/src/widgets/homeassistant/proxy.js b/src/widgets/homeassistant/proxy.js index fe488f86..e1f02ddb 100644 --- a/src/widgets/homeassistant/proxy.js +++ b/src/widgets/homeassistant/proxy.js @@ -62,14 +62,14 @@ async function getQuery(query, { url, key }) { } export default async function homeassistantProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/homebox/proxy.js b/src/widgets/homebox/proxy.js index 0d6fdf13..c91ce552 100644 --- a/src/widgets/homebox/proxy.js +++ b/src/widgets/homebox/proxy.js @@ -68,14 +68,14 @@ async function apiCall(widget, endpoint, service) { } export default async function homeboxProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/homebridge/proxy.js b/src/widgets/homebridge/proxy.js index 17dc8635..4da9197b 100644 --- a/src/widgets/homebridge/proxy.js +++ b/src/widgets/homebridge/proxy.js @@ -71,14 +71,14 @@ async function apiCall(widget, endpoint, service) { } export default async function homebridgeProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/jackett/proxy.js b/src/widgets/jackett/proxy.js index 5292695f..035309b3 100644 --- a/src/widgets/jackett/proxy.js +++ b/src/widgets/jackett/proxy.js @@ -25,14 +25,14 @@ async function fetchJackettCookie(widget, loginURL) { } export default async function jackettProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.error("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget || !widgets[widget.type].api) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/jdownloader/proxy.js b/src/widgets/jdownloader/proxy.js index 88a92d95..ae8c845c 100644 --- a/src/widgets/jdownloader/proxy.js +++ b/src/widgets/jdownloader/proxy.js @@ -12,12 +12,12 @@ const proxyName = "jdownloaderProxyHandler"; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return null; diff --git a/src/widgets/kavita/proxy.js b/src/widgets/kavita/proxy.js index b8e9813f..1c41c45f 100644 --- a/src/widgets/kavita/proxy.js +++ b/src/widgets/kavita/proxy.js @@ -70,14 +70,14 @@ async function apiCall(widget, endpoint, service) { } export default async function KavitaProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/minecraft/proxy.js b/src/widgets/minecraft/proxy.js index f7bac9d4..98d1be88 100644 --- a/src/widgets/minecraft/proxy.js +++ b/src/widgets/minecraft/proxy.js @@ -7,8 +7,8 @@ const proxyName = "minecraftProxyHandler"; const logger = createLogger(proxyName); export default async function minecraftProxyHandler(req, res) { - const { group, service } = req.query; - const serviceWidget = await getServiceWidget(group, service); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const url = new URL(serviceWidget.url); try { const pingResponse = await pingWithPromise(url.hostname, url.port || 25565); diff --git a/src/widgets/npm/proxy.js b/src/widgets/npm/proxy.js index 978254f8..6c7ba09e 100644 --- a/src/widgets/npm/proxy.js +++ b/src/widgets/npm/proxy.js @@ -36,10 +36,10 @@ async function login(loginUrl, username, password, service) { } export default async function npmProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widgets?.[widget.type]?.api) { return res.status(403).json({ error: "Service does not support API calls" }); diff --git a/src/widgets/omada/proxy.js b/src/widgets/omada/proxy.js index 8e8994a5..f4da1293 100644 --- a/src/widgets/omada/proxy.js +++ b/src/widgets/omada/proxy.js @@ -33,10 +33,10 @@ async function login(loginUrl, username, password, controllerVersionMajor) { } export default async function omadaProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (widget) { const { url } = widget; diff --git a/src/widgets/openmediavault/proxy.js b/src/widgets/openmediavault/proxy.js index e1f97a56..9cda42e8 100644 --- a/src/widgets/openmediavault/proxy.js +++ b/src/widgets/openmediavault/proxy.js @@ -12,14 +12,14 @@ const BG_POLL_PERIOD = 500; const logger = createLogger(PROXY_NAME); async function getWidget(req) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/openwrt/proxy.js b/src/widgets/openwrt/proxy.js index 977db8ca..0a0da3ff 100644 --- a/src/widgets/openwrt/proxy.js +++ b/src/widgets/openwrt/proxy.js @@ -17,14 +17,14 @@ const PARAMS = { }; async function getWidget(req) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/photoprism/proxy.js b/src/widgets/photoprism/proxy.js index 509bfa0c..fe5096b3 100644 --- a/src/widgets/photoprism/proxy.js +++ b/src/widgets/photoprism/proxy.js @@ -6,14 +6,14 @@ import createLogger from "utils/logger"; const logger = createLogger("photoprismProxyHandler"); export default async function photoprismProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/pihole/proxy.js b/src/widgets/pihole/proxy.js index 35873fa9..bf24624d 100644 --- a/src/widgets/pihole/proxy.js +++ b/src/widgets/pihole/proxy.js @@ -33,7 +33,7 @@ async function login(widget, service) { } export default async function piholeProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; let endpoint = "stats/summary"; if (!group || !service) { @@ -41,7 +41,7 @@ export default async function piholeProxyHandler(req, res) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid widget configuration" }); diff --git a/src/widgets/plex/proxy.js b/src/widgets/plex/proxy.js index d8033065..2956f280 100644 --- a/src/widgets/plex/proxy.js +++ b/src/widgets/plex/proxy.js @@ -16,14 +16,14 @@ const tvCacheKey = `${proxyName}__tv`; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return null; } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/pyload/proxy.js b/src/widgets/pyload/proxy.js index d9469d1c..a380c865 100644 --- a/src/widgets/pyload/proxy.js +++ b/src/widgets/pyload/proxy.js @@ -67,11 +67,11 @@ async function login(loginUrl, service, username, password = "") { } export default async function pyloadProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; try { if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (widget) { const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); diff --git a/src/widgets/qbittorrent/proxy.js b/src/widgets/qbittorrent/proxy.js index e1a0f055..aead7582 100644 --- a/src/widgets/qbittorrent/proxy.js +++ b/src/widgets/qbittorrent/proxy.js @@ -21,14 +21,14 @@ async function login(widget) { } export default async function qbittorrentProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/qnap/proxy.js b/src/widgets/qnap/proxy.js index 508c8a46..07917d28 100644 --- a/src/widgets/qnap/proxy.js +++ b/src/widgets/qnap/proxy.js @@ -77,14 +77,14 @@ async function apiCall(widget, endpoint, service) { } export default async function qnapProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); diff --git a/src/widgets/rutorrent/proxy.js b/src/widgets/rutorrent/proxy.js index 47c76191..e0ae44fe 100644 --- a/src/widgets/rutorrent/proxy.js +++ b/src/widgets/rutorrent/proxy.js @@ -45,10 +45,10 @@ const getTorrentInfo = (data) => ({ }); export default async function rutorrentProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (group && service) { - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (widget) { const api = widgets?.[widget.type]?.api; diff --git a/src/widgets/suwayomi/proxy.js b/src/widgets/suwayomi/proxy.js index d4d71675..def811cc 100644 --- a/src/widgets/suwayomi/proxy.js +++ b/src/widgets/suwayomi/proxy.js @@ -114,14 +114,14 @@ function extractCounts(responseJSON, fields) { } export default async function suwayomiProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/tdarr/proxy.js b/src/widgets/tdarr/proxy.js index 9e26fdc0..88da30fd 100644 --- a/src/widgets/tdarr/proxy.js +++ b/src/widgets/tdarr/proxy.js @@ -8,14 +8,14 @@ const proxyName = "tdarrProxyHandler"; const logger = createLogger(proxyName); export default async function tdarrProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/transmission/proxy.js b/src/widgets/transmission/proxy.js index 823def05..8b8049bc 100644 --- a/src/widgets/transmission/proxy.js +++ b/src/widgets/transmission/proxy.js @@ -11,14 +11,14 @@ const headerCacheKey = `${proxyName}__headers`; const logger = createLogger(proxyName); export default async function transmissionProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/unifi/proxy.js b/src/widgets/unifi/proxy.js index 98c98f37..559065e3 100644 --- a/src/widgets/unifi/proxy.js +++ b/src/widgets/unifi/proxy.js @@ -14,13 +14,13 @@ const prefixCacheKey = `${proxyName}__prefix`; const logger = createLogger(proxyName); async function getWidget(req) { - const { group, service } = req.query; + const { group, service, index } = req.query; let widget = null; if (group === "unifi_console" && service === "unifi_console") { // info widget - const index = req.query?.query ? JSON.parse(req.query.query).index : undefined; - widget = await getPrivateWidgetOptions("unifi_console", index); + const infowidgetIndex = req.query?.query ? JSON.parse(req.query.query).index : undefined; + widget = await getPrivateWidgetOptions("unifi_console", infowidgetIndex); if (!widget) { logger.debug("Error retrieving settings for this Unifi widget"); return null; @@ -32,7 +32,7 @@ async function getWidget(req) { return null; } - widget = await getServiceWidget(group, service); + widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/urbackup/proxy.js b/src/widgets/urbackup/proxy.js index 94b8eeff..4e7a0a8d 100644 --- a/src/widgets/urbackup/proxy.js +++ b/src/widgets/urbackup/proxy.js @@ -3,8 +3,8 @@ import { UrbackupServer } from "urbackup-server-api"; import getServiceWidget from "utils/config/service-helpers"; export default async function urbackupProxyHandler(req, res) { - const { group, service } = req.query; - const serviceWidget = await getServiceWidget(group, service); + const { group, service, index } = req.query; + const serviceWidget = await getServiceWidget(group, service, index); const server = new UrbackupServer({ url: serviceWidget.url, diff --git a/src/widgets/watchtower/proxy.js b/src/widgets/watchtower/proxy.js index b3155a1e..588d08ee 100644 --- a/src/widgets/watchtower/proxy.js +++ b/src/widgets/watchtower/proxy.js @@ -8,14 +8,14 @@ const proxyName = "watchtowerProxyHandler"; const logger = createLogger(proxyName); export default async function watchtowerProxyHandler(req, res) { - const { group, service, endpoint } = req.query; + const { group, service, endpoint, index } = req.query; if (!group || !service) { logger.debug("Invalid or missing service '%s' or group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); if (!widget) { logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); diff --git a/src/widgets/xteve/proxy.js b/src/widgets/xteve/proxy.js index 421f2b49..453e3645 100644 --- a/src/widgets/xteve/proxy.js +++ b/src/widgets/xteve/proxy.js @@ -7,13 +7,13 @@ import getServiceWidget from "utils/config/service-helpers"; const logger = createLogger("xteveProxyHandler"); export default async function xteveProxyHandler(req, res) { - const { group, service } = req.query; + const { group, service, index } = req.query; if (!group || !service) { return res.status(400).json({ error: "Invalid proxy service type" }); } - const widget = await getServiceWidget(group, service); + const widget = await getServiceWidget(group, service, index); const api = widgets?.[widget.type]?.api; if (!api) { return res.status(403).json({ error: "Service does not support API calls" }); From be8363cc3514749f54c5060c6548138129612e82 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:01:47 -0800 Subject: [PATCH 051/195] Feature: nested groups (#4346) --- src/components/services/group.jsx | 33 ++++++++-- src/components/services/item.jsx | 6 +- src/components/services/list.jsx | 4 +- src/components/services/ping.jsx | 4 +- src/components/services/site-monitor.jsx | 4 +- src/pages/api/ping.js | 6 +- src/pages/api/siteMonitor.js | 6 +- src/pages/index.jsx | 6 +- src/utils/config/api-response.js | 24 +++++-- src/utils/config/service-helpers.js | 81 ++++++++++++++++-------- 10 files changed, 119 insertions(+), 55 deletions(-) diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx index cdbb89f3..f25f7ec1 100644 --- a/src/components/services/group.jsx +++ b/src/components/services/group.jsx @@ -3,12 +3,13 @@ import classNames from "classnames"; import { Disclosure, Transition } from "@headlessui/react"; import { MdKeyboardArrowDown } from "react-icons/md"; +import { columnMap } from "../../utils/layout/columns"; + import List from "components/services/list"; import ResolvedIcon from "components/resolvedicon"; export default function ServicesGroup({ group, - services, layout, fiveColumns, disableCollapse, @@ -23,7 +24,7 @@ export default function ServicesGroup({ return (
)}

- {services.name} + {group.name}

- + + {group.groups?.length > 0 && ( +
+ {group.groups.map((subgroup) => ( + + ))} +
+ )}
diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 54560d6f..adf5fc97 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -12,7 +12,7 @@ import Kubernetes from "widgets/kubernetes/component"; import { SettingsContext } from "utils/contexts/settings"; import ResolvedIcon from "components/resolvedicon"; -export default function Item({ service, group, useEqualHeights }) { +export default function Item({ service, groupName, useEqualHeights }) { const hasLink = service.href && service.href !== "#"; const { settings } = useContext(SettingsContext); const showStats = service.showStats === false ? false : settings.showStats; @@ -90,14 +90,14 @@ export default function Item({ service, group, useEqualHeights }) { > {service.ping && (
- + Ping status
)} {service.siteMonitor && (
- + Site monitor status
)} diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx index f3fd6e2a..c15d6aed 100644 --- a/src/components/services/list.jsx +++ b/src/components/services/list.jsx @@ -4,7 +4,7 @@ import { columnMap } from "../../utils/layout/columns"; import Item from "components/services/item"; -export default function List({ group, services, layout, useEqualHeights }) { +export default function List({ groupName, services, layout, useEqualHeights }) { return (
    s).join("-")} service={service} - group={group} + groupName={groupName} useEqualHeights={layout?.useEqualHeights ?? useEqualHeights} /> ))} diff --git a/src/components/services/ping.jsx b/src/components/services/ping.jsx index f72d40b3..670f9d4b 100644 --- a/src/components/services/ping.jsx +++ b/src/components/services/ping.jsx @@ -1,9 +1,9 @@ import { useTranslation } from "react-i18next"; import useSWR from "swr"; -export default function Ping({ group, service, style }) { +export default function Ping({ groupName, serviceName, style }) { const { t } = useTranslation(); - const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ group, service }).toString()}`, { + const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ groupName, serviceName }).toString()}`, { refreshInterval: 30000, }); diff --git a/src/components/services/site-monitor.jsx b/src/components/services/site-monitor.jsx index 3d5ef79e..4dceb44c 100644 --- a/src/components/services/site-monitor.jsx +++ b/src/components/services/site-monitor.jsx @@ -1,9 +1,9 @@ import { useTranslation } from "react-i18next"; import useSWR from "swr"; -export default function SiteMonitor({ group, service, style }) { +export default function SiteMonitor({ groupName, serviceName, style }) { const { t } = useTranslation(); - const { data, error } = useSWR(`/api/siteMonitor?${new URLSearchParams({ group, service }).toString()}`, { + const { data, error } = useSWR(`/api/siteMonitor?${new URLSearchParams({ groupName, serviceName }).toString()}`, { refreshInterval: 30000, }); diff --git a/src/pages/api/ping.js b/src/pages/api/ping.js index e540fa68..8ef64ffc 100644 --- a/src/pages/api/ping.js +++ b/src/pages/api/ping.js @@ -6,10 +6,10 @@ import createLogger from "utils/logger"; const logger = createLogger("ping"); export default async function handler(req, res) { - const { group, service } = req.query; - const serviceItem = await getServiceItem(group, service); + const { groupName, serviceName } = req.query; + const serviceItem = await getServiceItem(groupName, serviceName); if (!serviceItem) { - logger.debug(`No service item found for group ${group} named ${service}`); + logger.debug(`No service item found for group ${groupName} named ${serviceName}`); return res.status(400).send({ error: "Unable to find service, see log for details.", }); diff --git a/src/pages/api/siteMonitor.js b/src/pages/api/siteMonitor.js index 9e030d74..072d3d4c 100644 --- a/src/pages/api/siteMonitor.js +++ b/src/pages/api/siteMonitor.js @@ -7,10 +7,10 @@ import { httpProxy } from "utils/proxy/http"; const logger = createLogger("siteMonitor"); export default async function handler(req, res) { - const { group, service } = req.query; - const serviceItem = await getServiceItem(group, service); + const { groupName, serviceName } = req.query; + const serviceItem = await getServiceItem(groupName, serviceName); if (!serviceItem) { - logger.debug(`No service item found for group ${group} named ${service}`); + logger.debug(`No service item found for group ${groupName} named ${serviceName}`); return res.status(400).send({ error: "Unable to find service, see log for details.", }); diff --git a/src/pages/index.jsx b/src/pages/index.jsx index dd0df95f..7a7fdef0 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -291,8 +291,7 @@ function Home({ initialSettings }) { group.services ? ( ( { + if (group.name === mergedGroup.name) { + // eslint-disable-next-line no-param-reassign + group.services = mergedGroup.services; + } else if (group.groups) { + mergeSubgroups(group.groups, mergedGroup); + } + }); +} + export async function servicesResponse() { let discoveredDockerServices; let discoveredKubernetesServices; @@ -140,25 +152,29 @@ export async function servicesResponse() { const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null; mergedGroupsNames.forEach((groupName) => { - const discoveredDockerGroup = discoveredDockerServices.find((group) => group.name === groupName) || { + const discoveredDockerGroup = findGroupByName(discoveredDockerServices, groupName) || { services: [], }; - const discoveredKubernetesGroup = discoveredKubernetesServices.find((group) => group.name === groupName) || { + const discoveredKubernetesGroup = findGroupByName(discoveredKubernetesServices, groupName) || { services: [], }; - const configuredGroup = configuredServices.find((group) => group.name === groupName) || { services: [] }; + const configuredGroup = findGroupByName(configuredServices, groupName) || { services: [] }; const mergedGroup = { name: groupName, services: [...discoveredDockerGroup.services, ...discoveredKubernetesGroup.services, ...configuredGroup.services] .filter((service) => service) .sort(compareServices), + groups: [...configuredGroup.groups], }; if (definedLayouts) { const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name); if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup; - else unsortedGroups.push(mergedGroup); + else if (configuredGroup.name) { + // this is a nested group, so find the parent group and merge the services + mergeSubgroups(configuredServices, mergedGroup); + } else unsortedGroups.push(mergedGroup); } else { unsortedGroups.push(mergedGroup); } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index e6ef6173..0f1e2c8c 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -13,6 +13,38 @@ import * as shvl from "utils/config/shvl"; const logger = createLogger("service-helpers"); +function parseServicesToGroups(services) { + if (!services) { + return []; + } + + // map easy to write YAML objects into easy to consume JS arrays + return services.map((serviceGroup) => { + const name = Object.keys(serviceGroup)[0]; + let groups = []; + const serviceGroupServices = []; + serviceGroup[name].forEach((entries) => { + const entryName = Object.keys(entries)[0]; + if (Array.isArray(entries[entryName])) { + groups = groups.concat(parseServicesToGroups([{ [entryName]: entries[entryName] }])); + } else { + serviceGroupServices.push({ + name: entryName, + ...entries[entryName], + weight: entries[entryName].weight || serviceGroupServices.length * 100, // default weight + type: "service", + }); + } + }); + return { + name, + type: "group", + services: serviceGroupServices, + groups, + }; + }); +} + export async function servicesFromConfig() { checkAndCopyConfig("services.yaml"); @@ -20,31 +52,7 @@ export async function servicesFromConfig() { const rawFileContents = await fs.readFile(servicesYaml, "utf8"); const fileContents = substituteEnvironmentVars(rawFileContents); const services = yaml.load(fileContents); - - if (!services) { - return []; - } - - // map easy to write YAML objects into easy to consume JS arrays - const servicesArray = services.map((servicesGroup) => ({ - name: Object.keys(servicesGroup)[0], - services: servicesGroup[Object.keys(servicesGroup)[0]].map((entries) => ({ - name: Object.keys(entries)[0], - ...entries[Object.keys(entries)[0]], - type: "service", - })), - })); - - // add default weight to services based on their position in the configuration - servicesArray.forEach((group, groupIndex) => { - group.services.forEach((service, serviceIndex) => { - if (service.weight === undefined) { - servicesArray[groupIndex].services[serviceIndex].weight = (serviceIndex + 1) * 100; - } - }); - }); - - return servicesArray; + return parseServicesToGroups(services); } export async function servicesFromDocker() { @@ -667,13 +675,30 @@ export function cleanServiceGroups(groups) { }); return cleanedService; }), + type: serviceGroup.type || "group", + groups: serviceGroup.groups ? cleanServiceGroups(serviceGroup.groups) : [], })); } +export function findGroupByName(groups, name) { + for (let i = 0; i < groups.length; i += 1) { + const group = groups[i]; + if (group.name === name) { + return group; + } else if (group.groups) { + const foundGroup = findGroupByName(group.groups, name); + if (foundGroup) { + return foundGroup; + } + } + } + return null; +} + export async function getServiceItem(group, service) { const configuredServices = await servicesFromConfig(); - const serviceGroup = configuredServices.find((g) => g.name === group); + const serviceGroup = findGroupByName(configuredServices, group); if (serviceGroup) { const serviceEntry = serviceGroup.services.find((s) => s.name === service); if (serviceEntry) return serviceEntry; @@ -681,14 +706,14 @@ export async function getServiceItem(group, service) { const discoveredServices = await servicesFromDocker(); - const dockerServiceGroup = discoveredServices.find((g) => g.name === group); + const dockerServiceGroup = findGroupByName(discoveredServices, group); if (dockerServiceGroup) { const dockerServiceEntry = dockerServiceGroup.services.find((s) => s.name === service); if (dockerServiceEntry) return dockerServiceEntry; } const kubernetesServices = await servicesFromKubernetes(); - const kubernetesServiceGroup = kubernetesServices.find((g) => g.name === group); + const kubernetesServiceGroup = findGroupByName(kubernetesServices, group); if (kubernetesServiceGroup) { const kubernetesServiceEntry = kubernetesServiceGroup.services.find((s) => s.name === service); if (kubernetesServiceEntry) return kubernetesServiceEntry; From aaf4a3e92f2e3bc6933f3a2699c3b764818ff5bd Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:59:15 -0800 Subject: [PATCH 052/195] Add note --- src/utils/config/service-helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 0f1e2c8c..e3c491ac 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -681,6 +681,7 @@ export function cleanServiceGroups(groups) { } export function findGroupByName(groups, name) { + // Deep search for a group by name. Using for loop allows for early return for (let i = 0; i < groups.length; i += 1) { const group = groups[i]; if (group.name === name) { From 230da3d2ebddb9031f601a871a7483430500617f Mon Sep 17 00:00:00 2001 From: DamitusThyYeetus123 <108782125+DamitusThyYeetus123@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:45:30 +1100 Subject: [PATCH 053/195] Enhancement: support hrefs for info widgets (#4347) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/components/widgets/widget/container.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/widgets/widget/container.jsx b/src/components/widgets/widget/container.jsx index c9240dd3..4a1fdd37 100644 --- a/src/components/widgets/widget/container.jsx +++ b/src/components/widgets/widget/container.jsx @@ -1,10 +1,13 @@ import classNames from "classnames"; +import { useContext } from "react"; import WidgetIcon from "./widget_icon"; import PrimaryText from "./primary_text"; import SecondaryText from "./secondary_text"; import Raw from "./raw"; +import { SettingsContext } from "utils/contexts/settings"; + export function getAllClasses(options, additionalClassNames = "") { if (options?.style?.header === "boxedWidgets") { if (options?.style?.cardBlur !== undefined) { @@ -56,7 +59,17 @@ export function getBottomBlock(children) { } export default function Container({ children = [], options, additionalClassNames = "" }) { - return ( + const { settings } = useContext(SettingsContext); + return options.href ? ( + + {getInnerBlock(children)} + {getBottomBlock(children)} + + ) : (
    {getInnerBlock(children)} {getBottomBlock(children)} From 6d829bce7993795ecaa7c89206afb904948f4cb5 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 27 Nov 2024 22:12:59 -0800 Subject: [PATCH 054/195] Enhancement: use css color-scheme (#4349) --- src/pages/index.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 7a7fdef0..3f8ebf86 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -452,6 +452,7 @@ function Home({ initialSettings }) { } export default function Wrapper({ initialSettings, fallback }) { + const { theme } = useContext(ThemeContext); const wrappedStyle = {}; let backgroundBlur = false; let backgroundSaturate = false; @@ -482,8 +483,9 @@ export default function Wrapper({ initialSettings, fallback }) { id="page_wrapper" className={classNames( "relative", - initialSettings.theme && initialSettings.theme, + theme && theme, initialSettings.color && `theme-${initialSettings.color}`, + theme === "dark" ? "scheme-dark" : "scheme-light", )} >
    Date: Wed, 27 Nov 2024 22:49:14 -0800 Subject: [PATCH 055/195] Documentation: doc updates for nesting, reorganizing, fixes --- docs/configs/info-widgets.md | 24 +++++++++++ docs/configs/service-widgets.md | 58 ------------------------- docs/configs/services.md | 71 +++++++++++++++++++++++++++++++ docs/configs/settings.md | 29 +++++++++++-- docs/troubleshooting/index.md | 2 +- docs/widgets/authoring/proxies.md | 2 +- docs/widgets/index.md | 4 ++ docs/widgets/info/openmeteo.md | 2 +- docs/widgets/info/weather.md | 22 ---------- mkdocs.yml | 4 +- src/skeleton/services.yaml | 2 +- src/skeleton/settings.yaml | 2 +- src/skeleton/widgets.yaml | 2 +- 13 files changed, 132 insertions(+), 92 deletions(-) create mode 100644 docs/configs/info-widgets.md delete mode 100644 docs/configs/service-widgets.md delete mode 100644 docs/widgets/info/weather.md diff --git a/docs/configs/info-widgets.md b/docs/configs/info-widgets.md new file mode 100644 index 00000000..76314396 --- /dev/null +++ b/docs/configs/info-widgets.md @@ -0,0 +1,24 @@ +--- +title: Information Widgets +description: Homepage info widgets. +--- + +Information widgets are widgets that provide information about your system or environment and are displayed at the top of the homepage. You can find a list of all available info widgets under the [Info Widgets](../widgets/info/index.md) section. + +Info widgets are defined in the widgets.yaml + +Each widget has its own configuration options, which are detailed in the widget's documentation. + +## Layout + +Info widgets are displayed in the order they are defined in the `widgets.yaml` file. You can change the order by moving the widgets around in the file. However, some widgets (weather, search and datetime) are aligned to the right side of the screen which can affect the layout of the widgets. + +## Adding A Link + +You can add a link to an info widget such as the logo or text widgets by adding an `href` option, for example: + +```yaml +logo: + href: https://example.com + target: _blank # Optional, can be set in settings +``` diff --git a/docs/configs/service-widgets.md b/docs/configs/service-widgets.md deleted file mode 100644 index df696f61..00000000 --- a/docs/configs/service-widgets.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Service Widgets -description: Service Widget Configuration ---- - -Unless otherwise noted, URLs should not end with a `/` or other API path. Each widget will handle the path on its own. - -Each service can have widgets attached to it (often matching the service type, but that's not forced). - -In addition to the href of the service, you can also specify the target location in which to open that link. See [Link Target](settings.md#link-target) for more details. - -Using Emby as an example, this is how you would attach the Emby service widget. - -```yaml -- Emby: - icon: emby.png - href: http://emby.host.or.ip/ - description: Movies & TV Shows - widget: - type: emby - url: http://emby.host.or.ip - key: apikeyapikeyapikeyapikeyapikey -``` - -## Multiple Widgets - -Each service can have multiple widgets attached to it, for example: - -```yaml -- Emby: - icon: emby.png - href: http://emby.host.or.ip/ - description: Movies & TV Shows - widgets: - - type: emby - url: http://emby.host.or.ip - key: apikeyapikeyapikeyapikeyapikey - - type: uptimekuma - url: http://uptimekuma.host.or.ip:port - slug: statuspageslug -``` - -## Field Visibility - -Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields. - -**In all cases a widget will work and display all fields without specifying the `fields` property.** - -```yaml -- Sonarr: - icon: sonarr.png - href: http://sonarr.host.or.ip - widget: - type: sonarr - fields: ["wanted", "queued"] - url: http://sonarr.host.or.ip - key: apikeyapikeyapikeyapikeyapikey -``` diff --git a/docs/configs/services.md b/docs/configs/services.md index 9cb75177..6ef25c39 100644 --- a/docs/configs/services.md +++ b/docs/configs/services.md @@ -21,6 +21,23 @@ Groups are defined as top-level array entries. Service Groups +### Nested Groups + +Groups can be nested by using the same format as the top-level groups. + +```yaml +- Group A: + - Service A: + href: http://localhost/ + + - Group B: + - Service B: + href: http://localhost/ + + - Service C: + href: http://localhost/ +``` + ## Services Services are defined as array entries on groups, @@ -43,6 +60,60 @@ Services are defined as array entries on groups, Service Services +### Service Widgets + +Each service can have widgets attached to it (often matching the service type, but that's not forced). + +In addition to the href of the service, you can also specify the target location in which to open that link. See [Link Target](settings.md#link-target) for more details. + +Using Emby as an example, this is how you would attach the Emby service widget. + +```yaml +- Emby: + icon: emby.png + href: http://emby.host.or.ip/ + description: Movies & TV Shows + widget: + type: emby + url: http://emby.host.or.ip + key: apikeyapikeyapikeyapikeyapikey +``` + +#### Multiple Widgets + +Each service can have multiple widgets attached to it, for example: + +```yaml +- Emby: + icon: emby.png + href: http://emby.host.or.ip/ + description: Movies & TV Shows + widgets: + - type: emby + url: http://emby.host.or.ip + key: apikeyapikeyapikeyapikeyapikey + - type: uptimekuma + url: http://uptimekuma.host.or.ip:port + slug: statuspageslug +``` + +#### Field Visibility + +Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields. + +**In all cases a widget will work and display all fields without specifying the `fields` property.** + +```yaml +- Sonarr: + icon: sonarr.png + href: http://sonarr.host.or.ip + widget: + type: sonarr + fields: ["wanted", "queued"] + url: http://sonarr.host.or.ip + key: apikeyapikeyapikeyapikeyapikey +``` + ## Descriptions Services may have descriptions, diff --git a/docs/configs/settings.md b/docs/configs/settings.md index 2f387a65..7e1815bb 100644 --- a/docs/configs/settings.md +++ b/docs/configs/settings.md @@ -137,6 +137,27 @@ layout: columns: 3 ``` +### Nested Groups + +If your services config has nested groups, you can apply settings to these groups by nesting them in the layout block +and using the same settings. For example + +```yaml +layout: + Group A: + style: row + columns: 4 + Group C: + style: row + columns: 2 + Nested Group A: + style: row + columns: 2 + Nested Group B: + style: row + columns: 2 +``` + ### Headers You can hide headers for each section in the layout as well by passing `header` as false, like so: @@ -348,12 +369,12 @@ This can also be set for individual services. Note setting this at the service l ## Providers -The `providers` section allows you to define shared API provider options and secrets. Currently this allows you to define your weather API keys in secret and is also the location of the Longhorn URL and credentials. +The `providers` section allows you to define shared API provider options and secrets. ```yaml providers: openweathermap: openweathermapapikey - weatherapi: weatherapiapikey + finnhub: yourfinnhubapikeyhere longhorn: url: https://longhorn.example.com username: admin @@ -363,10 +384,10 @@ providers: You can then pass `provider` instead of `apiKey` in your widget configuration. ```yaml -- weatherapi: +- openweathermap: latitude: 50.449684 longitude: 30.525026 - provider: weatherapi + provider: openweathermap ``` ## Quick Launch diff --git a/docs/troubleshooting/index.md b/docs/troubleshooting/index.md index 82a7381c..bbde4cf3 100644 --- a/docs/troubleshooting/index.md +++ b/docs/troubleshooting/index.md @@ -17,7 +17,7 @@ hide: All service widgets work essentially the same, that is, homepage makes a proxied call to an API made available by that service. The majority of the time widgets don't work it is a configuration issue. Of course, sometimes things do break. Some basic steps to try: -1. Ensure that you follow the rule mentioned on https://gethomepage.dev/configs/service-widgets/. **Unless otherwise noted, URLs should not end with a / or other API path. Each widget will handle the path on its own.**. This is very important as including a trailing slash can result in an error. +1. **URLs should not end with a / or other API path. Each widget will handle the path on its own.**. Including a trailing slash can result in an error. 2. Verify the homepage installation can connect to the IP address or host you are using for the widget `url`. This is most simply achieved by pinging the server from the homepage machine, in Docker this means _from inside the container_ itself, e.g.: diff --git a/docs/widgets/authoring/proxies.md b/docs/widgets/authoring/proxies.md index 15cdb670..a8b8073e 100644 --- a/docs/widgets/authoring/proxies.md +++ b/docs/widgets/authoring/proxies.md @@ -50,7 +50,7 @@ You can also pass API keys from the widget configuration to the proxy handler, f ### `credentialedProxyHandler` -A proxy handler that makes authenticated by setting request headers. Credentials are pulled from the widgets configuration. +A proxy handler that makes authenticated requests by setting request headers. Credentials are pulled from the widgets configuration. By default the key is passed as an `X-API-Key` header. If you need to pass the key as something else, either add a case to the credentialedProxyHandler or create a new proxy handler. diff --git a/docs/widgets/index.md b/docs/widgets/index.md index 4bd45af7..fbb8edc6 100644 --- a/docs/widgets/index.md +++ b/docs/widgets/index.md @@ -28,6 +28,8 @@ Service widgets are used to display the status of a service, often a web service slug: aaaaaaabbbbb ``` +More detail on configuring service widgets can be found in the [Service Widgets Config](../configs/services.md) section. + ## Info Widgets Info widgets are used to display information in the header, often about your system or environment. Info widgets are defined your `widgets.yaml` file. Here's an example: @@ -39,3 +41,5 @@ Info widgets are used to display information in the header, often about your sys longitude: -117.51 cache: 5 ``` + +More detail on configuring info widgets can be found in the [Info Widgets Config](../configs/info-widgets.md) section. diff --git a/docs/widgets/info/openmeteo.md b/docs/widgets/info/openmeteo.md index fb5bb171..ec84ab17 100644 --- a/docs/widgets/info/openmeteo.md +++ b/docs/widgets/info/openmeteo.md @@ -3,7 +3,7 @@ title: Open-Meteo description: Open-Meteo Information Widget Configuration --- -No registration is required at all! See [https://open-meteo.com/en/docs](https://open-meteo.com/en/docs) +Homepage's recommended weather widget. No registration is required at all! See [https://open-meteo.com/en/docs](https://open-meteo.com/en/docs) ```yaml - openmeteo: diff --git a/docs/widgets/info/weather.md b/docs/widgets/info/weather.md deleted file mode 100644 index ab13b673..00000000 --- a/docs/widgets/info/weather.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Weather API -description: Weather API Information Widget Configuration ---- - -**Note: this widget is considered 'deprecated' since there is no longer a free Weather API tier for new members. See the openmeteo or openweathermap widgets for alternatives.** - -The free tier is all that's required, you will need to [register](https://www.weatherapi.com/signup.aspx) and grab your API key. - -```yaml -- weatherapi: - label: Kyiv # optional - latitude: 50.449684 - longitude: 30.525026 - units: metric # or imperial - apiKey: yourweatherapikey - cache: 5 # Time in minutes to cache API responses, to stay within limits - format: # optional, Intl.NumberFormat options - maximumFractionDigits: 1 -``` - -You can optionally not pass a `latitude` and `longitude` and the widget will use your current location (requires a secure context, eg. HTTPS). diff --git a/mkdocs.yml b/mkdocs.yml index a19d3b83..fa2188ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,8 +21,8 @@ nav: - configs/index.md - configs/settings.md - configs/bookmarks.md + - configs/info-widgets.md - configs/services.md - - configs/service-widgets.md - configs/kubernetes.md - configs/docker.md - configs/custom-css-js.md @@ -142,6 +142,7 @@ nav: - widgets/services/spoolman.md - widgets/services/stash.md - widgets/services/stocks.md + - widgets/services/suwayomi.md - widgets/services/swagdashboard.md - widgets/services/syncthing-relay-server.md - widgets/services/tailscale.md @@ -177,7 +178,6 @@ nav: - widgets/info/search.md - widgets/info/stocks.md - widgets/info/unifi_controller.md - - widgets/info/weather.md - "Learn": - widgets/authoring/index.md - "Getting Started": widgets/authoring/getting-started.md diff --git a/src/skeleton/services.yaml b/src/skeleton/services.yaml index 77626b1c..39b37926 100644 --- a/src/skeleton/services.yaml +++ b/src/skeleton/services.yaml @@ -1,6 +1,6 @@ --- # For configuration options and examples, please see: -# https://gethomepage.dev/configs/services +# https://gethomepage.dev/configs/services/ - My First Group: - My First Service: diff --git a/src/skeleton/settings.yaml b/src/skeleton/settings.yaml index 141053f5..2e828c08 100644 --- a/src/skeleton/settings.yaml +++ b/src/skeleton/settings.yaml @@ -1,6 +1,6 @@ --- # For configuration options and examples, please see: -# https://gethomepage.dev/configs/settings +# https://gethomepage.dev/configs/settings/ providers: openweathermap: openweathermapapikey diff --git a/src/skeleton/widgets.yaml b/src/skeleton/widgets.yaml index 23c8d613..b1cf0f55 100644 --- a/src/skeleton/widgets.yaml +++ b/src/skeleton/widgets.yaml @@ -1,6 +1,6 @@ --- # For configuration options and examples, please see: -# https://gethomepage.dev/configs/service-widgets +# https://gethomepage.dev/configs/info-widgets/ - resources: cpu: true From 5cc487a96ddd6327d2d7a0d6acfabc3d5f960e59 Mon Sep 17 00:00:00 2001 From: zombaru <16330202+zombaru@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:15:28 -0800 Subject: [PATCH 056/195] Documentation: Add missing admonition type to UniFi docs (#4353) --- docs/widgets/info/unifi_controller.md | 2 +- docs/widgets/services/unifi-controller.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/widgets/info/unifi_controller.md b/docs/widgets/info/unifi_controller.md index b77d8ed0..e16ca40b 100644 --- a/docs/widgets/info/unifi_controller.md +++ b/docs/widgets/info/unifi_controller.md @@ -7,7 +7,7 @@ _(Find the Unifi Controller service widget [here](../services/unifi-controller.m You can display general connectivity status from your Unifi (Network) Controller. -!!! +!!! warning When authenticating you will want to use a local account that has at least read privileges. diff --git a/docs/widgets/services/unifi-controller.md b/docs/widgets/services/unifi-controller.md index d137c2a9..c5efc688 100644 --- a/docs/widgets/services/unifi-controller.md +++ b/docs/widgets/services/unifi-controller.md @@ -9,7 +9,7 @@ _(Find the Unifi Controller information widget [here](../info/unifi_controller.m You can display general connectivity status from your Unifi (Network) Controller. -!!! +!!! warning When authenticating you will want to use a local account that has at least read privileges. From 276a1c3ef423f7b027a84ad5c8ac7ef30d04ca2f Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 28 Nov 2024 21:54:22 -0800 Subject: [PATCH 057/195] Chore: better tailscale error handling --- src/widgets/tailscale/component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/tailscale/component.jsx b/src/widgets/tailscale/component.jsx index 3929b2ed..d3c937d5 100644 --- a/src/widgets/tailscale/component.jsx +++ b/src/widgets/tailscale/component.jsx @@ -11,8 +11,8 @@ export default function Component({ service }) { const { data: statsData, error: statsError } = useWidgetAPI(widget, "device"); - if (statsError) { - return ; + if (statsError || statsData?.message) { + return ; } if (!statsData) { From a28952ce698e330c9cfef55289acf8c5cc30efe6 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:14:53 -0800 Subject: [PATCH 058/195] Chore: move custom css loading, add letter-spacing (#4359) --- src/pages/_document.jsx | 2 ++ src/pages/index.jsx | 2 -- src/styles/globals.css | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index bfe3fc93..e69ca007 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -10,6 +10,8 @@ export default function Document() { /> + + {/* eslint-disable-line @next/next/no-css-tags */}
    diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 3f8ebf86..0bdc78b6 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -374,8 +374,6 @@ function Home({ initialSettings }) { )} - - {/* eslint-disable-line @next/next/no-css-tags */}