From 0c6c40dae7ab96f387139d037e00dcbed48b36f7 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:35:34 -0700 Subject: [PATCH 01/28] Development: specify pnpm version (#5364) --- .github/workflows/docker-publish.yml | 2 -- package.json | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 5e5e617ad..618bd2f85 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -35,7 +35,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10 run_install: false - name: Setup Node.js @@ -94,7 +93,6 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10 run_install: false - name: Setup Node.js diff --git a/package.json b/package.json index bb093c436..5f430842b 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,13 @@ "optionalDependencies": { "osx-temperature-sensor": "^1.0.8" }, + "packageManager": "pnpm@10.8.1", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "10.8.1" + } + }, "pnpm": { "onlyBuiltDependencies": [ "sharp" From 8f4013012a937ed90dd4341d19a46658322a40c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:47:34 +0000 Subject: [PATCH 02/28] Chore(deps-dev): Bump prettier from 3.5.2 to 3.5.3 (#5368) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 5f430842b..2413d8a77 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", "postcss": "^8.5.3", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "prettier-plugin-organize-imports": "^4.1.0", "tailwind-scrollbar": "^4.0.1", "tailwindcss": "^4.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3387dea7..5810b730e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,10 +98,6 @@ importers: xml-js: specifier: ^1.6.11 version: 1.6.11 - optionalDependencies: - osx-temperature-sensor: - specifier: ^1.0.8 - version: 1.0.8 devDependencies: '@tailwindcss/forms': specifier: ^0.5.10 @@ -126,7 +122,7 @@ importers: version: 6.10.2(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-prettier: specifier: ^5.2.6 - version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.2) + version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.3) eslint-plugin-react: specifier: ^7.37.4 version: 7.37.4(eslint@9.25.1(jiti@2.4.2)) @@ -137,11 +133,11 @@ importers: specifier: ^8.5.3 version: 8.5.3 prettier: - specifier: ^3.5.2 - version: 3.5.2 + specifier: ^3.5.3 + version: 3.5.3 prettier-plugin-organize-imports: specifier: ^4.1.0 - version: 4.1.0(prettier@3.5.2)(typescript@5.7.3) + version: 4.1.0(prettier@3.5.3)(typescript@5.7.3) tailwind-scrollbar: specifier: ^4.0.1 version: 4.0.1(react@18.3.1)(tailwindcss@4.0.9) @@ -151,6 +147,10 @@ importers: typescript: specifier: ^5.7.3 version: 5.7.3 + optionalDependencies: + osx-temperature-sensor: + specifier: ^1.0.8 + version: 1.0.8 packages: @@ -2262,8 +2262,8 @@ packages: vue-tsc: optional: true - prettier@3.5.2: - resolution: {integrity: sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} hasBin: true @@ -4183,10 +4183,10 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.2): + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.3): dependencies: eslint: 9.25.1(jiti@2.4.2) - prettier: 3.5.2 + prettier: 3.5.3 prettier-linter-helpers: 1.0.0 synckit: 0.11.4 optionalDependencies: @@ -5089,12 +5089,12 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-organize-imports@4.1.0(prettier@3.5.2)(typescript@5.7.3): + prettier-plugin-organize-imports@4.1.0(prettier@3.5.3)(typescript@5.7.3): dependencies: - prettier: 3.5.2 + prettier: 3.5.3 typescript: 5.7.3 - prettier@3.5.2: {} + prettier@3.5.3: {} pretty-bytes@6.1.1: {} From 417d69d39d6a4b4775081344ae3e78943857c747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:01:52 +0000 Subject: [PATCH 03/28] Chore(deps): Bump luxon from 3.5.0 to 3.6.1 (#5365) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2413d8a77..b98b6c08a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "ical.js": "^2.1.0", "js-yaml": "^4.1.0", "json-rpc-2.0": "^1.7.0", - "luxon": "^3.5.0", + "luxon": "^3.6.1", "memory-cache": "^0.2.0", "minecraftstatuspinger": "^1.2.2", "next": "^15.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5810b730e..8485f66ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: ^1.7.0 version: 1.7.0 luxon: - specifier: ^3.5.0 - version: 3.5.0 + specifier: ^3.6.1 + version: 3.6.1 memory-cache: specifier: ^0.2.0 version: 0.2.0 @@ -1989,8 +1989,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - luxon@3.5.0: - resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + luxon@3.6.1: + resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} engines: {node: '>=12'} math-intrinsics@1.1.0: @@ -4842,7 +4842,7 @@ snapshots: lru-cache@10.4.3: {} - luxon@3.5.0: {} + luxon@3.6.1: {} math-intrinsics@1.1.0: {} From fc99a0d1db6ac1772ad405c74a52bc24cee97d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:18:49 +0000 Subject: [PATCH 04/28] Chore(deps-dev): Bump tailwind-scrollbar from 4.0.1 to 4.0.2 (#5366) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b98b6c08a..eb19871e5 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "postcss": "^8.5.3", "prettier": "^3.5.3", "prettier-plugin-organize-imports": "^4.1.0", - "tailwind-scrollbar": "^4.0.1", + "tailwind-scrollbar": "^4.0.2", "tailwindcss": "^4.0.9", "typescript": "^5.7.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8485f66ad..3eef86eea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,8 +139,8 @@ importers: specifier: ^4.1.0 version: 4.1.0(prettier@3.5.3)(typescript@5.7.3) tailwind-scrollbar: - specifier: ^4.0.1 - version: 4.0.1(react@18.3.1)(tailwindcss@4.0.9) + specifier: ^4.0.2 + version: 4.0.2(react@18.3.1)(tailwindcss@4.0.9) tailwindcss: specifier: ^4.0.9 version: 4.0.9 @@ -2642,8 +2642,8 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true - tailwind-scrollbar@4.0.1: - resolution: {integrity: sha512-j2ZfUI7p8xmSQdlqaCxEb4Mha8ErvWjDVyu2Ke4IstWprQ/6TmIz1GSLE62vsTlXwnMLYhuvbFbIFzaJGOGtMg==} + tailwind-scrollbar@4.0.2: + resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==} engines: {node: '>=12.13.0'} peerDependencies: tailwindcss: 4.x @@ -5566,7 +5566,7 @@ snapshots: systeminformation@5.25.11: {} - tailwind-scrollbar@4.0.1(react@18.3.1)(tailwindcss@4.0.9): + tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@4.0.9): dependencies: prism-react-renderer: 2.4.1(react@18.3.1) tailwindcss: 4.0.9 From 0e96778740759eb86f7643beee113113c4d1ae75 Mon Sep 17 00:00:00 2001 From: Jon Fuller Date: Wed, 4 Jun 2025 20:29:43 -0700 Subject: [PATCH 05/28] Feature: Trilium Service widget (#5380) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/trilium.md | 17 +++++++++++ public/locales/en/common.json | 6 ++++ src/utils/proxy/handlers/credentialed.js | 2 ++ src/widgets/components.js | 1 + src/widgets/trilium/component.jsx | 38 ++++++++++++++++++++++++ src/widgets/trilium/widget.js | 15 ++++++++++ src/widgets/widgets.js | 2 ++ 7 files changed, 81 insertions(+) create mode 100644 docs/widgets/services/trilium.md create mode 100644 src/widgets/trilium/component.jsx create mode 100644 src/widgets/trilium/widget.js diff --git a/docs/widgets/services/trilium.md b/docs/widgets/services/trilium.md new file mode 100644 index 000000000..003157d20 --- /dev/null +++ b/docs/widgets/services/trilium.md @@ -0,0 +1,17 @@ +--- +title: Trilium +description: Trilium Widget Configuration +--- + +Learn more about [Trilium](https://github.com/TriliumNext/Notes). + +This widget is compatible with [TriliumNext](https://github.com/TriliumNext/Notes) versions >= [v0.94.0](https://github.com/TriliumNext/Notes/releases/tag/v0.94.0). + +Find (or create) your ETAPI key under `Options > ETAPI > Create new ETAPI token`. + +```yaml +widget: + type: trilium + url: https://trilium.host.or.ip + key: etapi_token +``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 87895e6e0..43378ec1f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -359,6 +359,12 @@ "services": "Services", "middleware": "Middleware" }, + "trilium": { + "version": "Version", + "notesCount": "Notes", + "dbSize": "Database Size", + "unknown": "Unknown" + }, "navidrome": { "nothing_streaming": "No Active Streams", "please_wait": "Please Wait" diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index 6eb4f9a95..712273bd6 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -106,6 +106,8 @@ export default async function credentialedProxyHandler(req, res, map) { } else { headers.Authorization = widget.password; } + } else if (widget.type === "trilium") { + headers.Authorization = widget.key; } else if (widget.type === "gitlab") { headers["PRIVATE-TOKEN"] = widget.key; } else if (widget.type === "speedtest") { diff --git a/src/widgets/components.js b/src/widgets/components.js index 71c7f095a..92e057dc9 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -132,6 +132,7 @@ const components = { tdarr: dynamic(() => import("./tdarr/component")), traefik: dynamic(() => import("./traefik/component")), transmission: dynamic(() => import("./transmission/component")), + trilium: dynamic(() => import("./trilium/component")), tubearchivist: dynamic(() => import("./tubearchivist/component")), truenas: dynamic(() => import("./truenas/component")), unifi: dynamic(() => import("./unifi/component")), diff --git a/src/widgets/trilium/component.jsx b/src/widgets/trilium/component.jsx new file mode 100644 index 000000000..132c9a073 --- /dev/null +++ b/src/widgets/trilium/component.jsx @@ -0,0 +1,38 @@ +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + + const { data: metricsData, error: metricsError } = useWidgetAPI(widget, "metrics"); + + if (metricsError) { + return ; + } + + if (!metricsData) { + return ( + + + + + + ); + } + + const version = metricsData.version?.app; + const notesCount = metricsData.database?.activeNotes || 0; + const databaseSizeBytes = metricsData.statistics?.databaseSizeBytes || 0; + + return ( + + + + + + ); +} diff --git a/src/widgets/trilium/widget.js b/src/widgets/trilium/widget.js new file mode 100644 index 000000000..f3bbaccba --- /dev/null +++ b/src/widgets/trilium/widget.js @@ -0,0 +1,15 @@ +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; + +const widget = { + api: "{url}/etapi/{endpoint}", + proxyHandler: credentialedProxyHandler, + + mappings: { + metrics: { + endpoint: "metrics?format=json", + validate: ["version", "database"], + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index b36ee5025..2a2df8c23 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -123,6 +123,7 @@ import tdarr from "./tdarr/widget"; import technitium from "./technitium/widget"; import traefik from "./traefik/widget"; import transmission from "./transmission/widget"; +import trilium from "./trilium/widget"; import truenas from "./truenas/widget"; import tubearchivist from "./tubearchivist/widget"; import unifi from "./unifi/widget"; @@ -266,6 +267,7 @@ const widgets = { tdarr, traefik, transmission, + trilium, tubearchivist, truenas, unifi, From 30abf4e4227607b2b179cc9dfefba36ae71ffa89 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 5 Jun 2025 20:51:31 -0700 Subject: [PATCH 06/28] Add k8s yaml to validate --- src/pages/api/validate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/validate.js b/src/pages/api/validate.js index bab53057c..a0b6c995c 100644 --- a/src/pages/api/validate.js +++ b/src/pages/api/validate.js @@ -1,6 +1,6 @@ import checkAndCopyConfig from "utils/config/config"; -const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml"]; +const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "kubernetes.yaml"]; export default async function handler(req, res) { const errors = configs.map((config) => checkAndCopyConfig(config)).filter((status) => status !== true); From 5759596a371c08c89105c38ce6c249bf7bfdf0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20M=C3=A9doc?= Date: Fri, 6 Jun 2025 16:25:21 +0200 Subject: [PATCH 07/28] Feature: Proxmox status & stats integration (#5385) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/configs/proxmox.md | 79 +++++++++++++++++++++ docs/widgets/services/proxmox.md | 29 +------- src/components/services/item.jsx | 32 +++++++++ src/components/services/proxmox-status.jsx | 65 +++++++++++++++++ src/pages/api/proxmox/stats/[...service].js | 65 +++++++++++++++++ src/pages/api/validate.js | 2 +- src/skeleton/proxmox.yaml | 4 ++ src/utils/config/proxmox.js | 14 ++++ src/widgets/proxmoxvm/component.jsx | 32 +++++++++ 9 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 docs/configs/proxmox.md create mode 100644 src/components/services/proxmox-status.jsx create mode 100644 src/pages/api/proxmox/stats/[...service].js create mode 100644 src/skeleton/proxmox.yaml create mode 100644 src/utils/config/proxmox.js create mode 100644 src/widgets/proxmoxvm/component.jsx diff --git a/docs/configs/proxmox.md b/docs/configs/proxmox.md new file mode 100644 index 000000000..6a210b272 --- /dev/null +++ b/docs/configs/proxmox.md @@ -0,0 +1,79 @@ +--- +title: Proxmox +description: Proxmox Configuration +--- + +The Proxmox connection is configured in the `proxmox.yaml` file. See [Create token](#create-token) section below for details on how to generate the required API token. + +```yaml +url: https://proxmox.host.or.ip:8006 +token: username@pam!Token ID +secret: secret +``` + +## Services + +Once the Proxmox connection is configured, individual services can be configured to pull statistics of VMs or LXCs. Only CPU and Memory are currently supported. + +### Configuration Options + +- `proxmoxNode`: The name of the Proxmox node where your VM/LXC is running +- `proxmoxVMID`: The ID of the Proxmox VM or LXC container +- `proxmoxType`: (Optional) The type of Proxmox virtual machine. Defaults to `qemu` for VMs, but can be set to `lxc` for LXC containers + +#### Examples + +For a QEMU VM (default): + +```yaml +- HomeAssistant: + icon: home-assistant.png + href: http://homeassistant.local/ + description: Home automation + proxmoxNode: pve + proxmoxVMID: 101 + # proxmoxType: qemu # This is the default, so it can be omitted +``` + +For an LXC container: + +```yaml +- Nginx: + icon: nginx.png + href: http://nginx.local/ + description: Web server + proxmoxNode: pve + proxmoxVMID: 200 + proxmoxType: lxc +``` + +## Create token + +You will need to generate an API Token for new or an existing user. Here is an example of how to do this for a new user. + +1. Navigate to the Proxmox portal, click on Datacenter +2. Expand Permissions, click on Groups +3. Click the Create button +4. Name the group something informative, like api-ro-users +5. Click on the Permissions "folder" +6. Click Add -> Group Permission + - Path: / + - Group: group from bullet 4 above + - Role: PVEAuditor + - Propagate: Checked +7. Expand Permissions, click on Users +8. Click the Add button + - User name: something informative like `api` + - Realm: Linux PAM standard authentication + - Group: group from bullet 4 above +9. Expand Permissions, click on API Tokens +10. Click the Add button + - User: user from bullet 8 above + - Token ID: something informative like the application or purpose like `homepage` + - Privilege Separation: Checked +11. Go back to the "Permissions" menu +12. Click Add -> API Token Permission + - Path: / + - API Token: select the Token ID created in Step 10 + - Role: PVE Auditor + - Propagate: Checked diff --git a/docs/widgets/services/proxmox.md b/docs/widgets/services/proxmox.md index 080dc17cb..0673fc235 100644 --- a/docs/widgets/services/proxmox.md +++ b/docs/widgets/services/proxmox.md @@ -7,34 +7,7 @@ Learn more about [Proxmox](https://www.proxmox.com/en/). This widget shows the running and total counts of both QEMU VMs and LX Containers in the Proxmox cluster. It also shows the CPU and memory usage of the first node in the cluster. -You will need to generate an API Token for new or an existing user. Here is an example of how to do this for a new user. - -1. Navigate to the Proxmox portal, click on Datacenter -2. Expand Permissions, click on Groups -3. Click the Create button -4. Name the group something informative, like api-ro-users -5. Click on the Permissions "folder" -6. Click Add -> Group Permission - - Path: / - - Group: group from bullet 4 above - - Role: PVEAuditor - - Propagate: Checked -7. Expand Permissions, click on Users -8. Click the Add button - - User name: something informative like `api` - - Realm: Linux PAM standard authentication - - Group: group from bullet 4 above -9. Expand Permissions, click on API Tokens -10. Click the Add button - - User: user from bullet 8 above - - Token ID: something informative like the application or purpose like `homepage` - - Privilege Separation: Checked -11. Go back to the "Permissions" menu -12. Click Add -> API Token Permission - - Path: / - - API Token: select the Token ID created in Step 10 - - Role: PVE Auditor - - Propagate: Checked +See the [Proxmox configuration documentation](../../configs/proxmox.md#create-token) for details on creating API tokens. Use `username@pam!Token ID` as the `username` (e.g `api@pam!homepage`) setting and `Secret` as the `password` setting. diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 8e8516117..c23e16a23 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -4,9 +4,11 @@ import { useContext, useState } from "react"; import { SettingsContext } from "utils/contexts/settings"; import Docker from "widgets/docker/component"; import Kubernetes from "widgets/kubernetes/component"; +import ProxmoxVM from "widgets/proxmoxvm/component"; import KubernetesStatus from "./kubernetes-status"; import Ping from "./ping"; +import ProxmoxStatus from "./proxmox-status"; import SiteMonitor from "./site-monitor"; import Status from "./status"; import Widget from "./widget"; @@ -121,6 +123,16 @@ export default function Item({ service, groupName, useEqualHeights }) { View container stats )} + {service.proxmoxNode && service.proxmoxVMID && ( + + )} @@ -152,6 +164,26 @@ export default function Item({ service, groupName, useEqualHeights }) { )} )} + {service.proxmoxNode && service.proxmoxVMID && ( +
+ {(showStats || statsOpen) && ( + + )} +
+ )} {service.widgets.map((widget) => ( diff --git a/src/components/services/proxmox-status.jsx b/src/components/services/proxmox-status.jsx new file mode 100644 index 000000000..c4ddecb9b --- /dev/null +++ b/src/components/services/proxmox-status.jsx @@ -0,0 +1,65 @@ +import { useTranslation } from "next-i18next"; +import useSWR from "swr"; + +export default function ProxmoxStatus({ service, style }) { + const { t } = useTranslation(); + + const vmType = service.proxmoxType || "qemu"; + const apiUrl = `/api/proxmox/stats/${service.proxmoxNode}/${service.proxmoxVMID}?type=${vmType}`; + + const { data, error } = useSWR(apiUrl); + + let statusLabel = t("docker.unknown"); + let backgroundClass = "px-1.5 py-0.5 bg-theme-500/10 dark:bg-theme-900/50"; + let colorClass = "text-black/20 dark:text-white/40 "; + + if (error) { + statusLabel = t("docker.error"); + colorClass = "text-rose-500/80"; + } else if (data) { + if (data.status === "running") { + statusLabel = t("docker.running"); + colorClass = "text-emerald-500/80"; + } + + if (data.status === "stopped") { + statusLabel = t("docker.exited"); + colorClass = "text-orange-400/50 dark:text-orange-400/80"; + } + + if (data.status === "paused") { + statusLabel = "paused"; + colorClass = "text-blue-500/80"; + } + + if (data.status === "offline") { + statusLabel = "offline"; + colorClass = "text-orange-400/50 dark:text-orange-400/80"; + } + + if (data.status === "not found") { + statusLabel = t("docker.not_found"); + colorClass = "text-orange-400/50 dark:text-orange-400/80"; + } + } + + if (style === "dot") { + colorClass = colorClass.replace(/text-/g, "bg-").replace(/\/\d\d/g, ""); + backgroundClass = "p-4 hover:bg-theme-500/10 dark:hover:bg-theme-900/20"; + } + + return ( +
+ {style !== "dot" ? ( +
{statusLabel}
+ ) : ( +
+ )} +
+ ); +} diff --git a/src/pages/api/proxmox/stats/[...service].js b/src/pages/api/proxmox/stats/[...service].js new file mode 100644 index 000000000..96cdb5019 --- /dev/null +++ b/src/pages/api/proxmox/stats/[...service].js @@ -0,0 +1,65 @@ +import { getProxmoxConfig } from "utils/config/proxmox"; +import createLogger from "utils/logger"; +import { httpProxy } from "utils/proxy/http"; + +const logger = createLogger("proxmoxStatsService"); + +export default async function handler(req, res) { + const { service, type: vmType } = req.query; + + const [node, vmid] = service; + + if (!node) { + return res.status(400).send({ + error: "Proxmox node parameter is required", + }); + } + + try { + const proxmoxConfig = getProxmoxConfig(); + + if (!proxmoxConfig) { + return res.status(500).send({ + error: "Proxmox server configuration not found", + }); + } + + const baseUrl = `${proxmoxConfig.url}/api2/json`; + const headers = { + Authorization: `PVEAPIToken=${proxmoxConfig.token}=${proxmoxConfig.secret}`, + }; + + const statusUrl = `${baseUrl}/nodes/${node}/${vmType}/${vmid}/status/current`; + + const [status, , data] = await httpProxy(statusUrl, { + method: "GET", + headers, + }); + + if (status !== 200) { + logger.error("HTTP Error %d calling Proxmox API", status); + return res.status(status).send({ + error: `Failed to fetch Proxmox ${vmType} status`, + }); + } + + let parsedData = JSON.parse(Buffer.from(data).toString()); + + if (!parsedData || !parsedData.data) { + return res.status(500).send({ + error: "Invalid response from Proxmox API", + }); + } + + return res.status(200).json({ + status: parsedData.data.status || "unknown", + cpu: parsedData.data.cpu, + mem: parsedData.data.mem, + }); + } catch (error) { + logger.error("Error fetching Proxmox status:", error); + return res.status(500).send({ + error: "Failed to fetch Proxmox status", + }); + } +} diff --git a/src/pages/api/validate.js b/src/pages/api/validate.js index a0b6c995c..803d22827 100644 --- a/src/pages/api/validate.js +++ b/src/pages/api/validate.js @@ -1,6 +1,6 @@ import checkAndCopyConfig from "utils/config/config"; -const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "kubernetes.yaml"]; +const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml", "kubernetes.yaml", "proxmox.yaml"]; export default async function handler(req, res) { const errors = configs.map((config) => checkAndCopyConfig(config)).filter((status) => status !== true); diff --git a/src/skeleton/proxmox.yaml b/src/skeleton/proxmox.yaml new file mode 100644 index 000000000..9fd1836ac --- /dev/null +++ b/src/skeleton/proxmox.yaml @@ -0,0 +1,4 @@ +--- +# url: https://proxmox.host.or.ip:8006 +# token: username@pam!Token ID +# secret: secret diff --git a/src/utils/config/proxmox.js b/src/utils/config/proxmox.js new file mode 100644 index 000000000..c8b809a69 --- /dev/null +++ b/src/utils/config/proxmox.js @@ -0,0 +1,14 @@ +import { readFileSync } from "fs"; +import path from "path"; + +import yaml from "js-yaml"; + +import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config"; + +export function getProxmoxConfig() { + checkAndCopyConfig("proxmox.yaml"); + const configFile = path.join(CONF_DIR, "proxmox.yaml"); + const rawConfigData = readFileSync(configFile, "utf8"); + const configData = substituteEnvironmentVars(rawConfigData); + return yaml.load(configData); +} diff --git a/src/widgets/proxmoxvm/component.jsx b/src/widgets/proxmoxvm/component.jsx new file mode 100644 index 000000000..1356b8cf3 --- /dev/null +++ b/src/widgets/proxmoxvm/component.jsx @@ -0,0 +1,32 @@ +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; +import useSWR from "swr"; + +export default function ProxmoxVM({ service }) { + const { t } = useTranslation(); + + const { widget } = service; + + const { data, error } = useSWR(`/api/proxmox/stats/${widget.node}/${widget.vmid}?type=${widget.type || "qemu"}`); + + if (error) { + return ; + } + + if (!data) { + return ( + + + + + ); + } + + return ( + + + + + ); +} From 34bffe980a5e93d56529401d25dadde5f7b298c8 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 7 Jun 2025 15:49:07 -0700 Subject: [PATCH 08/28] Change: use glances rss instead of data for process memory reporting (#5392) --- src/widgets/glances/metrics/process.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/glances/metrics/process.jsx b/src/widgets/glances/metrics/process.jsx index ad3fee543..333c8a344 100644 --- a/src/widgets/glances/metrics/process.jsx +++ b/src/widgets/glances/metrics/process.jsx @@ -23,7 +23,7 @@ export default function Component({ service }) { const { widget } = service; const { chart, refreshInterval = defaultInterval, version = 3 } = widget; - const memoryInfoKey = version === 3 ? 0 : "data"; + const memoryInfoKey = version === 3 ? 0 : "rss"; const { data, error } = useWidgetAPI(service.widget, `${version}/processlist`, { refreshInterval: Math.max(defaultInterval, refreshInterval), @@ -69,7 +69,7 @@ export default function Component({ service }) {
{item.cpu_percent.toFixed(1)}%
{t("common.bytes", { - value: item.memory_info[memoryInfoKey] ?? item.memory_info.wset, + value: item.memory_info[memoryInfoKey] ?? item.memory_info.data ?? item.memory_info.wset, maximumFractionDigits: 0, })}
From fcfb9c2237c053e1cd1e52a020b2e43c97181960 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 8 Jun 2025 06:51:17 -0700 Subject: [PATCH 09/28] Fix typo in k8s error logging --- src/pages/api/widgets/kubernetes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/widgets/kubernetes.js b/src/pages/api/widgets/kubernetes.js index 583f6f15b..7f972e224 100644 --- a/src/pages/api/widgets/kubernetes.js +++ b/src/pages/api/widgets/kubernetes.js @@ -67,7 +67,7 @@ export default async function handler(req, res) { nodeMap[nodeMetric.metadata.name].memory.percent = (mem / nodeMap[nodeMetric.metadata.name].memory.total) * 100; }); } catch (error) { - logger.error("Error getting metrics, ensure you have metrics-server installed: s", JSON.stringify(error)); + logger.error("Error getting metrics, ensure you have metrics-server installed:", JSON.stringify(error)); return res.status(500).send({ error: "Error getting metrics, check logs for more details", }); From a64c83209af007f9a6a58d1150499e9f94386aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCrger?= <32954283+andrebuerger@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:41:48 +0200 Subject: [PATCH 10/28] Enhancement: add enableMediaControl flag to hide play/pause controls for emby/jellyfin (#5402) --- docs/widgets/services/emby.md | 1 + docs/widgets/services/jellyfin.md | 1 + src/utils/config/service-helpers.js | 2 ++ src/widgets/emby/component.jsx | 15 +++++++++------ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/widgets/services/emby.md b/docs/widgets/services/emby.md index e658d73b7..5027d408d 100644 --- a/docs/widgets/services/emby.md +++ b/docs/widgets/services/emby.md @@ -17,6 +17,7 @@ widget: enableBlocks: true # optional, defaults to false enableNowPlaying: true # optional, defaults to true enableUser: true # optional, defaults to false + enableMediaControl: false # optional, defaults to true showEpisodeNumber: true # optional, defaults to false expandOneStreamToTwoRows: false # optional, defaults to true ``` diff --git a/docs/widgets/services/jellyfin.md b/docs/widgets/services/jellyfin.md index b6724a152..667930e3c 100644 --- a/docs/widgets/services/jellyfin.md +++ b/docs/widgets/services/jellyfin.md @@ -17,6 +17,7 @@ widget: enableBlocks: true # optional, defaults to false enableNowPlaying: true # optional, defaults to true enableUser: true # optional, defaults to false + enableMediaControl: false # optional, defaults to true showEpisodeNumber: true # optional, defaults to false expandOneStreamToTwoRows: false # optional, defaults to true ``` diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 3e343cbdf..79e3a489b 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -295,6 +295,7 @@ export function cleanServiceGroups(groups) { // emby, jellyfin enableBlocks, enableNowPlaying, + enableMediaControl, // emby, jellyfin, tautulli enableUser, @@ -471,6 +472,7 @@ export function cleanServiceGroups(groups) { if (wan) widget.wan = wan; } if (["emby", "jellyfin"].includes(type)) { + if (enableMediaControl !== undefined) widget.enableMediaControl = !!JSON.parse(enableMediaControl); if (enableBlocks !== undefined) widget.enableBlocks = JSON.parse(enableBlocks); if (enableNowPlaying !== undefined) widget.enableNowPlaying = JSON.parse(enableNowPlaying); } diff --git a/src/widgets/emby/component.jsx b/src/widgets/emby/component.jsx index 88858da2a..0eaff8291 100644 --- a/src/widgets/emby/component.jsx +++ b/src/widgets/emby/component.jsx @@ -45,7 +45,7 @@ function generateStreamTitle(session, enableUser, showEpisodeNumber) { return enableUser ? `${streamTitle} (${UserName})` : streamTitle; } -function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) { +function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumber, enableMediaControl }) { const { PlayState: { PositionTicks, IsPaused, IsMuted }, } = session; @@ -85,7 +85,7 @@ function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumbe }} />
- {IsPaused && ( + {enableMediaControl && IsPaused && ( { playCommand(session, "Unpause"); @@ -93,7 +93,7 @@ function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumbe className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" /> )} - {!IsPaused && ( + {enableMediaControl && !IsPaused && ( { playCommand(session, "Pause"); @@ -114,7 +114,7 @@ function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumbe ); } -function SessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) { +function SessionEntry({ playCommand, session, enableUser, showEpisodeNumber, enableMediaControl }) { const { PlayState: { PositionTicks, IsPaused, IsMuted }, } = session; @@ -139,7 +139,7 @@ function SessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) { }} />
- {IsPaused && ( + {enableMediaControl && IsPaused && ( { playCommand(session, "Unpause"); @@ -147,7 +147,7 @@ function SessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) { className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" /> )} - {!IsPaused && ( + {enableMediaControl && !IsPaused && ( { playCommand(session, "Pause"); @@ -238,6 +238,7 @@ export default function Component({ service }) { const enableBlocks = service.widget?.enableBlocks; const enableNowPlaying = service.widget?.enableNowPlaying ?? true; + const enableMediaControl = service.widget?.enableMediaControl !== false; // default is true const enableUser = !!service.widget?.enableUser; // default is false const expandOneStreamToTwoRows = service.widget?.expandOneStreamToTwoRows !== false; // default is true const showEpisodeNumber = !!service.widget?.showEpisodeNumber; // default is false @@ -304,6 +305,7 @@ export default function Component({ service }) { session={session} enableUser={enableUser} showEpisodeNumber={showEpisodeNumber} + enableMediaControl={enableMediaControl} />
@@ -321,6 +323,7 @@ export default function Component({ service }) { session={session} enableUser={enableUser} showEpisodeNumber={showEpisodeNumber} + enableMediaControl={enableMediaControl} /> ))}
From 6e16adc460909c90b479070700ce55b58dbd1ea1 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:02:48 -0700 Subject: [PATCH 11/28] Change: preserve trailing slash in custom api calls (#5410) --- src/utils/proxy/handlers/generic.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index 1914114c2..166dc145c 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -19,9 +19,11 @@ export default async function genericProxyHandler(req, res, map) { if (widget) { // if there are more than one question marks, replace others to & - const url = new URL( - formatApiCall(widgets[widget.type].api, { endpoint, ...widget }).replace(/(?<=\?.*)\?/g, "&"), - ); + let urlString = formatApiCall(widgets[widget.type].api, { endpoint, ...widget }).replace(/(?<=\?.*)\?/g, "&"); + if (widget.type === "customapi" && widget.url?.endsWith("/")) { + urlString += "/"; // Ensure we dont lose the trailing slash for custom API calls + } + const url = new URL(urlString); const headers = req.extraHeaders ?? widget.headers ?? widgets[widget.type].headers ?? {}; From 8ce9e57ed80691e5ce3b19a8ac496f4f968d959d Mon Sep 17 00:00:00 2001 From: Andrew Ensley Date: Wed, 11 Jun 2025 12:05:53 -0400 Subject: [PATCH 12/28] Enhancement: Komodo widget (#5407) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/komodo.md | 22 ++++++++ public/locales/en/common.json | 11 ++++ src/utils/config/service-helpers.js | 8 +++ src/widgets/components.js | 1 + src/widgets/komodo/component.jsx | 84 +++++++++++++++++++++++++++++ src/widgets/komodo/proxy.js | 55 +++++++++++++++++++ src/widgets/komodo/widget.js | 32 +++++++++++ src/widgets/widgets.js | 2 + 8 files changed, 215 insertions(+) create mode 100644 docs/widgets/services/komodo.md create mode 100644 src/widgets/komodo/component.jsx create mode 100644 src/widgets/komodo/proxy.js create mode 100644 src/widgets/komodo/widget.js diff --git a/docs/widgets/services/komodo.md b/docs/widgets/services/komodo.md new file mode 100644 index 000000000..9eb93de86 --- /dev/null +++ b/docs/widgets/services/komodo.md @@ -0,0 +1,22 @@ +--- +title: Komodo +description: Komodo Widget Configuration +--- + +This widget shows either details about all containers or stacks (if `showStacks` is true) managed by [Komodo](https://komo.do/) or the number of running servers, containers and stacks when `showSummary` is enabled. + +The api key and secret can be found in the Komodo settings. + +Allowed fields (max 4): `["total", "running", "stopped", "unhealthy", "unknown"]`. +Allowed fields with `showStacks` (max 4): `["total", "running", "down", "unhealthy", "unknown"]`. +Allowed fields with `showSummary`: `["servers", "stacks", "containers"]`. + +```yaml +widget: + type: komodo + url: http://komodo.hostname.or.ip:port + key: K-xxxxxx... + secret: S-xxxxxx... + showSummary: true # optional, default: false + showStacks: true # optional, default: false +``` diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 43378ec1f..07c12d5a8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1060,5 +1060,16 @@ "checkmk": { "serviceErrors": "Service issues", "hostErrors": "Host issues" + }, + "komodo": { + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "down": "Down", + "unhealthy": "Unhealthy", + "unknown": "Unknown", + "servers": "Servers", + "stacks": "Stacks", + "containers": "Containers" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 79e3a489b..d46c86057 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -338,6 +338,10 @@ export function cleanServiceGroups(groups) { // jellystat days, + // komodo + showSummary, + showStacks, + // kopia snapshotHost, snapshotPath, @@ -450,6 +454,10 @@ export function cleanServiceGroups(groups) { if (type === "proxmoxbackupserver") { if (datastore) widget.datastore = datastore; } + if (type === "komodo") { + if (showSummary !== undefined) widget.showSummary = !!JSON.parse(showSummary); + if (showStacks !== undefined) widget.showStacks = !!JSON.parse(showStacks); + } if (type === "kubernetes") { if (namespace) widget.namespace = namespace; if (app) widget.app = app; diff --git a/src/widgets/components.js b/src/widgets/components.js index 92e057dc9..b652c2d86 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -63,6 +63,7 @@ const components = { jellystat: dynamic(() => import("./jellystat/component")), kavita: dynamic(() => import("./kavita/component")), komga: dynamic(() => import("./komga/component")), + komodo: dynamic(() => import("./komodo/component")), kopia: dynamic(() => import("./kopia/component")), lidarr: dynamic(() => import("./lidarr/component")), linkwarden: dynamic(() => import("./linkwarden/component")), diff --git a/src/widgets/komodo/component.jsx b/src/widgets/komodo/component.jsx new file mode 100644 index 000000000..4d22758e5 --- /dev/null +++ b/src/widgets/komodo/component.jsx @@ -0,0 +1,84 @@ +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "utils/proxy/use-widget-api"; + +const MAX_ALLOWED_FIELDS = 4; + +export default function Component({ service }) { + const { t } = useTranslation(); + const { widget } = service; + const containersEndpoint = !(!widget.showSummary && widget.showStacks) ? "containers" : ""; + const { data: containersData, error: containersError } = useWidgetAPI(widget, containersEndpoint); + const stacksEndpoint = widget.showSummary || widget.showStacks ? "stacks" : ""; + const { data: stacksData, error: stacksError } = useWidgetAPI(widget, stacksEndpoint); + const serversEndpoint = widget.showSummary ? "servers" : ""; + const { data: serversData, error: serversError } = useWidgetAPI(widget, serversEndpoint); + + if (containersError || stacksError || serversError) { + return ; + } + + if (!widget.fields || widget.fields.length === 0) { + widget.fields = widget.showSummary + ? ["servers", "stacks", "containers"] + : widget.showStacks + ? ["total", "running", "down", "unhealthy"] + : ["total", "running", "stopped", "unhealthy"]; + } else if (widget.fields?.length > MAX_ALLOWED_FIELDS) { + widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS); + } + + if ( + (!widget.showStacks && !containersData) || + (widget.showSummary && (!stacksData || !serversData)) || + (widget.showStacks && !stacksData) + ) { + return widget.showSummary ? ( + + + + + + ) : widget.showStacks ? ( + + + + + + + ) : ( + + + + + + + ); + } + + return widget.showSummary ? ( + + + + + + ) : widget.showStacks ? ( + + + + + + + + ) : ( + + + + + + + + ); +} diff --git a/src/widgets/komodo/proxy.js b/src/widgets/komodo/proxy.js new file mode 100644 index 000000000..f3c86b2e6 --- /dev/null +++ b/src/widgets/komodo/proxy.js @@ -0,0 +1,55 @@ +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import validateWidgetData from "utils/proxy/validate-widget-data"; +import widgets from "widgets/widgets"; + +const logger = createLogger("komodoProxyHandler"); + +export default async function komodoProxyHandler(req, res) { + const { group, service, endpoint, index } = req.query; + + if (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" }); + } + + if (widget) { + // api uses unified read endpoint + const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint: "read", ...widget })).toString(); + + const headers = { + "Content-Type": "application/json", + "X-API-Key": `${widget.key}`, + "X-API-Secret": `${widget.secret}`, + }; + const [status, contentType, data] = await httpProxy(url, { + method: "POST", + body: JSON.stringify(widgets[widget.type].mappings?.[endpoint]?.body || {}), + headers, + }); + + let resultData = data; + + if (status >= 400) { + logger.error("HTTP Error %d calling %s", status, sanitizeErrorURL(url)); + } + + if (status === 200) { + if (!validateWidgetData(widget, endpoint, resultData)) { + return res + .status(500) + .json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } }); + } + } + + if (contentType) res.setHeader("Content-Type", contentType); + return res.status(status).send(resultData); + } + } + + logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group); + return res.status(400).json({ error: "Invalid proxy service type" }); +} diff --git a/src/widgets/komodo/widget.js b/src/widgets/komodo/widget.js new file mode 100644 index 000000000..55454f97c --- /dev/null +++ b/src/widgets/komodo/widget.js @@ -0,0 +1,32 @@ +import komodoProxyHandler from "./proxy"; + +const widget = { + api: "{url}/{endpoint}", + proxyHandler: komodoProxyHandler, + + mappings: { + containers: { + endpoint: "containers", // api actually uses unified read endpoint + body: { + type: "GetDockerContainersSummary", + params: {}, + }, + }, + stacks: { + endpoint: "stacks", // api actually uses unified read endpoint + body: { + type: "GetStacksSummary", + params: {}, + }, + }, + servers: { + endpoint: "servers", // api actually uses unified read endpoint + body: { + type: "GetServersSummary", + params: {}, + }, + }, + }, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 2a2df8c23..8178f26ba 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -54,6 +54,7 @@ import jellystat from "./jellystat/widget"; import karakeep from "./karakeep/widget"; import kavita from "./kavita/widget"; import komga from "./komga/widget"; +import komodo from "./komodo/widget"; import kopia from "./kopia/widget"; import lidarr from "./lidarr/widget"; import linkwarden from "./linkwarden/widget"; @@ -197,6 +198,7 @@ const widgets = { jellystat, kavita, komga, + komodo, kopia, lidarr, linkwarden, From 73aa2018a8ff68dd828687b946aaccc5a9fd7105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20M=C3=A9doc?= Date: Fri, 13 Jun 2025 09:17:20 +0200 Subject: [PATCH 13/28] Enhancement: add kubernetes support to portainer widget (#5414) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/portainer.md | 6 ++- src/utils/config/service-helpers.js | 6 +++ src/widgets/portainer/component.jsx | 67 +++++++++++++++++++++++++---- src/widgets/portainer/widget.js | 15 +++++-- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/docs/widgets/services/portainer.md b/docs/widgets/services/portainer.md index f18d4eec4..d5e6380e3 100644 --- a/docs/widgets/services/portainer.md +++ b/docs/widgets/services/portainer.md @@ -7,12 +7,16 @@ Learn more about [Portainer](https://github.com/portainer/portainer). You'll need to make sure you have the correct environment set for the integration to work properly. From the Environments section inside of Portainer, click the one you'd like to connect to and observe the ID at the end of the URL (should be), something like `#!/endpoints/1`, here `1` is the value to set as the `env` value. In order to generate an API key, please follow the steps outlined here https://docs.portainer.io/api/access. -Allowed fields: `["running", "stopped", "total"]`. +Allowed fields: + +- For Docker mode (default): `["running", "stopped", "total"]` +- For Kubernetes mode (`kubernetes: true`): `["applications", "services", "namespaces"]` ```yaml widget: type: portainer url: https://portainer.host.or.ip:9443 env: 1 + kubernetes: true # optional, defaults to false key: ptr_accesskeyaccesskeyaccesskeyaccesskey ``` diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index d46c86057..68de39449 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -367,6 +367,9 @@ export function cleanServiceGroups(groups) { // opnsense, pfsense wan, + // portainer + kubernetes, + // prometheusmetric metrics, @@ -448,6 +451,9 @@ export function cleanServiceGroups(groups) { if (type === "unifi") { if (site) widget.site = site; } + if (type === "portainer") { + if (kubernetes) widget.kubernetes = !!JSON.parse(kubernetes); + } if (type === "proxmox") { if (node) widget.node = node; } diff --git a/src/widgets/portainer/component.jsx b/src/widgets/portainer/component.jsx index f8a89507d..d141bc306 100644 --- a/src/widgets/portainer/component.jsx +++ b/src/widgets/portainer/component.jsx @@ -6,15 +6,64 @@ import useWidgetAPI from "utils/proxy/use-widget-api"; export default function Component({ service }) { const { widget } = service; - const { data: containersData, error: containersError } = useWidgetAPI(widget, "docker/containers/json", { - all: 1, - }); + if (!widget.fields) { + widget.fields = widget.kubernetes ? ["applications", "services", "namespaces"] : ["running", "stopped", "total"]; + } + + const { data: containersCount, error: containersError } = useWidgetAPI( + widget, + widget.kubernetes ? "" : "docker/containers", + { + all: 1, + }, + ); + + const { data: applicationsCount, error: applicationsError } = useWidgetAPI( + widget, + widget.kubernetes ? "kubernetes/applications" : "", + ); + + const { data: servicesCount, error: servicesError } = useWidgetAPI( + widget, + widget.kubernetes ? "kubernetes/services" : "", + ); + + const { data: namespacesCount, error: namespacesError } = useWidgetAPI( + widget, + widget.kubernetes ? "kubernetes/namespaces" : "", + ); + + if (widget.kubernetes) { + // count can be an error object + const error = applicationsError ?? servicesError ?? namespacesError ?? applicationsCount; + if (error) { + return ; + } + + if (applicationsCount == undefined || servicesCount == undefined || namespacesCount == undefined) { + return ( + + + + + + ); + } + + return ( + + + + + + ); + } if (containersError) { return ; } - if (!containersData) { + if (!containersCount) { return ( @@ -24,14 +73,14 @@ export default function Component({ service }) { ); } - if (containersData.error || containersData.message) { + if (containersCount.error || containersCount.message) { // containersData can be itself an error object e.g. if environment fails - return ; + return ; } - const running = containersData.filter((c) => c.State === "running").length; - const stopped = containersData.filter((c) => c.State === "exited").length; - const total = containersData.length; + const running = containersCount.filter((c) => c.State === "running").length; + const stopped = containersCount.filter((c) => c.State === "exited").length; + const total = containersCount.length; return ( diff --git a/src/widgets/portainer/widget.js b/src/widgets/portainer/widget.js index ca3d5bb0b..c47c88377 100644 --- a/src/widgets/portainer/widget.js +++ b/src/widgets/portainer/widget.js @@ -1,14 +1,23 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { - api: "{url}/api/endpoints/{env}/{endpoint}", + api: "{url}/api/{endpoint}", proxyHandler: credentialedProxyHandler, mappings: { - "docker/containers/json": { - endpoint: "docker/containers/json", + "docker/containers": { + endpoint: "endpoints/{env}/docker/containers/json", params: ["all"], }, + "kubernetes/applications": { + endpoint: "kubernetes/{env}/applications/count", + }, + "kubernetes/services": { + endpoint: "kubernetes/{env}/services/count", + }, + "kubernetes/namespaces": { + endpoint: "kubernetes/{env}/namespaces/count", + }, }, }; From 00bb1a3f37940a0c3c681c3eef0a10d3e1fa0053 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:18:26 -0700 Subject: [PATCH 14/28] Enhancement: improve glances info widget spacing (#5425) --- src/widgets/glances/metrics/info.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/widgets/glances/metrics/info.jsx b/src/widgets/glances/metrics/info.jsx index 85a7de5b5..0a3edcaa2 100644 --- a/src/widgets/glances/metrics/info.jsx +++ b/src/widgets/glances/metrics/info.jsx @@ -112,7 +112,7 @@ export default function Component({ service }) {
)} - + {quicklookData && quicklookData.cpu_name && chart && (
{quicklookData.cpu_name}
)} @@ -124,7 +124,7 @@ export default function Component({ service }) {
)} -
{!chart && }
+
{!chart && }
{chart && ( @@ -136,18 +136,18 @@ export default function Component({ service }) { )} {!chart && ( - - + + )} - - {chart && } + + {chart && } - {chart && } - {!chart && } + {chart && } + {!chart && } - {chart && } + {chart && }
); From 1a1039194c81fba778a2fe6cf9ade0f296ef6303 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:48:34 -0700 Subject: [PATCH 15/28] Move up glacnes info background --- src/widgets/glances/metrics/info.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/glances/metrics/info.jsx b/src/widgets/glances/metrics/info.jsx index 0a3edcaa2..6b875c06c 100644 --- a/src/widgets/glances/metrics/info.jsx +++ b/src/widgets/glances/metrics/info.jsx @@ -109,7 +109,7 @@ export default function Component({ service }) { return ( {chart && ( -
+
)} From e51db2441892e6d60f123e4e66131ce869432227 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:06:30 -0700 Subject: [PATCH 16/28] Update launch configuration --- .vscode/launch.json | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 366b54388..372a44597 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,19 +1,31 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", "configurations": [ { - "name": "Next.js: debug full stack", + "name": "Debug homepage", "type": "node", "request": "launch", - "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/next", - "serverReadyAction": { - "pattern": "started server on .+, url: (https?://.+)", - "uriFormat": "%s", - "action": "debugWithChrome" + "runtimeExecutable": "pnpm", + "runtimeArgs": ["run", "dev"], + "env": { + "LOG_LEVEL": "debug" + }, + "skipFiles": ["/**"], + "console": "integratedTerminal", + "serverReadyAction":{ + "pattern": ".*http://localhost:3000.*", + "action": "startDebugging", + "name": "Launch Chromium", + "killOnServerStop": true, } + }, + { + "name": "Launch Chromium", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000", + "urlFilter": "http://localhost:3000", + "webRoot": "${workspaceFolder}", + "trace": true } ] } From 0fe76b5af89993ecc54247645adf83235ffaf830 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:30:34 -0700 Subject: [PATCH 17/28] Enhancement: include ua for outlook ical integrations (#5444) --- src/widgets/calendar/proxy.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/widgets/calendar/proxy.js b/src/widgets/calendar/proxy.js index c98441eea..7330a97fa 100644 --- a/src/widgets/calendar/proxy.js +++ b/src/widgets/calendar/proxy.js @@ -16,7 +16,14 @@ export default async function calendarProxyHandler(req, res) { return res.status(403).json({ error: "No integration URL specified" }); } - const [status, contentType, data] = await httpProxy(integration.url); + const options = {}; + if (integration.url?.includes("outlook")) { + // Outlook requires a user agent header + options.headers = { + "User-Agent": `gethomepage/${process.env.NEXT_PUBLIC_VERSION || "dev"}`, + }; + } + const [status, contentType, data] = await httpProxy(integration.url, options); if (contentType) res.setHeader("Content-Type", contentType); From 0a44a2dadece2d1f27d68772eae04567a4348014 Mon Sep 17 00:00:00 2001 From: Mark McKinney <3046690+mpmckinney@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:30:00 -0700 Subject: [PATCH 18/28] Enhancement: add links to sonarr and radarr calendar widget items (#5448) --- docs/widgets/services/calendar.md | 1 + src/widgets/calendar/event.jsx | 29 +++++++++++++++----- src/widgets/calendar/integrations/ical.jsx | 2 ++ src/widgets/calendar/integrations/radarr.jsx | 4 +++ src/widgets/calendar/integrations/sonarr.jsx | 1 + 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/widgets/services/calendar.md b/docs/widgets/services/calendar.md index bb8b5016c..cce981dc0 100644 --- a/docs/widgets/services/calendar.md +++ b/docs/widgets/services/calendar.md @@ -22,6 +22,7 @@ widget: service_group: Media # group name where widget exists service_name: Sonarr # service name for that widget color: teal # optional - defaults to pre-defined color for the service (teal for sonarr) + baseUrl: https://sonarr.domain.url # optional - adds links to sonarr/radarr pages params: # optional - additional params for the service unmonitored: true # optional - defaults to false, used with *arr stack - type: ical # Show calendar events from another service diff --git a/src/widgets/calendar/event.jsx b/src/widgets/calendar/event.jsx index 6ea2e1aea..ff06b32be 100644 --- a/src/widgets/calendar/event.jsx +++ b/src/widgets/calendar/event.jsx @@ -8,13 +8,8 @@ export default function Event({ event, colorVariants, showDate = false, showTime const [hover, setHover] = useState(false); const { i18n } = useTranslation(); - return ( -
setHover(!hover)} - onMouseLeave={() => setHover(!hover)} - key={`event-${event.title}-${event.date}-${event.additional}`} - > + const children = ( + <> {showDateColumn && ( @@ -36,6 +31,26 @@ export default function Event({ event, colorVariants, showDate = false, showTime )} + + ); + const className = + "flex flex-row text-theme-700 dark:text-theme-200 items-center text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1"; + const key = `event-${event.title}-${event.date}-${event.additional}`; + return event.url ? ( + setHover(!hover)} + onMouseLeave={() => setHover(!hover)} + key={key} + href={event.url} + target="_blank" + rel="noopener noreferrer" + > + {children} + + ) : ( +
setHover(!hover)} onMouseLeave={() => setHover(!hover)} key={key}> + {children}
); } diff --git a/src/widgets/calendar/integrations/ical.jsx b/src/widgets/calendar/integrations/ical.jsx index 3a3309e6c..764e57f41 100644 --- a/src/widgets/calendar/integrations/ical.jsx +++ b/src/widgets/calendar/integrations/ical.jsx @@ -54,6 +54,7 @@ export default function Integration({ config, params, setEvents, hideErrors, tim ICAL.Time.now(), // handles events without a date location: event.getFirstPropertyValue("location"), status: event.getFirstPropertyValue("status"), + url: event.getFirstPropertyValue("url"), }; }; @@ -133,6 +134,7 @@ export default function Integration({ config, params, setEvents, hideErrors, tim isCompleted: getIsCompleted(), additional: event.location, type: "ical", + url: event.url, }; }); }); diff --git a/src/widgets/calendar/integrations/radarr.jsx b/src/widgets/calendar/integrations/radarr.jsx index 9c8880a90..9cb38c0d2 100644 --- a/src/widgets/calendar/integrations/radarr.jsx +++ b/src/widgets/calendar/integrations/radarr.jsx @@ -22,6 +22,7 @@ export default function Integration({ config, params, setEvents, hideErrors = fa const cinemaTitle = `${event.title} - ${t("calendar.inCinemas")}`; const physicalTitle = `${event.title} - ${t("calendar.physicalRelease")}`; const digitalTitle = `${event.title} - ${t("calendar.digitalRelease")}`; + const url = config?.baseUrl && event.titleSlug && `${config.baseUrl}/movie/${event.titleSlug}`; if (event.inCinemas) { eventsToAdd[cinemaTitle] = { @@ -30,6 +31,7 @@ export default function Integration({ config, params, setEvents, hideErrors = fa color: config?.color ?? "amber", isCompleted: event.hasFile, additional: "", + url, }; } @@ -40,6 +42,7 @@ export default function Integration({ config, params, setEvents, hideErrors = fa color: config?.color ?? "cyan", isCompleted: event.hasFile, additional: "", + url, }; } @@ -50,6 +53,7 @@ export default function Integration({ config, params, setEvents, hideErrors = fa color: config?.color ?? "emerald", isCompleted: event.hasFile, additional: "", + url, }; } }); diff --git a/src/widgets/calendar/integrations/sonarr.jsx b/src/widgets/calendar/integrations/sonarr.jsx index abdec3287..a489f6943 100644 --- a/src/widgets/calendar/integrations/sonarr.jsx +++ b/src/widgets/calendar/integrations/sonarr.jsx @@ -29,6 +29,7 @@ export default function Integration({ config, params, setEvents, hideErrors = fa color: config?.color ?? "teal", isCompleted: event.hasFile, additional: `S${event.seasonNumber} E${event.episodeNumber}`, + url: config?.baseUrl && event.series.titleSlug && `${config.baseUrl}/series/${event.series.titleSlug}`, }; }); From e215e7d70e66dd4dc2053ba41eb702200b9d7419 Mon Sep 17 00:00:00 2001 From: AdamWHY2K Date: Sat, 28 Jun 2025 22:23:02 +0100 Subject: [PATCH 19/28] Enhancement: sort qBittorrent leechProgress (#5456) --- src/widgets/qbittorrent/component.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/widgets/qbittorrent/component.jsx b/src/widgets/qbittorrent/component.jsx index c9f648165..bd201fd51 100644 --- a/src/widgets/qbittorrent/component.jsx +++ b/src/widgets/qbittorrent/component.jsx @@ -45,6 +45,25 @@ export default function Component({ service }) { } const leech = torrentData.length - completed; + const statePriority = [ + "downloading", + "forcedDL", + "metaDL", + "forcedMetaDL", + "checkingDL", + "stalledDL", + "queuedDL", + "pausedDL", + ]; + + leechTorrents.sort((firstTorrent, secondTorrent) => { + const firstStateIndex = statePriority.indexOf(firstTorrent.state); + const secondStateIndex = statePriority.indexOf(secondTorrent.state); + if (firstStateIndex !== secondStateIndex) { + return firstStateIndex - secondStateIndex; + } + return secondTorrent.progress - firstTorrent.progress; + }); return ( <> From 0482bf7b1796050fa7a9ac125c075cddd8ef9c01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:25:23 -0700 Subject: [PATCH 20/28] Chore(deps-dev): Bump eslint-plugin-react-hooks from 5.1.0 to 5.2.0 (#5467) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index eb19871e5..9b069e67a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.5.3", "prettier": "^3.5.3", "prettier-plugin-organize-imports": "^4.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eef86eea..649dab01a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,8 +127,8 @@ importers: specifier: ^7.37.4 version: 7.37.4(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-react-hooks: - specifier: ^5.1.0 - version: 5.1.0(eslint@9.25.1(jiti@2.4.2)) + specifier: ^5.2.0 + version: 5.2.0(eslint@9.25.1(jiti@2.4.2)) postcss: specifier: ^8.5.3 version: 8.5.3 @@ -1378,8 +1378,8 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-react-hooks@5.1.0: - resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -4089,7 +4089,7 @@ snapshots: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.25.1(jiti@2.4.2)) - eslint-plugin-react-hooks: 5.1.0(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.25.1(jiti@2.4.2)) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -4192,7 +4192,7 @@ snapshots: optionalDependencies: eslint-config-prettier: 10.1.1(eslint@9.25.1(jiti@2.4.2)) - eslint-plugin-react-hooks@5.1.0(eslint@9.25.1(jiti@2.4.2)): + eslint-plugin-react-hooks@5.2.0(eslint@9.25.1(jiti@2.4.2)): dependencies: eslint: 9.25.1(jiti@2.4.2) From 50f6d5f0e786ae9359ea66241034e1f98cd414a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:36:48 +0000 Subject: [PATCH 21/28] Chore(deps): Bump systeminformation from 5.25.11 to 5.27.7 (#5469) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9b069e67a..21c2574d2 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "react-icons": "^5.4.0", "recharts": "^2.15.3", "swr": "^2.3.3", - "systeminformation": "^5.25.11", + "systeminformation": "^5.27.7", "tough-cookie": "^5.1.2", "urbackup-server-api": "^0.8.9", "winston": "^3.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 649dab01a..2f1be0881 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,8 +84,8 @@ importers: specifier: ^2.3.3 version: 2.3.3(react@18.3.1) systeminformation: - specifier: ^5.25.11 - version: 5.25.11 + specifier: ^5.27.7 + version: 5.27.7 tough-cookie: specifier: ^5.1.2 version: 5.1.2 @@ -2636,8 +2636,8 @@ packages: resolution: {integrity: sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==} engines: {node: ^14.18.0 || >=16.0.0} - systeminformation@5.25.11: - resolution: {integrity: sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==} + systeminformation@5.27.7: + resolution: {integrity: sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -5564,7 +5564,7 @@ snapshots: '@pkgr/core': 0.2.4 tslib: 2.8.1 - systeminformation@5.25.11: {} + systeminformation@5.27.7: {} tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@4.0.9): dependencies: From 927b943fa4a15a12266bddacd7cbf248df6fcb0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:59:47 +0000 Subject: [PATCH 22/28] Chore(deps-dev): Bump prettier from 3.5.3 to 3.6.2 (#5468) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 21c2574d2..08c48fffd 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.5.3", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.1.0", "tailwind-scrollbar": "^4.0.2", "tailwindcss": "^4.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f1be0881..e113faee2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,7 +122,7 @@ importers: version: 6.10.2(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-prettier: specifier: ^5.2.6 - version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.3) + version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2) eslint-plugin-react: specifier: ^7.37.4 version: 7.37.4(eslint@9.25.1(jiti@2.4.2)) @@ -133,11 +133,11 @@ importers: specifier: ^8.5.3 version: 8.5.3 prettier: - specifier: ^3.5.3 - version: 3.5.3 + specifier: ^3.6.2 + version: 3.6.2 prettier-plugin-organize-imports: specifier: ^4.1.0 - version: 4.1.0(prettier@3.5.3)(typescript@5.7.3) + version: 4.1.0(prettier@3.6.2)(typescript@5.7.3) tailwind-scrollbar: specifier: ^4.0.2 version: 4.0.2(react@18.3.1)(tailwindcss@4.0.9) @@ -2262,8 +2262,8 @@ packages: vue-tsc: optional: true - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -4183,10 +4183,10 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.5.3): + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2): dependencies: eslint: 9.25.1(jiti@2.4.2) - prettier: 3.5.3 + prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.4 optionalDependencies: @@ -5089,12 +5089,12 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-organize-imports@4.1.0(prettier@3.5.3)(typescript@5.7.3): + prettier-plugin-organize-imports@4.1.0(prettier@3.6.2)(typescript@5.7.3): dependencies: - prettier: 3.5.3 + prettier: 3.6.2 typescript: 5.7.3 - prettier@3.5.3: {} + prettier@3.6.2: {} pretty-bytes@6.1.1: {} From c393982e84d772ef8b06fb8493260130a06692bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:26:59 +0000 Subject: [PATCH 23/28] Chore(deps-dev): Bump eslint-plugin-prettier from 5.2.6 to 5.5.1 (#5471) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 08c48fffd..dbf13cb68 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "eslint-config-prettier": "^10.1.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-prettier": "^5.5.1", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e113faee2..c38bc95c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,8 +121,8 @@ importers: specifier: ^6.10.2 version: 6.10.2(eslint@9.25.1(jiti@2.4.2)) eslint-plugin-prettier: - specifier: ^5.2.6 - version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2) + specifier: ^5.5.1 + version: 5.5.1(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2) eslint-plugin-react: specifier: ^7.37.4 version: 7.37.4(eslint@9.25.1(jiti@2.4.2)) @@ -476,8 +476,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.4': - resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==} + '@pkgr/core@0.2.7': + resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} '@protobufjs/aspromise@1.1.2': @@ -1364,8 +1364,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-prettier@5.2.6: - resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} + eslint-plugin-prettier@5.5.1: + resolution: {integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -2632,8 +2632,8 @@ packages: peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - synckit@0.11.4: - resolution: {integrity: sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==} + synckit@0.11.8: + resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==} engines: {node: ^14.18.0 || >=16.0.0} systeminformation@5.27.7: @@ -3181,7 +3181,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.4': {} + '@pkgr/core@0.2.7': {} '@protobufjs/aspromise@1.1.2': {} @@ -4183,12 +4183,12 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2): + eslint-plugin-prettier@5.5.1(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2))(prettier@3.6.2): dependencies: eslint: 9.25.1(jiti@2.4.2) prettier: 3.6.2 prettier-linter-helpers: 1.0.0 - synckit: 0.11.4 + synckit: 0.11.8 optionalDependencies: eslint-config-prettier: 10.1.1(eslint@9.25.1(jiti@2.4.2)) @@ -5559,10 +5559,9 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.5.0(react@18.3.1) - synckit@0.11.4: + synckit@0.11.8: dependencies: - '@pkgr/core': 0.2.4 - tslib: 2.8.1 + '@pkgr/core': 0.2.7 systeminformation@5.27.7: {} From 767e7c58b411cf566248db608fb685769edb1af3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:58:43 -0700 Subject: [PATCH 24/28] Chore(deps): Bump react-i18next from 11.18.6 to 15.5.3 (#5470) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 37 +++++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index dbf13cb68..c50e16c5f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "raw-body": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-i18next": "^11.18.6", + "react-i18next": "^15.5.3", "react-icons": "^5.4.0", "recharts": "^2.15.3", "swr": "^2.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c38bc95c7..83baba534 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-i18next: - specifier: ^11.18.6 - version: 11.18.6(i18next@24.2.3(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.5.3 + version: 15.5.3(i18next@24.2.3(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3) react-icons: specifier: ^5.4.0 version: 5.4.0(react@18.3.1) @@ -166,8 +166,8 @@ packages: resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.1': - resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} engines: {node: '>=6.9.0'} '@balena/dockerignore@1.0.2': @@ -2322,6 +2322,22 @@ packages: react-native: optional: true + react-i18next@15.5.3: + resolution: {integrity: sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-icons@5.4.0: resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==} peerDependencies: @@ -2897,7 +2913,7 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@babel/runtime@7.27.1': {} + '@babel/runtime@7.27.6': {} '@balena/dockerignore@1.0.2': {} @@ -3929,7 +3945,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.27.1 + '@babel/runtime': 7.27.6 csstype: 3.1.3 dom-serializer@2.0.0: @@ -4535,7 +4551,7 @@ snapshots: i18next@21.10.0: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.26.9 i18next@24.2.3(typescript@5.7.3): dependencies: @@ -5160,14 +5176,15 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) - react-i18next@11.18.6(i18next@24.2.3(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-i18next@15.5.3(i18next@24.2.3(typescript@5.7.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3): dependencies: - '@babel/runtime': 7.26.9 + '@babel/runtime': 7.27.6 html-parse-stringify: 3.0.1 i18next: 24.2.3(typescript@5.7.3) react: 18.3.1 optionalDependencies: react-dom: 18.3.1(react@18.3.1) + typescript: 5.7.3 react-icons@5.4.0(react@18.3.1): dependencies: @@ -5187,7 +5204,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.27.1 + '@babel/runtime': 7.27.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 From 1d990ef7c71fa94248d48a45a38c0374605aa731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BB=A9c?= Date: Mon, 7 Jul 2025 23:29:48 +0700 Subject: [PATCH 25/28] Fix: increase color popover z-index (#5489) --- src/components/toggles/color.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/toggles/color.jsx b/src/components/toggles/color.jsx index 7ea700ab3..71b7fdc13 100644 --- a/src/components/toggles/color.jsx +++ b/src/components/toggles/color.jsx @@ -55,7 +55,7 @@ export default function ColorToggle() { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - +
{colors.map((color) => ( From f5ecd6d787187386e9744ede1197b5b0fcb14ef1 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:47:31 -0700 Subject: [PATCH 26/28] Fix: fix glances info when cpu = 0 (#5506) --- src/widgets/glances/metrics/info.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widgets/glances/metrics/info.jsx b/src/widgets/glances/metrics/info.jsx index 6b875c06c..b612c1c2b 100644 --- a/src/widgets/glances/metrics/info.jsx +++ b/src/widgets/glances/metrics/info.jsx @@ -31,7 +31,8 @@ function CPU({ quicklookData, className = "" }) { return ( quicklookData && - quicklookData.cpu && ( + quicklookData.cpu !== undefined && + quicklookData.cpu !== null && (
{t("glances.cpu")}
From dba3a1f893c4d9876fdabb5fa5da56cbfc81f220 Mon Sep 17 00:00:00 2001 From: Oriano de Stefani Date: Sat, 12 Jul 2025 17:36:13 +0200 Subject: [PATCH 27/28] Enhancement: support for new grafana alerting api (#5476) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/grafana.md | 7 ++++ src/utils/config/service-helpers.js | 19 ++++++++++- src/widgets/grafana/component.jsx | 50 +++++++++++++++++++++-------- src/widgets/grafana/widget.js | 3 ++ 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/docs/widgets/services/grafana.md b/docs/widgets/services/grafana.md index 272cc3ba8..fe62c1925 100644 --- a/docs/widgets/services/grafana.md +++ b/docs/widgets/services/grafana.md @@ -5,11 +5,18 @@ description: Grafana Widget Configuration Learn more about [Grafana](https://github.com/grafana/grafana). +| Grafana Version | Homepage Widget Version | +| --------------- | ----------------------- | +| <= v10.4 | 1 (default) | +| > v10.4 | 2 | + Allowed fields: `["dashboards", "datasources", "totalalerts", "alertstriggered"]`. ```yaml widget: type: grafana + version: 2 # optional, default is 1 + alerts: alertmanager # optional, default is grafana url: http://grafana.host.or.ip:port username: username password: password diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 68de39449..a55fff70b 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -407,6 +407,9 @@ export function cleanServiceGroups(groups) { // spoolman spoolIds, + + // grafana + alerts, } = widgetData; let fieldsList = fields; @@ -514,7 +517,18 @@ export function cleanServiceGroups(groups) { if (snapshotPath) widget.snapshotPath = snapshotPath; } if ( - ["beszel", "glances", "immich", "komga", "mealie", "pfsense", "pihole", "speedtest", "wgeasy"].includes(type) + [ + "beszel", + "glances", + "immich", + "komga", + "mealie", + "pfsense", + "pihole", + "speedtest", + "wgeasy", + "grafana", + ].includes(type) ) { if (version) widget.version = parseInt(version, 10); } @@ -593,6 +607,9 @@ export function cleanServiceGroups(groups) { if (type === "jellystat") { if (days !== undefined) widget.days = parseInt(days, 10); } + if (type === "grafana") { + if (alerts) widget.alerts = alerts; + } return widget; }); return cleanedService; diff --git a/src/widgets/grafana/component.jsx b/src/widgets/grafana/component.jsx index 82d6e5c99..75724cefa 100755 --- a/src/widgets/grafana/component.jsx +++ b/src/widgets/grafana/component.jsx @@ -6,27 +6,51 @@ import useWidgetAPI from "utils/proxy/use-widget-api"; export default function Component({ service }) { const { t } = useTranslation(); - const { widget } = service; + + const { version = 1, alerts = "grafana" } = widget; + const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats"); - const { data: alertsData, error: alertsError } = useWidgetAPI(widget, "alerts"); - const { data: alertmanagerData, error: alertmanagerError } = useWidgetAPI(widget, "alertmanager"); - let alertsInt = 0; - - if (alertsError || !alertsData || alertsData.length === 0) { - if (alertmanagerData) { - alertsInt = alertmanagerData.length; - } - } else { - alertsInt = alertsData.filter((a) => a.state === "alerting").length; + let primaryAlertsEndpoint = "alerts"; + let secondaryAlertsEndpoint = "grafana"; + if (version === 2) { + primaryAlertsEndpoint = alerts; + secondaryAlertsEndpoint = ""; } - if (statsError || (alertsError && alertmanagerError)) { + const { data: primaryAlertsData, error: primaryAlertsError } = useWidgetAPI(widget, primaryAlertsEndpoint); + const { data: secondaryAlertsData, error: secondaryAlertsError } = useWidgetAPI(widget, secondaryAlertsEndpoint); + + let alertsInt = 0; + let alertsError = null; + if (version === 1) { + if (primaryAlertsError || !primaryAlertsData || primaryAlertsData.length === 0) { + if (secondaryAlertsData) { + alertsInt = secondaryAlertsData.length; + } + } else { + alertsInt = primaryAlertsData.filter((a) => a.state === "alerting").length; + } + + if (primaryAlertsError && secondaryAlertsError) { + alertsError = primaryAlertsError ?? secondaryAlertsError; + } + } else if (version === 2) { + if (primaryAlertsData) { + alertsInt = primaryAlertsData.length; + } + + if (primaryAlertsError) { + alertsError = primaryAlertsError; + } + } + + if (statsError || alertsError) { return ; } - if (!statsData || (!alertsData && !alertmanagerData)) { + if (!statsData) { return ( diff --git a/src/widgets/grafana/widget.js b/src/widgets/grafana/widget.js index 2805555f8..1c013c9af 100755 --- a/src/widgets/grafana/widget.js +++ b/src/widgets/grafana/widget.js @@ -9,6 +9,9 @@ const widget = { endpoint: "alerts", }, alertmanager: { + endpoint: "alertmanager/alertmanager/api/v2/alerts", + }, + grafana: { endpoint: "alertmanager/grafana/api/v2/alerts", }, stats: { From d0165326a64f19d96b0bbdf701ecde1e98568cc6 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 15 Jul 2025 06:56:46 -0700 Subject: [PATCH 28/28] Tweak: Improve ownership check and chown handling for .next dir (#5513) --- docker-entrypoint.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 99379de1e..16ce33aa6 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -44,9 +44,15 @@ fi if [ -d /app/.next ]; then CURRENT_UID=$(stat -c %u /app/.next) - if [ "$CURRENT_UID" -ne "$PUID" ]; then + CURRENT_GID=$(stat -c %g /app/.next) + + if [ "$PUID" -ne 0 ] && ([ "$CURRENT_UID" -ne "$PUID" ] || [ "$CURRENT_GID" -ne "$PGID" ]); then echo "Fixing ownership of /app/.next" - chown -R "$PUID:$PGID" /app/.next || echo "Warning: Could not chown /app/.next" + if ! chown -R "$PUID:$PGID" /app/.next 2>/dev/null; then + echo "Warning: Could not chown /app/.next; continuing anyway" + fi + else + echo "/app/.next already owned by correct UID/GID or running as root, skipping chown" fi fi