From f8009a706771eb73ce6e7643c86c3b072707c930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:12:51 -0800 Subject: [PATCH 01/27] Chore(deps): Bump dessant/lock-threads from 5 to 6 (#6084) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/repo-maintenance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/repo-maintenance.yml b/.github/workflows/repo-maintenance.yml index 08d482cf5..c1466ea8a 100644 --- a/.github/workflows/repo-maintenance.yml +++ b/.github/workflows/repo-maintenance.yml @@ -32,7 +32,7 @@ jobs: name: 'Lock Old Threads' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v6 with: issue-inactive-days: '30' pr-inactive-days: '30' From 681a8a828b64a911b4daa9fe542082b3606d0c19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:22:01 +0000 Subject: [PATCH 02/27] Chore(deps): Bump actions/cache from 4 to 5 (#6085) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- .github/workflows/docs-publish.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 188df16b2..aba284501 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -84,7 +84,7 @@ jobs: latest=auto - name: Next.js build cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .next/cache key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }} diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 2a8b57e24..d15bfe518 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -37,7 +37,7 @@ jobs: with: python-version: 3.x - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: key: mkdocs-material-${{ env.cache_id }} path: .cache @@ -63,7 +63,7 @@ jobs: with: python-version: 3.x - run: echo "cache_id=${{github.sha}}" >> $GITHUB_ENV - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: key: mkdocs-material-${{ env.cache_id }} path: .cache From 1b32cbbbfd7ee42b131e747e4feaa4e5254167b4 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:04:22 -0800 Subject: [PATCH 03/27] Enhancement: refactor UptimeRobot widget (#6088) --- src/widgets/uptimerobot/component.jsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/widgets/uptimerobot/component.jsx b/src/widgets/uptimerobot/component.jsx index 2c0f564be..3948fc54c 100644 --- a/src/widgets/uptimerobot/component.jsx +++ b/src/widgets/uptimerobot/component.jsx @@ -49,10 +49,12 @@ export default function Component({ service }) { // single monitor const monitor = uptimerobotData.monitors[0]; + const logs = Array.isArray(monitor.logs) ? monitor.logs : []; + const lastUpLog = logs.find((log) => log.type === 2); + const lastDownLog = logs.find((log) => log.type === 1); + let status; let uptime = 0; - let logIndex = 0; - const hasLogs = Array.isArray(monitor.logs) && monitor.logs.length > 0; switch (monitor.status) { case 0: @@ -63,8 +65,7 @@ export default function Component({ service }) { break; case 2: status = t("uptimerobot.up"); - uptime = t("common.duration", { value: hasLogs ? monitor.logs[0].duration : 0 }); - logIndex = 1; + uptime = t("common.duration", { value: lastUpLog?.duration ?? 0 }); break; case 8: status = t("uptimerobot.seemsdown"); @@ -77,14 +78,14 @@ export default function Component({ service }) { break; } - const lastDown = hasLogs ? new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString() : ""; - const downDuration = t("common.duration", { value: hasLogs ? monitor.logs[logIndex].duration : 0 }); - const hideDown = !hasLogs || (logIndex === 1 && monitor.logs[logIndex].type !== 1); + const lastDown = lastDownLog ? new Date(lastDownLog.datetime * 1000).toLocaleString() : ""; + const downDuration = t("common.duration", { value: lastDownLog?.duration ?? 0 }); + const hideDown = !lastDownLog; return ( - {hasLogs && } + {!hideDown && } {!hideDown && } From e66b58dc53ed887babf11ad8354ed1d8af2fb72c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 23:17:21 +0000 Subject: [PATCH 04/27] Chore(deps): Bump next from 15.5.7 to 15.5.9 (#6089) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index df47bbd8d..287511102 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "luxon": "^3.6.1", "memory-cache": "^0.2.0", "minecraftstatuspinger": "^1.2.2", - "next": "^15.5.7", + "next": "^15.5.9", "next-i18next": "^12.1.0", "ping": "^0.4.4", "pretty-bytes": "^7.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf61baf2c..0c5320eee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,11 +51,11 @@ importers: specifier: ^1.2.2 version: 1.2.2 next: - specifier: ^15.5.7 - version: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.5.9 + version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-i18next: specifier: ^12.1.0 - version: 12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ping: specifier: ^0.4.4 version: 0.4.4 @@ -466,8 +466,8 @@ packages: '@napi-rs/wasm-runtime@0.2.8': resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} - '@next/env@15.5.7': - resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==} + '@next/env@15.5.9': + resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==} '@next/eslint-plugin-next@15.2.4': resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==} @@ -1120,8 +1120,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -2212,8 +2212,8 @@ packages: next: '>= 10.0.0' react: '>= 16.8.0' - next@15.5.7: - resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==} + next@15.5.9: + resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -3374,7 +3374,7 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next/env@15.5.7': {} + '@next/env@15.5.9': {} '@next/eslint-plugin-next@15.2.4': dependencies: @@ -4024,7 +4024,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001760: {} chalk@4.1.2: dependencies: @@ -5259,7 +5259,7 @@ snapshots: net@1.0.2: {} - next-i18next@12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-i18next@12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.9 '@types/hoist-non-react-statics': 3.3.6 @@ -5267,18 +5267,18 @@ snapshots: hoist-non-react-statics: 3.3.2 i18next: 21.10.0 i18next-fs-backend: 1.2.0 - next: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-i18next: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - react-dom - react-native - next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 15.5.7 + '@next/env': 15.5.9 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001759 + caniuse-lite: 1.0.30001760 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) From 0d99a8766f09354e650e17f2884552e76989a11c Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:05:00 -0800 Subject: [PATCH 05/27] Fix: retrieve stats from all network interfaces (#6102) --- src/pages/api/widgets/resources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/widgets/resources.js b/src/pages/api/widgets/resources.js index 0e1fcbcf6..21819206c 100644 --- a/src/pages/api/widgets/resources.js +++ b/src/pages/api/widgets/resources.js @@ -59,7 +59,7 @@ export default async function handler(req, res) { } if (type === "network") { - let networkData = await si.networkStats(); + let networkData = await si.networkStats("*"); let interfaceDefault; logger.debug("networkData:", JSON.stringify(networkData)); if (interfaceName && interfaceName !== "default") { From be7a00d631157704d1c354d162655173f8c8375f Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 23 Dec 2025 08:02:58 -0800 Subject: [PATCH 06/27] Enhancement: fully support custom headers (#6125) --- docs/configs/docker.md | 2 ++ docs/configs/kubernetes.md | 1 + docs/configs/services.md | 19 +++++++++++++++++++ src/utils/proxy/handlers/credentialed.js | 3 +++ src/utils/proxy/handlers/generic.js | 6 +++++- 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/configs/docker.md b/docs/configs/docker.md index 1dcacbf0a..0fffc9626 100644 --- a/docs/configs/docker.md +++ b/docs/configs/docker.md @@ -189,6 +189,8 @@ labels: ... - homepage.widgets[1].slug=youreventslughere ``` +To pass custom HTTP headers with a widget request when using labels, use the same dot-notation: `homepage.widget.headers.X-Auth-Key=secret` (or `homepage.widgets[0].headers.X-Auth-Key=secret` when multiple widgets are present). + 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/kubernetes.md b/docs/configs/kubernetes.md index 51cd4b5dd..88748f177 100644 --- a/docs/configs/kubernetes.md +++ b/docs/configs/kubernetes.md @@ -94,6 +94,7 @@ metadata: gethomepage.dev/name: Emby gethomepage.dev/widget.type: "emby" gethomepage.dev/widget.url: "https://emby.example.com" + gethomepage.dev/widget.headers.X-Auth-Key: "your-secret-here" gethomepage.dev/pod-selector: "" gethomepage.dev/weight: 10 # optional gethomepage.dev/instance: "public" # optional diff --git a/docs/configs/services.md b/docs/configs/services.md index 06067bf6c..6e1b15761 100644 --- a/docs/configs/services.md +++ b/docs/configs/services.md @@ -101,6 +101,25 @@ Each service can have multiple widgets attached to it, for example: Multiple widgets per service are not yet supported with Kubernetes ingress annotations. +#### Custom HTTP headers + +Widgets that make HTTP calls support extra request headers via `headers`. This is useful when a reverse proxy expects a secret header. + +```yaml +- UptimeRobot: + icon: uptimekuma.png + href: https://uptimerobot.com/ + widget: + type: uptimerobot + url: https://api.uptimerobot.com + key: ${UPTIMEROBOT_API_KEY} + headers: + User-Agent: homepage + X-Auth-Key: your-secret-here +``` + +If you define services via Docker labels or Kubernetes annotations, use the same key with dot-notation (for example `homepage.widget.headers.X-Auth-Key=secret` or `gethomepage.dev/widget.headers.X-Auth-Key: "secret"`). + #### 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/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js index f5e9cf51a..3c61aa00e 100644 --- a/src/utils/proxy/handlers/credentialed.js +++ b/src/utils/proxy/handlers/credentialed.js @@ -27,6 +27,9 @@ export default async function credentialedProxyHandler(req, res, map) { const headers = { "Content-Type": "application/json", + ...(widgets[widget.type].headers ?? {}), + ...(widget.headers ?? {}), + ...(req.extraHeaders ?? {}), }; if (widget.type === "stocks") { diff --git a/src/utils/proxy/handlers/generic.js b/src/utils/proxy/handlers/generic.js index 166dc145c..e7653c708 100644 --- a/src/utils/proxy/handlers/generic.js +++ b/src/utils/proxy/handlers/generic.js @@ -25,7 +25,11 @@ export default async function genericProxyHandler(req, res, map) { } const url = new URL(urlString); - const headers = req.extraHeaders ?? widget.headers ?? widgets[widget.type].headers ?? {}; + const headers = { + ...(widgets[widget.type].headers ?? {}), + ...(widget.headers ?? {}), + ...(req.extraHeaders ?? {}), + }; if (widget.username && widget.password) { headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`; From 31da9ee417a5cd18f0806f94f8afd0abd0f2e419 Mon Sep 17 00:00:00 2001 From: Cameron Horn Date: Wed, 24 Dec 2025 17:47:22 -0500 Subject: [PATCH 07/27] Fix: prevent cache collision with multiple plex widgets (#6126) --- src/widgets/plex/proxy.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/widgets/plex/proxy.js b/src/widgets/plex/proxy.js index 18ffc49b6..f21d07d03 100644 --- a/src/widgets/plex/proxy.js +++ b/src/widgets/plex/proxy.js @@ -65,7 +65,7 @@ async function fetchFromPlexAPI(endpoint, widget) { export default async function plexProxyHandler(req, res) { const widget = await getWidget(req); - const { service } = req.query; + const { service, index } = req.query; if (!widget) { return res.status(400).json({ error: "Invalid proxy service type" }); @@ -85,19 +85,19 @@ export default async function plexProxyHandler(req, res) { streams = apiData.MediaContainer._attributes.size; } - let libraries = cache.get(`${librariesCacheKey}.${service}`); + let libraries = cache.get(`${librariesCacheKey}.${service}.${index}`); if (libraries === null) { logger.debug("Getting libraries from Plex API"); [status, apiData] = await fetchFromPlexAPI("/library/sections", widget); if (apiData && apiData.MediaContainer) { libraries = [].concat(apiData.MediaContainer.Directory); - cache.put(`${librariesCacheKey}.${service}`, libraries, 1000 * 60 * 60 * 6); + cache.put(`${librariesCacheKey}.${service}.${index}`, libraries, 1000 * 60 * 60 * 6); } } - let albums = cache.get(`${albumsCacheKey}.${service}`); - let movies = cache.get(`${moviesCacheKey}.${service}`); - let tv = cache.get(`${tvCacheKey}.${service}`); + let albums = cache.get(`${albumsCacheKey}.${service}.${index}`); + let movies = cache.get(`${moviesCacheKey}.${service}.${index}`); + let tv = cache.get(`${tvCacheKey}.${service}.${index}`); if (albums === null || movies === null || tv === null) { albums = 0; movies = 0; @@ -123,9 +123,9 @@ export default async function plexProxyHandler(req, res) { } }), ); - cache.put(`${albumsCacheKey}.${service}`, albums, 1000 * 60 * 10); - cache.put(`${tvCacheKey}.${service}`, tv, 1000 * 60 * 10); - cache.put(`${moviesCacheKey}.${service}`, movies, 1000 * 60 * 10); + cache.put(`${albumsCacheKey}.${service}.${index}`, albums, 1000 * 60 * 10); + cache.put(`${tvCacheKey}.${service}.${index}`, tv, 1000 * 60 * 10); + cache.put(`${moviesCacheKey}.${service}.${index}`, movies, 1000 * 60 * 10); } const data = { From ff296be4a4daf81e95a7a0527c88ab39a6116257 Mon Sep 17 00:00:00 2001 From: I-am-not-a-number <24510085+I-am-not-a-number@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:44:30 +0100 Subject: [PATCH 08/27] Enhancement: include prefix length when displaying ipv6 prefix (#6130) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/widgets/fritzbox/proxy.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widgets/fritzbox/proxy.js b/src/widgets/fritzbox/proxy.js index c8c57fbc4..46d43e83b 100644 --- a/src/widgets/fritzbox/proxy.js +++ b/src/widgets/fritzbox/proxy.js @@ -85,6 +85,9 @@ export default async function fritzboxProxyHandler(req, res) { requestExternalIPv6Prefix ? requestEndpoint(apiBaseUrl, "WANIPConnection", "X_AVM_DE_GetIPv6Prefix") : null, ]) .then(([statusInfo, linkProperties, addonInfos, externalIPAddress, externalIPv6Address, externalIPv6Prefix]) => { + const ipv6Prefix = externalIPv6Prefix?.NewIPv6Prefix; + const ipv6Len = externalIPv6Prefix?.NewPrefixLength; + res.status(200).json({ connectionStatus: statusInfo?.NewConnectionStatus || "Unconfigured", uptime: statusInfo?.NewUptime || 0, @@ -96,7 +99,7 @@ export default async function fritzboxProxyHandler(req, res) { sent: addonInfos?.NewX_AVM_DE_TotalBytesSent64 || 0, externalIPAddress: externalIPAddress?.NewExternalIPAddress || null, externalIPv6Address: externalIPv6Address?.NewExternalIPv6Address || null, - externalIPv6Prefix: externalIPv6Prefix?.NewIPv6Prefix || null, + externalIPv6Prefix: ipv6Prefix && ipv6Len != null ? `${ipv6Prefix}/${ipv6Len}` : (ipv6Prefix ?? null), }); }) .catch((error) => { From ae258b8276685f289b02463ffe1fd603272ff56e Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 29 Dec 2025 23:03:09 +0100 Subject: [PATCH 09/27] Fix: ensure minimum gap for resource widget items (#6137) --- src/components/widgets/widget/resource.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/widgets/widget/resource.jsx b/src/components/widgets/widget/resource.jsx index b1f737407..e80324b21 100644 --- a/src/components/widgets/widget/resource.jsx +++ b/src/components/widgets/widget/resource.jsx @@ -24,12 +24,12 @@ export default function Resource({ wide ? " min-w-[120px]" : "min-w-[85px]" }`} > -
+
{value}
{label}
{expanded && ( -
+
{expandedValue}
{expandedLabel}
From 2b31c23b9e88e75a83c20ddfedf87d87debb2cb9 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:15:42 -0800 Subject: [PATCH 10/27] Fix: support latest homebridge status labels (#6139) --- public/locales/en/common.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2d237a23c..22d2134f6 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", From f7ad322d4c60081a2093d8a23fcf5a99105a7f97 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:01:08 -0800 Subject: [PATCH 11/27] Revert "Fix: restore bg image to body again (#5828)" This reverts commit 06cf76d724b8d2f145b14f05a59aac3bf453052b. --- src/pages/index.jsx | 68 ++++++++++++++++++------------------------ src/styles/globals.css | 12 ++++++++ 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 9109b4bd3..c243c3ff4 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -554,48 +554,38 @@ export default function Wrapper({ initialSettings, fallback }) { html.classList.add(desiredThemeClass); } - if (backgroundImage) { - const safeBackgroundImage = backgroundImage.replace(/'/g, "\\'"); - body.style.backgroundImage = `linear-gradient(rgb(var(--bg-color) / ${opacity}), rgb(var(--bg-color) / ${opacity})), url('${safeBackgroundImage}')`; - body.style.backgroundSize = "cover"; - body.style.backgroundPosition = "center"; - body.style.backgroundAttachment = "fixed"; - body.style.backgroundRepeat = "no-repeat"; - body.style.backgroundColor = ""; - } else { - body.style.backgroundImage = "none"; - body.style.backgroundColor = "rgb(var(--bg-color))"; - body.style.backgroundSize = ""; - body.style.backgroundPosition = ""; - body.style.backgroundAttachment = ""; - body.style.backgroundRepeat = ""; - } - - return () => { - body.style.backgroundImage = ""; - body.style.backgroundColor = ""; - body.style.backgroundSize = ""; - body.style.backgroundPosition = ""; - body.style.backgroundAttachment = ""; - body.style.backgroundRepeat = ""; - }; + // Remove any previously applied inline styles + body.style.backgroundImage = ""; + body.style.backgroundColor = ""; + body.style.backgroundAttachment = ""; }, [backgroundImage, opacity, theme, color, initialSettings.color]); return ( -
-
- + <> + {backgroundImage && ( + + ); } diff --git a/src/styles/globals.css b/src/styles/globals.css index ab5e4d793..ce3dddf80 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -30,6 +30,18 @@ body, height: 100%; margin: 0; padding: 0; + background-color: rgb(var(--bg-color)); +} + +#background { + position: fixed; + inset: 0; + z-index: 0; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + background-attachment: scroll; + pointer-events: none; } html, From 682e0cbc82ec4fb4b733a157f6d90acc75c98edd Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:18:56 -0800 Subject: [PATCH 12/27] Enhancement: Add support for Pyload 0.5.0 CSRF-protected API (#6142) --- src/widgets/pyload/proxy.js | 68 ++++++++++++++++++++++++++++++------ src/widgets/pyload/widget.js | 1 + 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/widgets/pyload/proxy.js b/src/widgets/pyload/proxy.js index 2a1949c17..7bfbd46cb 100644 --- a/src/widgets/pyload/proxy.js +++ b/src/widgets/pyload/proxy.js @@ -11,6 +11,15 @@ const logger = createLogger(proxyName); const sessionCacheKey = `${proxyName}__sessionId`; const isNgCacheKey = `${proxyName}__isNg`; +function parsePyloadResponse(url, data) { + try { + return JSON.parse(Buffer.from(data).toString()); + } catch (e) { + logger.error(`Error communicating with pyload API at ${url}, returned: ${JSON.stringify(data)}`); + return data; + } +} + async function fetchFromPyloadAPI(url, sessionId, params, service) { const options = { body: params @@ -33,13 +42,33 @@ async function fetchFromPyloadAPI(url, sessionId, params, service) { // eslint-disable-next-line no-unused-vars const [status, contentType, data, responseHeaders] = await httpProxy(url, options); - let returnData; - try { - returnData = JSON.parse(Buffer.from(data).toString()); - } catch (e) { - logger.error(`Error communicating with pyload API at ${url}, returned: ${JSON.stringify(data)}`); - returnData = data; + const returnData = parsePyloadResponse(url, data); + return [status, returnData, responseHeaders]; +} + +async function fetchFromPyloadAPIBasic(url, params, username, password) { + const parsedUrl = new URL(url); + const isGetRequest = !params || Object.keys(params).length === 0; + + const options = { + method: isGetRequest ? "GET" : "POST", + headers: { + Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`, + }, + }; + + if (isGetRequest) { + if (params) { + Object.keys(params).forEach((key) => parsedUrl.searchParams.append(key, params[key])); + } + } else { + options.headers["Content-Type"] = "application/json"; + options.body = JSON.stringify(params); } + + // eslint-disable-next-line no-unused-vars + const [status, contentType, data, responseHeaders] = await httpProxy(parsedUrl, options); + const returnData = parsePyloadResponse(parsedUrl, data); return [status, returnData, responseHeaders]; } @@ -66,24 +95,43 @@ async function login(loginUrl, service, username, password = "") { return sessionId; } -export default async function pyloadProxyHandler(req, res) { +export default async function pyloadProxyHandler(req, res, map = {}) { const { group, service, endpoint, index } = req.query; + const { ngEndpoint } = map; try { if (group && service) { const widget = await getServiceWidget(group, service, index); if (widget) { - const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); + const apiTemplate = widgets[widget.type].api; + const url = new URL(formatApiCall(apiTemplate, { endpoint, ...widget })); + const ngUrl = ngEndpoint ? new URL(formatApiCall(apiTemplate, { endpoint: ngEndpoint, ...widget })) : url; const loginUrl = `${widget.url}/api/login`; + const hasCredentials = widget.username && widget.password; + + if (hasCredentials) { + const [status, data] = await fetchFromPyloadAPIBasic(ngUrl, null, widget.username, widget.password); + + if (status === 200 && !data?.error) { + cache.put(`${isNgCacheKey}.${service}`, true); + return res.json(data); + } + + if (status === 401) { + return res + .status(status) + .send({ error: { message: "Invalid credentials communicating with Pyload API", data } }); + } + } let sessionId = cache.get(`${sessionCacheKey}.${service}`) ?? (await login(loginUrl, service, widget.username, widget.password)); let [status, data] = await fetchFromPyloadAPI(url, sessionId, null, service); - if (status === 403 || status === 401) { - logger.info("Failed to retrieve data from Pyload API, trying to login again..."); + if (status === 403 || status === 401 || (status === 400 && data?.error?.includes("CSRF token"))) { + logger.info("Failed to retrieve data from Pyload API with session auth, trying to login again..."); cache.del(`${sessionCacheKey}.${service}`); sessionId = await login(loginUrl, service, widget.username, widget.password); [status, data] = await fetchFromPyloadAPI(url, sessionId, null, service); diff --git a/src/widgets/pyload/widget.js b/src/widgets/pyload/widget.js index 45834bd61..ac082ab01 100644 --- a/src/widgets/pyload/widget.js +++ b/src/widgets/pyload/widget.js @@ -7,6 +7,7 @@ const widget = { mappings: { status: { endpoint: "statusServer", + map: { ngEndpoint: "status_server" }, }, }, }; From 08da8e66fdb33aa6788904078a5be6bf6cbf3c32 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:24:42 -0800 Subject: [PATCH 13/27] Add @tailwindcss/oxide to onlyBuiltDependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 287511102..f9022680c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ }, "pnpm": { "onlyBuiltDependencies": [ + "@tailwindcss/oxide", "osx-temperature-sensor", "sharp" ] From 0515f891aba02012d8ba3ef6ff20a80fbd261ca7 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 2 Jan 2026 06:40:58 +0100 Subject: [PATCH 14/27] Fix: fix default configured service weight = 0 (#6151) --- src/utils/config/service-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 2d477db57..79f2129d9 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -35,7 +35,7 @@ function parseServicesToGroups(services) { serviceGroupServices.push({ name: entryName, ...entries[entryName], - weight: entries[entryName].weight || serviceGroupServices.length * 100, // default weight + weight: entries[entryName].weight ?? serviceGroupServices.length * 100, // default weight type: "service", }); } From a4ad00e27cf9db4309f6be7bb8b84b71764fb90e Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:45:19 -0800 Subject: [PATCH 15/27] Update service-helpers.js --- src/utils/config/service-helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 79f2129d9..b47e1b7c4 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -35,7 +35,7 @@ function parseServicesToGroups(services) { serviceGroupServices.push({ name: entryName, ...entries[entryName], - weight: entries[entryName].weight ?? serviceGroupServices.length * 100, // default weight + weight: entries[entryName].weight ?? (serviceGroupServices.length + 1) * 100, // default weight type: "service", }); } @@ -107,6 +107,7 @@ export async function servicesFromDocker() { constructedService = { container: containerName.replace(/^\//, ""), server: serverName, + weight: 0, type: "service", }; } From 77e67b34c441a58f309888844e559b94e7cd4cd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:55:32 -0800 Subject: [PATCH 16/27] Chore(deps): Bump js-yaml from 4.1.0 to 4.1.1 (#6144) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f9022680c..c2d3032ba 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "gamedig": "^5.3.2", "i18next": "^25.5.3", "ical.js": "^2.1.0", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "json-rpc-2.0": "^1.7.0", "luxon": "^3.6.1", "memory-cache": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c5320eee..9b2535063 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 js-yaml: - specifier: ^4.1.0 - version: 4.1.0 + specifier: ^4.1.1 + version: 4.1.1 json-rpc-2.0: specifier: ^1.7.0 version: 1.7.0 @@ -1966,8 +1966,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsep@1.4.0: @@ -3128,7 +3128,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3352,7 +3352,7 @@ snapshots: '@types/ws': 8.5.14 form-data: 4.0.2 isomorphic-ws: 5.0.0(ws@8.18.0) - js-yaml: 4.1.0 + js-yaml: 4.1.1 jsonpath-plus: 10.3.0 node-fetch: 2.7.0 openid-client: 6.3.0 @@ -5070,7 +5070,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 From e7acd66c6e777fbf3879495cd3f9d6376a4c8829 Mon Sep 17 00:00:00 2001 From: JanGrosse Date: Fri, 2 Jan 2026 06:55:48 +0100 Subject: [PATCH 17/27] Fix: correct month handling for Wallos widget (#6150) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- src/widgets/wallos/component.jsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/widgets/wallos/component.jsx b/src/widgets/wallos/component.jsx index c3be5c434..6280c852b 100644 --- a/src/widgets/wallos/component.jsx +++ b/src/widgets/wallos/component.jsx @@ -6,8 +6,17 @@ import useWidgetAPI from "utils/proxy/use-widget-api"; const MAX_ALLOWED_FIELDS = 4; +const todayDate = new Date(); +function toApiMonthYear(offset = 0) { + // API expects 1-indexed months, wrap around if needed + const m = todayDate.getMonth() + 1 + offset; + return { + month: ((m + 11) % 12) + 1, + year: todayDate.getFullYear() + Math.floor((m - 1) / 12), + }; +} + export default function Component({ service }) { - const todayDate = new Date(); const { t } = useTranslation(); const { widget } = service; @@ -29,28 +38,19 @@ export default function Component({ service }) { const { data: subscriptionsThisMonthlyCostData, error: subscriptionsThisMonthlyCostError } = useWidgetAPI( widget, subscriptionsThisMonthlyEndpoint, - { - month: todayDate.getMonth(), - year: todayDate.getFullYear(), - }, + toApiMonthYear(), // this month ); const subscriptionsNextMonthlyEndpoint = widget.fields.includes("nextMonthlyCost") ? "get_monthly_cost" : ""; const { data: subscriptionsNextMonthlyCostData, error: subscriptionsNextMonthlyCostError } = useWidgetAPI( widget, subscriptionsNextMonthlyEndpoint, - { - month: todayDate.getMonth() + 1, - year: todayDate.getFullYear(), - }, + toApiMonthYear(1), // next month ); const subscriptionsPreviousMonthlyEndpoint = widget.fields.includes("previousMonthlyCost") ? "get_monthly_cost" : ""; const { data: subscriptionsPreviousMonthlyCostData, error: subscriptionsPreviousMonthlyCostError } = useWidgetAPI( widget, subscriptionsPreviousMonthlyEndpoint, - { - month: todayDate.getMonth() - 1, - year: todayDate.getFullYear(), - }, + toApiMonthYear(-1), // previous month ); if ( From 03dfc964f162f36fe1e3b29b577a79ba4e85217e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 06:09:56 +0000 Subject: [PATCH 18/27] Chore(deps): Bump urbackup-server-api from 0.8.9 to 0.91.0 (#6146) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 21 +++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index c2d3032ba..118dd67c0 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "swr": "^2.3.3", "systeminformation": "^5.27.11", "tough-cookie": "^6.0.0", - "urbackup-server-api": "^0.8.9", + "urbackup-server-api": "^0.91.0", "winston": "^3.17.0", "xml-js": "^1.6.11" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b2535063..9df7a23f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,8 +90,8 @@ importers: specifier: ^6.0.0 version: 6.0.0 urbackup-server-api: - specifier: ^0.8.9 - version: 0.8.9 + specifier: ^0.91.0 + version: 0.91.0 winston: specifier: ^3.17.0 version: 3.17.0 @@ -1035,8 +1035,8 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - async-mutex@0.3.2: - resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==} + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -2930,8 +2930,8 @@ packages: unrs-resolver@1.3.3: resolution: {integrity: sha512-PFLAGQzYlyjniXdbmQ3dnGMZJXX5yrl2YS4DLRfR3BhgUsE1zpRIrccp9XMOGRfIHpdFvCn/nr5N1KMVda4x3A==} - urbackup-server-api@0.8.9: - resolution: {integrity: sha512-Igu6A0xSZeMsiN6PWT7zG4aD+iJR5fXT/j5+xwAvnD/vCNfvVrettIsXv6MftxOajvTmtlgaYu8KDoH1EJQ6DQ==} + urbackup-server-api@0.91.0: + resolution: {integrity: sha512-N1CSnGSCSHjwWfGOp6jE56mHYoZor/p++ii8yPsN9P/3cKLBgCvrAZxAbfi+IgK9FZpQEx/kPX1R8OTJRy+x6A==} uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3931,7 +3931,7 @@ snapshots: async-function@1.0.0: {} - async-mutex@0.3.2: + async-mutex@0.5.0: dependencies: tslib: 2.8.1 @@ -6097,12 +6097,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.3.3 '@unrs/resolver-binding-win32-x64-msvc': 1.3.3 - urbackup-server-api@0.8.9: + urbackup-server-api@0.91.0: dependencies: - async-mutex: 0.3.2 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding + async-mutex: 0.5.0 uri-js@4.4.1: dependencies: From 392ff3833e4c97a166226a923d3cf986664231e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 06:26:25 +0000 Subject: [PATCH 19/27] Chore(deps-dev): Bump @tailwindcss/postcss from 4.1.14 to 4.1.18 (#6147) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 288 +++++++++++++++++++++++-------------------------- 2 files changed, 135 insertions(+), 155 deletions(-) diff --git a/package.json b/package.json index 118dd67c0..67c5a4c1a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/postcss": "^4.1.14", + "@tailwindcss/postcss": "^4.1.18", "eslint": "^9.25.1", "eslint-config-next": "^15.2.4", "eslint-config-prettier": "^10.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9df7a23f7..f675ed815 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,8 +103,8 @@ importers: specifier: ^0.5.10 version: 0.5.10(tailwindcss@4.0.9) '@tailwindcss/postcss': - specifier: ^4.1.14 - version: 4.1.14 + specifier: ^4.1.18 + version: 4.1.18 eslint: specifier: ^9.25.1 version: 9.25.1(jiti@2.6.1) @@ -653,65 +653,65 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' - '@tailwindcss/node@4.1.14': - resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} - '@tailwindcss/oxide-android-arm64@4.1.14': - resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.14': - resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.14': - resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.14': - resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': - resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': - resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.14': - resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.14': - resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.14': - resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.14': - resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -722,24 +722,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': - resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.14': - resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.14': - resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} engines: {node: '>= 10'} - '@tailwindcss/postcss@4.1.14': - resolution: {integrity: sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==} + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} '@tanstack/react-virtual@3.13.12': resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} @@ -1320,10 +1320,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} - engines: {node: '>=8'} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1362,8 +1358,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} es-abstract@1.23.9: @@ -2016,68 +2012,74 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} locate-path@6.0.0: @@ -2112,8 +2114,8 @@ packages: resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==} engines: {node: '>=12'} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} @@ -2176,10 +2178,6 @@ packages: resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} engines: {node: '>= 18'} - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -2805,11 +2803,11 @@ packages: tailwindcss@4.0.9: resolution: {integrity: sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==} - tailwindcss@4.1.14: - resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} tar-fs@2.1.3: @@ -2823,10 +2821,6 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} - tar@7.5.1: - resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} - engines: {node: '>=18'} - telnet-client@2.2.6: resolution: {integrity: sha512-ZUYrLsPtQupQww3eSEORDVOb6ztdtKEghya6TVXPo2tg/UQq2pn5rHhvwuUvyYpbnsoqdNY1fyD1GNkXHR8dYA==} @@ -3534,77 +3528,74 @@ snapshots: mini-svg-data-uri: 1.4.4 tailwindcss: 4.0.9 - '@tailwindcss/node@4.1.14': + '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.3 + enhanced-resolve: 5.18.4 jiti: 2.6.1 - lightningcss: 1.30.1 - magic-string: 0.30.19 + lightningcss: 1.30.2 + magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.14 + tailwindcss: 4.1.18 - '@tailwindcss/oxide-android-arm64@4.1.14': + '@tailwindcss/oxide-android-arm64@4.1.18': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.14': + '@tailwindcss/oxide-darwin-arm64@4.1.18': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.14': + '@tailwindcss/oxide-darwin-x64@4.1.18': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.14': + '@tailwindcss/oxide-freebsd-x64@4.1.18': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.14': + '@tailwindcss/oxide-linux-x64-musl@4.1.18': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.14': + '@tailwindcss/oxide-wasm32-wasi@4.1.18': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': optional: true - '@tailwindcss/oxide@4.1.14': - dependencies: - detect-libc: 2.1.1 - tar: 7.5.1 + '@tailwindcss/oxide@4.1.18': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.14 - '@tailwindcss/oxide-darwin-arm64': 4.1.14 - '@tailwindcss/oxide-darwin-x64': 4.1.14 - '@tailwindcss/oxide-freebsd-x64': 4.1.14 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 - '@tailwindcss/oxide-linux-x64-musl': 4.1.14 - '@tailwindcss/oxide-wasm32-wasi': 4.1.14 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - '@tailwindcss/postcss@4.1.14': + '@tailwindcss/postcss@4.1.18': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.14 - '@tailwindcss/oxide': 4.1.14 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 postcss: 8.5.6 - tailwindcss: 4.1.14 + tailwindcss: 4.1.18 '@tanstack/react-virtual@3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -4200,10 +4191,7 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.1.1: {} - - detect-libc@2.1.2: - optional: true + detect-libc@2.1.2: {} docker-modem@5.0.6: dependencies: @@ -4250,10 +4238,10 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.3: + enhanced-resolve@5.18.4: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.3 + tapable: 2.3.0 es-abstract@1.23.9: dependencies: @@ -5118,50 +5106,54 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-darwin-arm64@1.30.1: + lightningcss-android-arm64@1.30.2: optional: true - lightningcss-darwin-x64@1.30.1: + lightningcss-darwin-arm64@1.30.2: optional: true - lightningcss-freebsd-x64@1.30.1: + lightningcss-darwin-x64@1.30.2: optional: true - lightningcss-linux-arm-gnueabihf@1.30.1: + lightningcss-freebsd-x64@1.30.2: optional: true - lightningcss-linux-arm64-gnu@1.30.1: + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - lightningcss-linux-arm64-musl@1.30.1: + lightningcss-linux-arm64-gnu@1.30.2: optional: true - lightningcss-linux-x64-gnu@1.30.1: + lightningcss-linux-arm64-musl@1.30.2: optional: true - lightningcss-linux-x64-musl@1.30.1: + lightningcss-linux-x64-gnu@1.30.2: optional: true - lightningcss-win32-arm64-msvc@1.30.1: + lightningcss-linux-x64-musl@1.30.2: optional: true - lightningcss-win32-x64-msvc@1.30.1: + lightningcss-win32-arm64-msvc@1.30.2: optional: true - lightningcss@1.30.1: + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: dependencies: - detect-libc: 2.1.1 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 locate-path@6.0.0: dependencies: @@ -5192,7 +5184,7 @@ snapshots: luxon@3.6.1: {} - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5240,10 +5232,6 @@ snapshots: minipass: 7.1.2 rimraf: 5.0.10 - minizlib@3.1.0: - dependencies: - minipass: 7.1.2 - mkdirp-classic@0.5.3: {} mkdirp@3.0.1: {} @@ -5936,9 +5924,9 @@ snapshots: tailwindcss@4.0.9: {} - tailwindcss@4.1.14: {} + tailwindcss@4.1.18: {} - tapable@2.2.3: {} + tapable@2.3.0: {} tar-fs@2.1.3: dependencies: @@ -5964,14 +5952,6 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - tar@7.5.1: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.1.0 - yallist: 5.0.0 - telnet-client@2.2.6: dependencies: net: 1.0.2 From 3330954a44c6d1e233cb8fa0dbe65ffa6974fa8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 06:38:31 +0000 Subject: [PATCH 20/27] Chore(deps): Bump react-icons from 5.4.0 to 5.5.0 (#6148) 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 67c5a4c1a..be58b9c7b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^15.5.3", - "react-icons": "^5.4.0", + "react-icons": "^5.5.0", "recharts": "^3.1.2", "swr": "^2.3.3", "systeminformation": "^5.27.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f675ed815..56b42b453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: ^15.5.3 version: 15.5.3(i18next@25.5.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) + specifier: ^5.5.0 + version: 5.5.0(react@18.3.1) recharts: specifier: ^3.1.2 version: 3.1.2(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(redux@5.0.1) @@ -2456,8 +2456,8 @@ packages: typescript: optional: true - react-icons@5.4.0: - resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==} + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} peerDependencies: react: '*' @@ -5501,7 +5501,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) typescript: 5.7.3 - react-icons@5.4.0(react@18.3.1): + react-icons@5.5.0(react@18.3.1): dependencies: react: 18.3.1 From b0bc9b6b2e6d97fa87d3632b73c8069e8be7ff1a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:56:42 -0800 Subject: [PATCH 21/27] Tweak: skip chown operations when running as root (#6170) --- docker-entrypoint.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 03b410f4d..51a52523c 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -22,7 +22,9 @@ if [ "$HOSTNAME" = "::" ]; then fi # Check ownership before chown -if [ -e /app/config ]; then +if [ "$PUID" = "0" ]; then + echo "Skipping ownership changes for /app/config" +elif [ -e /app/config ]; then CURRENT_UID=$(stat -c %u /app/config) CURRENT_GID=$(stat -c %g /app/config) @@ -39,7 +41,9 @@ else fi # Ensure /app/config/logs exists and is owned -if [ -n "$PUID" ] && [ -n "$PGID" ]; then +if [ "$PUID" = "0" ]; then + echo "Skipping ownership changes for /app/config/logs" +elif [ -n "$PUID" ] && [ -n "$PGID" ]; then mkdir -p /app/config/logs 2>/dev/null || true if [ -d /app/config/logs ]; then LOG_UID=$(stat -c %u /app/config/logs) From 82d4d15622e33db7d5fa88bd48710cd18f17a122 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:19:54 -0800 Subject: [PATCH 22/27] Enhancement: TrueNAS widget web socket API support (#6161) --- docs/widgets/services/truenas.md | 6 ++ package.json | 1 + pnpm-lock.yaml | 17 +-- src/widgets/truenas/component.jsx | 4 +- src/widgets/truenas/proxy.js | 172 ++++++++++++++++++++++++++++++ src/widgets/truenas/widget.js | 27 +++-- 6 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 src/widgets/truenas/proxy.js diff --git a/docs/widgets/services/truenas.md b/docs/widgets/services/truenas.md index 97bba3be7..96785af41 100644 --- a/docs/widgets/services/truenas.md +++ b/docs/widgets/services/truenas.md @@ -5,6 +5,11 @@ description: TrueNas Scale Widget Configuration Learn more about [TrueNas](https://www.truenas.com/). +| TrueNAS Version | Homepage widget version | +| ----------------------- | ----------------------- | +| < 26.04 (REST API) | 1 (default) | +| > 25.04 (Websocket API) | 2 | + Allowed fields: `["load", "uptime", "alerts"]`. To create an API Key, follow [the official TrueNAS documentation](https://www.truenas.com/docs/scale/scaletutorials/toptoolbar/managingapikeys/). @@ -17,6 +22,7 @@ To use the `enablePools` option with TrueNAS Core, the `nasType` parameter is re widget: type: truenas url: http://truenas.host.or.ip + version: 2 # optional, defaults to 1 username: user # not required if using api key password: pass # not required if using api key key: yourtruenasapikey # not required if using username / password diff --git a/package.json b/package.json index be58b9c7b..9fe4f0a55 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "tough-cookie": "^6.0.0", "urbackup-server-api": "^0.91.0", "winston": "^3.17.0", + "ws": "^8.18.3", "xml-js": "^1.6.11" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56b42b453..72f620316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: winston: specifier: ^3.17.0 version: 3.17.0 + ws: + specifier: ^8.18.3 + version: 8.18.3 xml-js: specifier: ^1.6.11 version: 1.6.11 @@ -3011,8 +3014,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3345,7 +3348,7 @@ snapshots: '@types/tar': 6.1.13 '@types/ws': 8.5.14 form-data: 4.0.2 - isomorphic-ws: 5.0.0(ws@8.18.0) + isomorphic-ws: 5.0.0(ws@8.18.3) js-yaml: 4.1.1 jsonpath-plus: 10.3.0 node-fetch: 2.7.0 @@ -3355,7 +3358,7 @@ snapshots: tar: 7.4.3 tmp-promise: 3.0.3 tslib: 2.8.1 - ws: 8.18.0 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - encoding @@ -5033,9 +5036,9 @@ snapshots: isexe@2.0.0: {} - isomorphic-ws@5.0.0(ws@8.18.0): + isomorphic-ws@5.0.0(ws@8.18.3): dependencies: - ws: 8.18.0 + ws: 8.18.3 iterator.prototype@1.1.5: dependencies: @@ -6215,7 +6218,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.0: {} + ws@8.18.3: {} xml-js@1.6.11: dependencies: diff --git a/src/widgets/truenas/component.jsx b/src/widgets/truenas/component.jsx index 12ceef564..712a5115b 100644 --- a/src/widgets/truenas/component.jsx +++ b/src/widgets/truenas/component.jsx @@ -12,8 +12,8 @@ export default function Component({ service }) { const { data: alertData, error: alertError } = useWidgetAPI(widget, "alerts"); const { data: statusData, error: statusError } = useWidgetAPI(widget, "status"); - const { data: poolsData, error: poolsError } = useWidgetAPI(widget, widget?.enablePools ? "pools" : null); - const { data: datasetData, error: datasetError } = useWidgetAPI(widget, widget?.enablePools ? "dataset" : null); + const { data: poolsData, error: poolsError } = useWidgetAPI(widget, widget?.enablePools ? "pools" : ""); + const { data: datasetData, error: datasetError } = useWidgetAPI(widget, widget?.enablePools ? "dataset" : ""); if (alertError || statusError || poolsError) { const finalError = alertError ?? statusError ?? poolsError ?? datasetError; diff --git a/src/widgets/truenas/proxy.js b/src/widgets/truenas/proxy.js new file mode 100644 index 000000000..ebc5299ef --- /dev/null +++ b/src/widgets/truenas/proxy.js @@ -0,0 +1,172 @@ +import WebSocket from "ws"; + +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import { formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers"; +import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; +import validateWidgetData from "utils/proxy/validate-widget-data"; +import widgets from "widgets/widgets"; + +const logger = createLogger("truenasProxyHandler"); + +function waitForEvent(ws, handler, { event = "message", parseJson = true } = {}) { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + cleanup(); + reject(new Error("TrueNAS websocket wait timed out")); + }, 10000); + + const handleEvent = (payload) => { + try { + let parsed = payload; + if (parseJson) { + if (Buffer.isBuffer(payload)) { + parsed = JSON.parse(payload.toString()); + } else if (typeof payload === "string") { + parsed = JSON.parse(payload); + } + logger.info("Received TrueNAS websocket message: %o", parsed); + } else { + logger.info("Received TrueNAS websocket message: %o", payload); + } + const handlerResult = handler(parsed); + if (handlerResult !== undefined) { + cleanup(); + if (handlerResult instanceof Error) { + reject(handlerResult); + } else { + resolve(handlerResult); + } + } + } catch (err) { + cleanup(); + reject(err); + } + }; + + const handleError = (err) => { + cleanup(); + logger.error("TrueNAS websocket error: %s", err?.message ?? err); + reject(err); + }; + + const handleClose = () => { + cleanup(); + logger.error("TrueNAS websocket connection closed unexpectedly"); + reject(new Error("TrueNAS websocket closed the connection")); + }; + + function cleanup() { + clearTimeout(timeout); + ws.off(event, handleEvent); + ws.off("error", handleError); + ws.off("close", handleClose); + } + + ws.on(event, handleEvent); + ws.on("error", handleError); + ws.on("close", handleClose); + }); +} + +let nextId = 1; +async function sendMethod(ws, method, params = []) { + const id = nextId++; + const payload = { jsonrpc: "2.0", id, method, params }; + logger.info("Sending TrueNAS websocket method %s with id %d", method, id); + ws.send(JSON.stringify(payload)); + + return waitForEvent(ws, (message) => { + if (message?.id !== id) return undefined; + if (message?.error) { + return new Error(message.error?.message || JSON.stringify(message.error)); + } + return message?.result ?? message; + }); +} + +async function authenticate(ws, widget) { + if (widget?.key) { + try { + const apiKeyResult = await sendMethod(ws, "auth.login_with_api_key", [widget.key]); + if (apiKeyResult === true) return; + logger.warn("TrueNAS API key authentication failed, falling back to username/password when available."); + } catch (err) { + logger.warn("TrueNAS API key authentication failed: %s", err?.message ?? err); + } + } + + if (widget?.username && widget?.password) { + const loginResult = await sendMethod(ws, "auth.login", [widget.username, widget.password]); + if (loginResult === true) return; + logger.warn("TrueNAS username/password authentication failed."); + } + + throw new Error("TrueNAS authentication failed"); +} + +export default async function truenasProxyHandler(req, res, map) { + 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, 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" }); + } + + if (!endpoint) { + return res.status(204).end(); + } + + const version = Number(widget.version ?? 1); + if (Number.isNaN(version) || version < 2) { + // Use legacy REST proxy for version 1 + return credentialedProxyHandler(req, res, map); + } + + const mappingEntry = Object.values(widgets[widget.type].mappings).find((mapping) => mapping.endpoint === endpoint); + const wsMethod = mappingEntry.wsMethod; + + if (!wsMethod) { + logger.debug("Missing wsMethod mapping for TrueNAS endpoint %s", endpoint); + return res.status(500).json({ error: "Missing wsMethod mapping." }); + } + + try { + let data; + const wsUrl = new URL(formatApiCall(widgets[widget.type].wsAPI, { ...widget })); + const useSecure = wsUrl.protocol === "https:" || Boolean(widget.key); // API key requires secure connection + if (useSecure && wsUrl.protocol !== "https:") + logger.info("Upgrading TrueNAS websocket connection to secure wss://"); + wsUrl.protocol = useSecure ? "wss:" : "ws:"; + logger.info("Connecting to TrueNAS websocket at %s", wsUrl); + const ws = new WebSocket(wsUrl, { rejectUnauthorized: false }); + await waitForEvent(ws, () => true, { event: "open", parseJson: false }); // wait for open + logger.info("Connected to TrueNAS websocket at %s", wsUrl); + try { + await authenticate(ws, widget); + data = await sendMethod(ws, wsMethod); + } finally { + ws.close(); + } + + if (!validateWidgetData(widget, endpoint, data)) { + return res.status(500).json({ error: { message: "Invalid data", url: sanitizeErrorURL(widget.url), data } }); + } + + if (map) data = map(data); + + return res.status(200).json(data); + } catch (err) { + if (err?.status) { + return res.status(err.status).json({ error: err.message }); + } + logger.warn("Websocket call for TrueNAS failed: %s", err?.message ?? err); + return res.status(500).json({ error: err?.message ?? "TrueNAS websocket call failed" }); + } +} diff --git a/src/widgets/truenas/widget.js b/src/widgets/truenas/widget.js index 528114edb..3f022b745 100644 --- a/src/widgets/truenas/widget.js +++ b/src/widgets/truenas/widget.js @@ -1,32 +1,43 @@ +import truenasProxyHandler from "./proxy"; + import { asJson, jsonArrayFilter } from "utils/proxy/api-helpers"; -import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { api: "{url}/api/v2.0/{endpoint}", - proxyHandler: credentialedProxyHandler, + wsAPI: "{url}/api/current", + proxyHandler: truenasProxyHandler, mappings: { alerts: { endpoint: "alert/list", - map: (data) => ({ - pending: jsonArrayFilter(data, (item) => item?.dismissed === false).length, - }), + wsMethod: "alert.list", + map: (data) => { + if (Array.isArray(data)) { + return { pending: data.filter((item) => item?.dismissed === false).length }; + } + return { pending: jsonArrayFilter(data, (item) => item?.dismissed === false).length }; + }, }, status: { endpoint: "system/info", + wsMethod: "system.info", validate: ["loadavg", "uptime_seconds"], }, pools: { endpoint: "pool", - map: (data) => - asJson(data).map((entry) => ({ + wsMethod: "pool.query", + map: (data) => { + const list = Array.isArray(data) ? data : asJson(data); + return list.map((entry) => ({ id: entry.name, name: entry.name, healthy: entry.healthy, - })), + })); + }, }, dataset: { endpoint: "pool/dataset", + wsMethod: "pool.dataset.query", }, }, }; From a2697bfec03baf89f4e7795561f59556ea5e6614 Mon Sep 17 00:00:00 2001 From: ahpooch <81159041+ahpooch@users.noreply.github.com> Date: Sun, 11 Jan 2026 06:22:52 +0300 Subject: [PATCH 23/27] Documentation: Updated Gatus Widget information (#6180) Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com> --- docs/widgets/services/gatus.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/widgets/services/gatus.md b/docs/widgets/services/gatus.md index 3918b9f3d..3936d7512 100644 --- a/docs/widgets/services/gatus.md +++ b/docs/widgets/services/gatus.md @@ -3,6 +3,8 @@ title: Gatus description: Gatus Widget Configuration --- +Learn more about [Gatus](https://github.com/TwiN/gatus). + Allowed fields: `["up", "down", "uptime"]`. ```yaml From f9d920a8fbfc8576d20e0f071224cd8bad4fac18 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:04:28 -0800 Subject: [PATCH 24/27] Clarify security recommendations in documentation --- README.md | 2 +- docs/installation/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 090973bd1..4d2497a0f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ For configuration options, examples and more, [please check out the homepage doc ## Security Notice 🔒 -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. +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. If Homepage is reachable from any untrusted network, it **must** sit behind a reverse proxy (and/or VPN) that enforces authentication, TLS, and strictly validates Host headers. The built-in host check in Homepage is a best-effort guard and should not be treated as security when exposed publicly. ## With Docker diff --git a/docs/installation/index.md b/docs/installation/index.md index f082845b1..b6bfb23b5 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -37,4 +37,4 @@ The value is a comma-separated (no spaces) list of allowed hosts (sometimes with If you are seeing errors about host validation, check the homepage logs and ensure that the host exactly as output in the logs is in the `HOMEPAGE_ALLOWED_HOSTS` list. -This can be disabled by setting `HOMEPAGE_ALLOWED_HOSTS` to `*` but this is not recommended. +This can be disabled by setting `HOMEPAGE_ALLOWED_HOSTS` to `*` but this is not recommended. Public deployments must rely on a reverse proxy (and/or VPN) that enforces authentication, TLS, and unexpected Host headers; the built-in host check is a best-effort guard for local setups and is not a substitute for edge protections. From 9076cfd7e7658405459d44223eb9d47e4887227a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:39:15 -0800 Subject: [PATCH 25/27] Enhancement: support netalertx v26.1.17 breaking changes (#6196) --- docs/widgets/services/netalertx.md | 10 ++++++++-- src/utils/config/service-helpers.js | 3 ++- src/widgets/netalertx/component.jsx | 4 +++- src/widgets/netalertx/widget.js | 7 +++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/widgets/services/netalertx.md b/docs/widgets/services/netalertx.md index 1f618182c..a67de624c 100644 --- a/docs/widgets/services/netalertx.md +++ b/docs/widgets/services/netalertx.md @@ -9,11 +9,17 @@ _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. +Provide the `API_TOKEN` (f.k.a. `SYNC_api_token`) as the `key` in your config. + +| NetAlertX Version | Homepage Widget Version | +| ----------------- | ----------------------- | +| < v26.1.17 | 1 (default) | +| > v26.1.17 | 2 | ```yaml widget: type: netalertx url: http://ip:port - key: netalertxsyncapitoken # optional, only if password is enabled + key: yournetalertxapitoken + version: 2 # optional, default is 1 ``` diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index b47e1b7c4..dc141cd91 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -314,7 +314,7 @@ export function cleanServiceGroups(groups) { // gamedig gameToken, - // authentik, beszel, glances, immich, komga, mealie, pihole, pfsense, speedtest + // authentik, beszel, glances, immich, komga, mealie, netalertx, pihole, pfsense, speedtest version, // glances @@ -559,6 +559,7 @@ export function cleanServiceGroups(groups) { "immich", "komga", "mealie", + "netalertx", "pfsense", "pihole", "speedtest", diff --git a/src/widgets/netalertx/component.jsx b/src/widgets/netalertx/component.jsx index 786db9a55..21885a627 100644 --- a/src/widgets/netalertx/component.jsx +++ b/src/widgets/netalertx/component.jsx @@ -9,7 +9,9 @@ export default function Component({ service }) { const { widget } = service; - const { data: netalertxData, error: netalertxError } = useWidgetAPI(widget, "data"); + const dataEndpoint = widget?.version > 1 ? "datav2" : "data"; + + const { data: netalertxData, error: netalertxError } = useWidgetAPI(widget, dataEndpoint); if (netalertxError) { return ; diff --git a/src/widgets/netalertx/widget.js b/src/widgets/netalertx/widget.js index 033d3d81d..176a8c4c2 100644 --- a/src/widgets/netalertx/widget.js +++ b/src/widgets/netalertx/widget.js @@ -1,12 +1,15 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed"; const widget = { - api: "{url}/php/server/devices.php?action=getDevicesTotals", + api: "{url}/{endpoint}", proxyHandler: credentialedProxyHandler, mappings: { data: { - endpoint: "data", + endpoint: "php/server/devices.php?action=getDevicesTotals", + }, + datav2: { + endpoint: "devices/totals", }, }, }; From 4349f30169b210efa2aeba10bec1fdda9a90f74a Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 18 Jan 2026 20:47:30 -0800 Subject: [PATCH 26/27] Enhancement: booklore service widget (#6202) --- docs/widgets/services/booklore.md | 16 +++ docs/widgets/services/index.md | 1 + mkdocs.yml | 1 + public/locales/en/common.json | 6 ++ src/widgets/booklore/component.jsx | 43 ++++++++ src/widgets/booklore/proxy.js | 156 +++++++++++++++++++++++++++++ src/widgets/booklore/widget.js | 8 ++ src/widgets/components.js | 1 + src/widgets/widgets.js | 2 + 9 files changed, 234 insertions(+) create mode 100644 docs/widgets/services/booklore.md create mode 100644 src/widgets/booklore/component.jsx create mode 100644 src/widgets/booklore/proxy.js create mode 100644 src/widgets/booklore/widget.js diff --git a/docs/widgets/services/booklore.md b/docs/widgets/services/booklore.md new file mode 100644 index 000000000..137aa1e52 --- /dev/null +++ b/docs/widgets/services/booklore.md @@ -0,0 +1,16 @@ +--- +title: Booklore +description: Booklore Widget Configuration +--- + +Learn more about [Booklore](https://github.com/booklore-app/booklore). + +The widget authenticates with your Booklore credentials to surface total libraries, books, and reading progress counts for your account. + +```yaml +widget: + type: booklore + url: https://booklore.host.or.ip + username: username + password: password +``` diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md index 8ae84ee9e..8110a7fc4 100644 --- a/docs/widgets/services/index.md +++ b/docs/widgets/services/index.md @@ -17,6 +17,7 @@ You can also find a list of all available service widgets in the sidebar navigat - [Azure DevOps](azuredevops.md) - [Backrest](backrest.md) - [Bazarr](bazarr.md) +- [Booklore](booklore.md) - [Beszel](beszel.md) - [Caddy](caddy.md) - [Calendar](calendar.md) diff --git a/mkdocs.yml b/mkdocs.yml index b3865d3c0..2a21cbfc6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,6 +41,7 @@ nav: - widgets/services/azuredevops.md - widgets/services/backrest.md - widgets/services/bazarr.md + - widgets/services/booklore.md - widgets/services/beszel.md - widgets/services/caddy.md - widgets/services/calendar.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 22d2134f6..7adcd4c12 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -793,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/src/widgets/booklore/component.jsx b/src/widgets/booklore/component.jsx new file mode 100644 index 000000000..fc8b53483 --- /dev/null +++ b/src/widgets/booklore/component.jsx @@ -0,0 +1,43 @@ +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: bookloreData, error: bookloreError } = useWidgetAPI(widget); + + if (bookloreError) { + return ; + } + + if (!bookloreData) { + return ( + + + + + + + ); + } + + const stats = { + libraries: bookloreData.libraries ?? 0, + books: bookloreData.books ?? 0, + reading: bookloreData.reading ?? 0, + finished: bookloreData.finished ?? 0, + }; + + return ( + + + + + + + ); +} diff --git a/src/widgets/booklore/proxy.js b/src/widgets/booklore/proxy.js new file mode 100644 index 000000000..ccf2b2b69 --- /dev/null +++ b/src/widgets/booklore/proxy.js @@ -0,0 +1,156 @@ +import cache from "memory-cache"; + +import getServiceWidget from "utils/config/service-helpers"; +import createLogger from "utils/logger"; +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import widgets from "widgets/widgets"; + +const proxyName = "bookloreProxyHandler"; +const sessionTokenCacheKey = `${proxyName}__sessionToken`; +const logger = createLogger(proxyName); + +async function login(widget, service) { + if (!widget.username || !widget.password) { + logger.debug("Missing credentials for Booklore service '%s'", service); + return { accessToken: false }; + } + + const api = widgets?.[widget.type]?.api; + const loginUrl = new URL(formatApiCall(api, { ...widget, endpoint: "auth/login" })); + + const [status, , data] = await httpProxy(loginUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + accept: "application/json", + }, + body: JSON.stringify({ + username: widget.username, + password: widget.password, + }), + }); + + if (status !== 200) { + logger.debug("Booklore login failed for service '%s' with status %d", service, status); + return { accessToken: false }; + } + + try { + const { accessToken } = JSON.parse(data.toString()); + + if (accessToken) { + // access tokens are valid for ~10 hours; refresh 1 minute early. + cache.put(`${sessionTokenCacheKey}.${service}`, accessToken, 10 * 60 * 60 * 1000 - 60 * 1000); + return { accessToken }; + } + } catch (e) { + logger.error("Unable to login to Booklore API: %s", e); + } + + return { accessToken: false }; +} + +async function apiCall(widget, endpoint, service) { + const cacheKey = `${sessionTokenCacheKey}.${service}`; + let accessToken = cache.get(cacheKey); + + if (!accessToken) { + ({ accessToken } = await login(widget, service)); + } + + if (!accessToken) { + return { status: 401, data: null }; + } + + const headers = { + accept: "application/json", + Authorization: `Bearer ${accessToken}`, + }; + + const url = new URL(formatApiCall(widgets[widget.type].api, { ...widget, endpoint })); + let [status, , data] = await httpProxy(url, { + method: "GET", + headers, + }); + + if (status === 401 || status === 403) { + logger.debug("Booklore API rejected the request, attempting to obtain new session token"); + const refreshedToken = (await login(widget, service)).accessToken; + if (!refreshedToken) { + return { status, data: null }; + } + headers.Authorization = `Bearer ${refreshedToken}`; + [status, , data] = await httpProxy(url, { + method: "GET", + headers, + }); + } + + if (status !== 200) { + logger.error("Error getting data from Booklore: %s status %d. Data: %s", url, status, data); + return { status, data: null }; + } + + try { + return { status, data: JSON.parse(data.toString()) }; + } catch (e) { + logger.error("Error parsing Booklore response: %s", e); + } + + return { status, data: null }; +} + +function summarizeStatuses(books = []) { + return books.reduce( + (accumulator, book) => { + const status = (book?.readStatus || "").toString().toUpperCase(); + if (status === "READING") accumulator.reading += 1; + else if (status === "READ") accumulator.finished += 1; + return accumulator; + }, + { reading: 0, finished: 0 }, + ); +} + +export default async function bookloreProxyHandler(req, res) { + 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, 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" }); + } + + if (!widget.username || !widget.password) { + logger.debug("Missing credentials for Booklore widget in service '%s'", service); + return res.status(400).json({ error: "Missing Booklore credentials" }); + } + + const { data: librariesData, status: librariesStatus } = await apiCall(widget, "libraries", service); + + if (librariesStatus !== 200 || !Array.isArray(librariesData)) { + return res.status(librariesStatus || 500).send(librariesData || { error: "Error fetching libraries" }); + } + + const { data: booksData, status: booksStatus } = await apiCall(widget, "books", service); + + if (booksStatus !== 200 || !Array.isArray(booksData)) { + return res.status(booksStatus || 500).send(booksData || { error: "Error fetching books" }); + } + + const { reading, finished } = summarizeStatuses(booksData); + + return res.status(200).send({ + libraries: librariesData.length, + books: booksData.length, + reading, + finished, + }); +} diff --git a/src/widgets/booklore/widget.js b/src/widgets/booklore/widget.js new file mode 100644 index 000000000..3ff862e31 --- /dev/null +++ b/src/widgets/booklore/widget.js @@ -0,0 +1,8 @@ +import bookloreProxyHandler from "./proxy"; + +const widget = { + api: "{url}/api/v1/{endpoint}", + proxyHandler: bookloreProxyHandler, +}; + +export default widget; diff --git a/src/widgets/components.js b/src/widgets/components.js index 911be5fb8..30bafd50a 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -12,6 +12,7 @@ const components = { backrest: dynamic(() => import("./backrest/component")), bazarr: dynamic(() => import("./bazarr/component")), beszel: dynamic(() => import("./beszel/component")), + booklore: dynamic(() => import("./booklore/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 dcc0ba65e..f7947de78 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -9,6 +9,7 @@ import azuredevops from "./azuredevops/widget"; import backrest from "./backrest/widget"; import bazarr from "./bazarr/widget"; import beszel from "./beszel/widget"; +import booklore from "./booklore/widget"; import caddy from "./caddy/widget"; import calendar from "./calendar/widget"; import calibreweb from "./calibreweb/widget"; @@ -156,6 +157,7 @@ const widgets = { azuredevops, backrest, bazarr, + booklore, beszel, caddy, calibreweb, From 1c504bc3500e986792bae8a5d7ff30a230e5860e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 18 Jan 2026 21:35:55 -0800 Subject: [PATCH 27/27] New Crowdin translations by GitHub Action (#6074) Co-authored-by: Crowdin Bot --- public/locales/af/common.json | 11 +- public/locales/ar/common.json | 9 +- public/locales/bg/common.json | 49 ++-- public/locales/ca/common.json | 9 +- public/locales/cs/common.json | 9 +- public/locales/da/common.json | 9 +- public/locales/de/common.json | 15 +- public/locales/el/common.json | 9 +- public/locales/eo/common.json | 9 +- public/locales/es/common.json | 23 +- public/locales/eu/common.json | 9 +- public/locales/fi/common.json | 9 +- public/locales/fr/common.json | 9 +- public/locales/he/common.json | 9 +- public/locales/hi/common.json | 9 +- public/locales/hr/common.json | 9 +- public/locales/hu/common.json | 9 +- public/locales/id/common.json | 9 +- public/locales/it/common.json | 9 +- public/locales/ja/common.json | 9 +- public/locales/ko/common.json | 9 +- public/locales/lv/common.json | 9 +- public/locales/ms/common.json | 9 +- public/locales/nl/common.json | 9 +- public/locales/no/common.json | 9 +- public/locales/pl/common.json | 415 +++++++++++++++-------------- public/locales/pt/common.json | 9 +- public/locales/pt_BR/common.json | 9 +- public/locales/ro/common.json | 9 +- public/locales/ru/common.json | 9 +- public/locales/sk/common.json | 9 +- public/locales/sl/common.json | 9 +- public/locales/sr/common.json | 23 +- public/locales/sv/common.json | 9 +- public/locales/te/common.json | 9 +- public/locales/th/common.json | 9 +- public/locales/tr/common.json | 9 +- public/locales/uk/common.json | 9 +- public/locales/vi/common.json | 9 +- public/locales/yue/common.json | 9 +- public/locales/zh-Hans/common.json | 15 +- public/locales/zh-Hant/common.json | 9 +- 42 files changed, 580 insertions(+), 286 deletions(-) diff --git a/public/locales/af/common.json b/public/locales/af/common.json index af5ffbd79..1c76f1798 100644 --- a/public/locales/af/common.json +++ b/public/locales/af/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Op", "pending": "Afwagtend", - "down": "Af" + "down": "Af", + "ok": "Ok" }, "healthchecks": { "new": "Nuut", @@ -769,7 +770,7 @@ "gross_percent_today": "Vandag", "gross_percent_1y": "Een jaar", "gross_percent_max": "Alle tyd", - "net_worth": "Net Worth" + "net_worth": "Netto Waarde" }, "audiobookshelf": { "podcasts": "Podsendinge", @@ -792,6 +793,12 @@ "categories": "Kategorieë", "series": "Reekse" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Tou", "downloadBytesRemaining": "Oorblywende", diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json index 5412cb9d5..4551ed130 100644 --- a/public/locales/ar/common.json +++ b/public/locales/ar/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "جديد(ة)", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json index 16e34e686..a49aa6fce 100644 --- a/public/locales/bg/common.json +++ b/public/locales/bg/common.json @@ -63,7 +63,7 @@ "wlan_users": "WLAN Потребители", "up": "UP", "down": "DOWN", - "wait": "Please wait", + "wait": "Моля изчакайте", "empty_data": "Неизвестен статус на подсистема" }, "docker": { @@ -83,7 +83,7 @@ "partial": "Частично" }, "ping": { - "error": "Error", + "error": "Грешка", "ping": "Пинг", "down": "Down", "up": "Up", @@ -91,11 +91,11 @@ }, "siteMonitor": { "http_status": "HTTP статус", - "error": "Error", + "error": "Грешка", "response": "Отговор", "down": "Down", "up": "Up", - "not_available": "Not Available" + "not_available": "Не е налично" }, "emby": { "playing": "Възпроизвежда", @@ -111,7 +111,7 @@ "offline": "Offline", "offline_alt": "Offline", "online": "Онлайн", - "total": "Total", + "total": "Общо", "unknown": "Unknown" }, "evcc": { @@ -133,7 +133,7 @@ "unread": "Непрочетени" }, "fritzbox": { - "connectionStatus": "Status", + "connectionStatus": "Статус", "connectionStatusUnconfigured": "Неконфигуриран", "connectionStatusConnecting": "Свързване", "connectionStatusAuthenticating": "Удостоверяване", @@ -141,7 +141,7 @@ "connectionStatusDisconnecting": "Прекъсване на връзката", "connectionStatusDisconnected": "Не е свързан", "connectionStatusConnected": "Свързан", - "uptime": "Uptime", + "uptime": "Време на работа", "maxDown": "Макс сваляне", "maxUp": "Макс качване", "down": "Down", @@ -170,8 +170,8 @@ "tautulli": { "playing": "Playing", "transcoding": "Transcoding", - "bitrate": "Bitrate", - "no_active": "No Active Streams", + "bitrate": "Битрейт", + "no_active": "Няма активни потоци", "plex_connection_error": "Провери връзка с Plex" }, "omada": { @@ -189,7 +189,7 @@ "plex": { "streams": "Активни Потоци", "albums": "Албуми", - "movies": "Movies", + "movies": "Филми", "tv": "Сериали" }, "sabnzbd": { @@ -362,8 +362,8 @@ }, "trilium": { "version": "Version", - "notesCount": "Notes", - "dbSize": "Database Size", + "notesCount": "Бележки", + "dbSize": "Размер на базата данни", "unknown": "Unknown" }, "navidrome": { @@ -373,7 +373,7 @@ "npm": { "enabled": "Активирано", "disabled": "Деактивирано", - "total": "Total" + "total": "Общо" }, "coinmarketcap": { "configure": "Настрой за следене една или повече крипто валути", @@ -384,7 +384,7 @@ }, "gotify": { "apps": "Приложения", - "clients": "Clients", + "clients": "Клиенти", "messages": "Съобщения" }, "prowlarr": { @@ -405,7 +405,7 @@ "transferRate": "Rate" }, "mastodon": { - "user_count": "Users", + "user_count": "Потребители", "status_count": "Posts", "domain_count": "Domains" }, @@ -416,17 +416,17 @@ }, "minecraft": { "players": "Играчи", - "version": "Version", - "status": "Status", - "up": "Online", - "down": "Offline" + "version": "Версия", + "status": "Статус", + "up": "Онлайн", + "down": "Офлайн" }, "miniflux": { "read": "Read", "unread": "Unread" }, "authentik": { - "users": "Users", + "users": "Потребители", "loginsLast24H": "Logins (24h)", "failedLoginsLast24H": "Failed Logins (24h)" }, @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Нови", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json index 455dfa406..7add4b383 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nou", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json index 080750a55..8d3a193ef 100644 --- a/public/locales/cs/common.json +++ b/public/locales/cs/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nové", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/da/common.json b/public/locales/da/common.json index e13b6339c..50c1b7d17 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Ny", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/de/common.json b/public/locales/de/common.json index db8b602ea..6c5c23741 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -45,9 +45,9 @@ "free": "Frei", "used": "In Benutzung", "load": "Last", - "temp": "TEMP", + "temp": "Temp", "max": "Max", - "uptime": "BETRIEBSZEIT" + "uptime": "Betriebszeit" }, "unifi": { "users": "Benutzer", @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Online", "pending": "Wartend", - "down": "Offline" + "down": "Offline", + "ok": "Ok" }, "healthchecks": { "new": "Neu", @@ -602,7 +603,7 @@ "pangolin": { "orgs": "Orgs", "sites": "Sites", - "resources": "Resources", + "resources": "Ressourcen", "targets": "Targets", "traffic": "Traffic", "in": "In", @@ -792,6 +793,12 @@ "categories": "Kategorien", "series": "Serien" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Warteschlange", "downloadBytesRemaining": "Verbleibend", diff --git a/public/locales/el/common.json b/public/locales/el/common.json index 81befbefa..1ee0ff903 100644 --- a/public/locales/el/common.json +++ b/public/locales/el/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/eo/common.json b/public/locales/eo/common.json index 561d5866f..fe2b6bf6f 100644 --- a/public/locales/eo/common.json +++ b/public/locales/eo/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 7a5723ba0..62b604ca4 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Activo", "pending": "Pendiente", - "down": "Inactivo" + "down": "Inactivo", + "ok": "Ok" }, "healthchecks": { "new": "Nuevo", @@ -601,12 +602,12 @@ }, "pangolin": { "orgs": "Orgs", - "sites": "Sites", - "resources": "Resources", - "targets": "Targets", - "traffic": "Traffic", - "in": "In", - "out": "Out" + "sites": "Sitios", + "resources": "Recursos", + "targets": "Destinos", + "traffic": "Tráfico", + "in": "Entrante", + "out": "Saliente" }, "peanut": { "battery_charge": "Carga de la batería", @@ -769,7 +770,7 @@ "gross_percent_today": "Hoy", "gross_percent_1y": "Un año", "gross_percent_max": "Todo el tiempo", - "net_worth": "Net Worth" + "net_worth": "Patrimonio neto" }, "audiobookshelf": { "podcasts": "Podcasts", @@ -792,6 +793,12 @@ "categories": "Categorías", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "En cola", "downloadBytesRemaining": "Restante", diff --git a/public/locales/eu/common.json b/public/locales/eu/common.json index 06f0dcef3..0037bbba8 100644 --- a/public/locales/eu/common.json +++ b/public/locales/eu/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/fi/common.json b/public/locales/fi/common.json index 01eaf7c41..80aa7fef5 100644 --- a/public/locales/fi/common.json +++ b/public/locales/fi/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 2eb8e190f..9366c5f45 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "En ligne", "pending": "En attente", - "down": "Hors ligne" + "down": "Hors ligne", + "ok": "Ok" }, "healthchecks": { "new": "Nouveau", @@ -792,6 +793,12 @@ "categories": "Catégories", "series": "Séries" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "File d'attente", "downloadBytesRemaining": "Restant", diff --git a/public/locales/he/common.json b/public/locales/he/common.json index b8cd89e28..20d0ddc8f 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "למעלה", "pending": "ממתין", - "down": "למטה" + "down": "למטה", + "ok": "Ok" }, "healthchecks": { "new": "חדש", @@ -792,6 +793,12 @@ "categories": "קטגוריות", "series": "סדרות" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "תור", "downloadBytesRemaining": "נותר", diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index ea5cb9d39..67339b73d 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index 1484ab915..fe9208a46 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Aktivno", "pending": "U tijeku", - "down": "Neaktivno" + "down": "Neaktivno", + "ok": "Ok" }, "healthchecks": { "new": "Novo", @@ -792,6 +793,12 @@ "categories": "Kategorije", "series": "Serije" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Red čekanja", "downloadBytesRemaining": "Preostalo", diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index cd7e2fff9..dc64a89c5 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Fut", "pending": "Függőben lévő", - "down": "Leállt" + "down": "Leállt", + "ok": "Ok" }, "healthchecks": { "new": "Új", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/id/common.json b/public/locales/id/common.json index e031c2c98..ce48cec56 100644 --- a/public/locales/id/common.json +++ b/public/locales/id/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Baru", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 413231fda..30b7cf487 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nuovo", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 0463a362f..aec5ec8a3 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "新着", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json index bcf98bc70..d82c7f106 100644 --- a/public/locales/ko/common.json +++ b/public/locales/ko/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "업", "pending": "대기 중", - "down": "다운" + "down": "다운", + "ok": "Ok" }, "healthchecks": { "new": "신규", @@ -792,6 +793,12 @@ "categories": "카테고리", "series": "시리즈" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "대기열", "downloadBytesRemaining": "남음", diff --git a/public/locales/lv/common.json b/public/locales/lv/common.json index 9e4180c71..89b830082 100644 --- a/public/locales/lv/common.json +++ b/public/locales/lv/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/ms/common.json b/public/locales/ms/common.json index 4fea074cb..bfbb76f57 100644 --- a/public/locales/ms/common.json +++ b/public/locales/ms/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Baharu", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index 1039c2dab..37b401080 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Online", "pending": "In afwachting", - "down": "Offline" + "down": "Offline", + "ok": "Ok" }, "healthchecks": { "new": "Nieuw", @@ -792,6 +793,12 @@ "categories": "Categorieën", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Wachtrij", "downloadBytesRemaining": "Resterend", diff --git a/public/locales/no/common.json b/public/locales/no/common.json index 38604af56..a93165788 100644 --- a/public/locales/no/common.json +++ b/public/locales/no/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Ny", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index f33699de4..21869407d 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -61,7 +61,7 @@ "wlan_devices": "Urządzenia WLAN", "lan_users": "Użytkownicy LAN", "wlan_users": "Użytkownicy WLAN", - "up": "UP", + "up": "DZIAŁA", "down": "Pobieranie", "wait": "Proszę czekać", "empty_data": "Status podsystemu nieznany" @@ -69,7 +69,7 @@ "docker": { "rx": "Rx", "tx": "Tx", - "mem": "MEM", + "mem": "PAM", "cpu": "Procesor", "running": "Działa", "offline": "Nieosiągalny", @@ -93,8 +93,8 @@ "http_status": "Status HTTP", "error": "Błąd", "response": "Odpowiedź", - "down": "Down", - "up": "Up", + "down": "Nie działa", + "up": "Działa", "not_available": "Niedostępny" }, "emby": { @@ -111,8 +111,8 @@ "offline": "Offline", "offline_alt": "Offline", "online": "Dostępny", - "total": "Total", - "unknown": "Unknown" + "total": "Razem", + "unknown": "Nieznany" }, "evcc": { "pv_power": "Produkcja", @@ -141,11 +141,11 @@ "connectionStatusDisconnecting": "Rozłączanie", "connectionStatusDisconnected": "Rozłączono", "connectionStatusConnected": "Połączono", - "uptime": "Uptime", + "uptime": "Czas działania", "maxDown": "Maks. Pobieranie", "maxUp": "Maks. Wysyłanie", - "down": "Down", - "up": "Up", + "down": "Nie działa", + "up": "Działa", "received": "Odebrane", "sent": "Wysłane", "externalIPAddress": "Pub. IP", @@ -168,10 +168,10 @@ "passes": "Przebiegi" }, "tautulli": { - "playing": "Playing", - "transcoding": "Transcoding", + "playing": "Odtwarza", + "transcoding": "Transkoduje", "bitrate": "Bitrate", - "no_active": "No Active Streams", + "no_active": "Brak aktywnych strumieni", "plex_connection_error": "Sprawdź połączenie z Plex" }, "omada": { @@ -193,24 +193,24 @@ "tv": "Seriale" }, "sabnzbd": { - "rate": "Rate", + "rate": "Szybkość", "queue": "Kolejka", "timeleft": "Pozostało" }, "rutorrent": { "active": "Aktywny", - "upload": "Upload", + "upload": "Wysyłanie", "download": "Pobieranie" }, "transmission": { "download": "Pobieranie", - "upload": "Upload", + "upload": "Wysyłanie", "leech": "Leech", "seed": "Seed" }, "qbittorrent": { - "download": "Download", - "upload": "Upload", + "download": "Pobieranie", + "upload": "Wysyłanie", "leech": "Leech", "seed": "Seed" }, @@ -223,8 +223,8 @@ "invalid": "Nieprawidłowy" }, "deluge": { - "download": "Download", - "upload": "Upload", + "download": "Pobieranie", + "upload": "Wysyłanie", "leech": "Leech", "seed": "Seed" }, @@ -233,8 +233,8 @@ "cachemissbytes": "Straty cache'u" }, "downloadstation": { - "download": "Download", - "upload": "Upload", + "download": "Pobieranie", + "upload": "Wysyłanie", "leech": "Leech", "seed": "Seed" }, @@ -251,16 +251,16 @@ "queued": "W kolejce", "movies": "Filmy", "queue": "Kolejka", - "unknown": "Unknown" + "unknown": "Nieznane" }, "lidarr": { - "wanted": "Wanted", - "queued": "Queued", + "wanted": "Poszukiwane", + "queued": "W kolejce", "artists": "Artyści" }, "readarr": { - "wanted": "Wanted", - "queued": "Queued", + "wanted": "Poszukiwane", + "queued": "W kolejce", "books": "Książki" }, "bazarr": { @@ -276,7 +276,7 @@ "pending": "Oczekujące", "approved": "Zaakceptowane", "available": "Dostępne", - "issues": "Open Issues" + "issues": "Otwarte zgłoszenia" }, "overseerr": { "pending": "Oczekujące", @@ -285,8 +285,8 @@ "available": "Dostępne" }, "netalertx": { - "total": "Total", - "connected": "Connected", + "total": "Razem", + "connected": "Połączono", "new_devices": "Nowe urządzenia", "down_alerts": "Alerty niedostępności" }, @@ -303,20 +303,20 @@ "latency": "Opóźnienia" }, "speedtest": { - "upload": "Upload", - "download": "Download", + "upload": "Wysyłanie", + "download": "Pobieranie", "ping": "Ping" }, "portainer": { - "running": "Running", + "running": "Działa", "stopped": "Zatrzymane", - "total": "Total" + "total": "Razem" }, "suwayomi": { "download": "Pobrano", "nondownload": "Niepobrane", - "read": "Read", - "unread": "Unread", + "read": "Przeczytane", + "unread": "Nieprzeczytane", "downloadedread": "Pobrane i przeczytane", "downloadedunread": "Pobrane i nieprzeczytane", "nondownloadedread": "Niepobrane i przeczytane", @@ -337,7 +337,7 @@ "ago": "{{value}} temu" }, "technitium": { - "totalQueries": "Queries", + "totalQueries": "Zapytania", "totalNoError": "Sukces", "totalServerFailure": "Porażki", "totalNxDomain": "Domeny NX", @@ -345,12 +345,12 @@ "totalAuthoritative": "Autorytatywne", "totalRecursive": "Rekursywne", "totalCached": "Zbuforowane", - "totalBlocked": "Blocked", + "totalBlocked": "Zablokowane", "totalDropped": "Upuszczone", "totalClients": "Klienci" }, "tdarr": { - "queue": "Queue", + "queue": "W kolejce", "processed": "Przetworzone", "errored": "Błędne", "saved": "Zapisane" @@ -364,7 +364,7 @@ "version": "Wersja", "notesCount": "Notatki", "dbSize": "Rozmiar bazy danych", - "unknown": "Unknown" + "unknown": "Nieznane" }, "navidrome": { "nothing_streaming": "Brak aktywnych strumieni", @@ -373,7 +373,7 @@ "npm": { "enabled": "Włączone", "disabled": "Wyłączone", - "total": "Total" + "total": "Razem" }, "coinmarketcap": { "configure": "Wybierz jedną lub więcej kryptowalut do śledzenia", @@ -390,19 +390,19 @@ "prowlarr": { "enableIndexers": "Indeksery", "numberOfGrabs": "Pochwycenia", - "numberOfQueries": "Queries", + "numberOfQueries": "Zapytania", "numberOfFailGrabs": "Nieudane pochwycenia", "numberOfFailQueries": "Nieudane zapytania" }, "jackett": { "configured": "Skonfigurowane", - "errored": "Errored" + "errored": "Z błędami" }, "strelaysrv": { "numActiveSessions": "Sesje", "numConnections": "Połączenia", "dataRelayed": "Przekazane", - "transferRate": "Rate" + "transferRate": "Szybkość" }, "mastodon": { "user_count": "Użytkownicy", @@ -410,9 +410,9 @@ "domain_count": "Domeny" }, "medusa": { - "wanted": "Wanted", - "queued": "Queued", - "series": "Series" + "wanted": "Poszukiwane", + "queued": "W kolejce", + "series": "Seriale" }, "minecraft": { "players": "Gracze", @@ -423,7 +423,7 @@ }, "miniflux": { "read": "Przeczytane", - "unread": "Unread" + "unread": "Nieprzeczytane" }, "authentik": { "users": "Użytkownicy", @@ -443,14 +443,14 @@ "temp": "TEMP.", "_temp": "Temperatura", "warn": "Ostrzeżenie", - "uptime": "UP", - "total": "Total", + "uptime": "DZIAŁA", + "total": "Razem", "free": "Wolne", - "used": "Used", + "used": "Użyte", "days": "d", - "hours": "h", + "hours": "godz", "crit": "Krytyczyny", - "read": "Read", + "read": "Odczyt", "write": "Zapis", "gpu": "GPU", "mem": "Pamięć", @@ -530,15 +530,16 @@ "up_to_date": "Aktualny", "child_bridges": "Mostki podrzędne", "child_bridges_status": "{{ok}}/{{total}}", - "up": "Up", - "pending": "Pending", - "down": "Down" + "up": "Działa", + "pending": "Oczekujące", + "down": "Nie działa", + "ok": "Ok" }, "healthchecks": { "new": "Nowy", - "up": "Up", + "up": "Działa", "grace": "W okresie karencji", - "down": "Down", + "down": "Nie działa", "paused": "Wstrzymane", "status": "Status", "last_ping": "Ostatni ping", @@ -550,63 +551,63 @@ "containers_failed": "Niepowodzenie" }, "autobrr": { - "approvedPushes": "Approved", + "approvedPushes": "Zaakceptowane", "rejectedPushes": "Odrzucone", "filters": "Filtry", - "indexers": "Indexers" + "indexers": "Indeksery" }, "tubearchivist": { - "downloads": "Queue", + "downloads": "W kolejce", "videos": "Pliki wideo", "channels": "Kanały", "playlists": "Playlisty" }, "truenas": { "load": "Obciążenie systemu", - "uptime": "Uptime", - "alerts": "Alerts" + "uptime": "Czas działania", + "alerts": "Alerty" }, "pyload": { "speed": "Prędkość", - "active": "Active", - "queue": "Queue", - "total": "Total" + "active": "Aktywne", + "queue": "W kolejce", + "total": "Razem" }, "gluetun": { "public_ip": "Adres publiczny", "region": "Region", "country": "Państwo", - "port_forwarded": "Port Forwarded" + "port_forwarded": "Port otwarty" }, "hdhomerun": { - "channels": "Channels", + "channels": "Kanały", "hd": "HD", "tunerCount": "Tunery", "channelNumber": "Kanał", "channelNetwork": "Sieć", "signalStrength": "Siła sygnału", "signalQuality": "Jakość", - "symbolQuality": "Quality", + "symbolQuality": "Jakość", "networkRate": "Bitrate", "clientIP": "Klient" }, "scrutiny": { "passed": "Powodzenie", - "failed": "Failed", - "unknown": "Unknown" + "failed": "Nieudane", + "unknown": "Nieznane" }, "paperlessngx": { "inbox": "Skrzynka odbiorcza", - "total": "Total" + "total": "Razem" }, "pangolin": { - "orgs": "Orgs", - "sites": "Sites", - "resources": "Resources", - "targets": "Targets", - "traffic": "Traffic", - "in": "In", - "out": "Out" + "orgs": "Organizacje", + "sites": "Strony", + "resources": "Zasoby", + "targets": "Cele", + "traffic": "Ruch", + "in": "Do", + "out": "Z" }, "peanut": { "battery_charge": "Stan baterii", @@ -617,18 +618,18 @@ "low_battery": "Niski poziom baterii" }, "nextdns": { - "wait": "Please Wait", + "wait": "Proszę czekać", "no_devices": "Nie otrzymano danych urządzenia" }, "mikrotik": { "cpuLoad": "Obciążenie procesora", "memoryUsed": "Zużyta pamięć", - "uptime": "Uptime", + "uptime": "Czas działania", "numberOfLeases": "Dzierżawy" }, "xteve": { "streams_all": "Wszystkie strumienie", - "streams_active": "Active Streams", + "streams_active": "Aktywne strumienie", "streams_xepg": "Kanały XEPG" }, "opendtu": { @@ -663,9 +664,9 @@ "load": "Śr. Obciążenie", "memory": "Użycie pamięci", "wanStatus": "Status WAN", - "up": "Up", - "down": "Down", - "temp": "Temp", + "up": "Działa", + "down": "Nie działa", + "temp": "Temperatura", "disk": "Użycie dysku", "wanIP": "WAN IP" }, @@ -676,38 +677,38 @@ "memory_usage": "Pamięć" }, "immich": { - "users": "Users", + "users": "Użytkownicy", "photos": "Zdjęcia", - "videos": "Videos", + "videos": "Filmy", "storage": "Pamięć" }, "uptimekuma": { "up": "Działające", "down": "Niedziałające", - "uptime": "Uptime", + "uptime": "Czas działania", "incident": "Incydent", - "m": "m" + "m": "min" }, "atsumeru": { - "series": "Series", + "series": "Serie", "archives": "Archiwa", "chapters": "Rozdziały", "categories": "Kategorie" }, "komga": { "libraries": "Biblioteki", - "series": "Series", - "books": "Books" + "series": "Serie", + "books": "Książki" }, "diskstation": { - "days": "Days", - "uptime": "Uptime", - "volumeAvailable": "Available" + "days": "Dni", + "uptime": "Czas działania", + "volumeAvailable": "Dostępne" }, "mylar": { - "series": "Series", + "series": "Seriale", "issues": "Zgłoszenia", - "wanted": "Wanted" + "wanted": "Poszukiwane" }, "photoprism": { "albums": "Albumy", @@ -716,9 +717,9 @@ "people": "Ludzie" }, "fileflows": { - "queue": "Queue", - "processing": "Processing", - "processed": "Processed", + "queue": "W kolejce", + "processing": "Przetwarzane", + "processed": "Przetworzone", "time": "Czas" }, "firefly": { @@ -744,7 +745,7 @@ "size": "Rozmiar", "lastrun": "Ostatnie uruchomienie", "nextrun": "Następne uruchomienie", - "failed": "Failed" + "failed": "Nieudane" }, "unmanic": { "active_workers": "Aktywni pracownicy", @@ -761,15 +762,15 @@ "targets_total": "Wszystkich Celi" }, "gatus": { - "up": "Sites Up", - "down": "Sites Down", - "uptime": "Uptime" + "up": "Działające strony", + "down": "Niedziałające strony", + "uptime": "Czas działania" }, "ghostfolio": { "gross_percent_today": "Dzisiaj", "gross_percent_1y": "Rok", "gross_percent_max": "Od początku", - "net_worth": "Net Worth" + "net_worth": "Wartość netto" }, "audiobookshelf": { "podcasts": "Podcasty", @@ -784,22 +785,28 @@ }, "whatsupdocker": { "monitoring": "Monitoring", - "updates": "Updates" + "updates": "Aktualizacje" }, "calibreweb": { "books": "Książki", "authors": "Autorzy", "categories": "Kategorie", - "series": "Series" + "series": "Serie" + }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" }, "jdownloader": { - "downloadCount": "Queue", - "downloadBytesRemaining": "Remaining", - "downloadTotalBytes": "Size", + "downloadCount": "W kolejce", + "downloadBytesRemaining": "Pozostało", + "downloadTotalBytes": "Rozmiar", "downloadSpeed": "Prędkość" }, "kavita": { - "seriesCount": "Series", + "seriesCount": "Serie", "totalFiles": "Pliki" }, "azuredevops": { @@ -813,7 +820,7 @@ "inProgress": "W trakcie", "totalPrs": "Łącznie PRs", "myPrs": "Moje PRs", - "approved": "Approved" + "approved": "Zaakceptowane" }, "gamedig": { "status": "Status", @@ -841,33 +848,33 @@ }, "openmediavault": { "downloading": "Pobieranie", - "total": "Total", - "running": "Running", - "stopped": "Stopped", - "passed": "Passed", - "failed": "Failed" + "total": "Razem", + "running": "Działające", + "stopped": "Zatrzymane", + "passed": "Zaliczony", + "failed": "Nieudany" }, "openwrt": { - "uptime": "Uptime", + "uptime": "Czas działania", "cpuLoad": "Śr. obciążenie CPU (5m)", - "up": "Up", - "down": "Down", + "up": "Działa", + "down": "Nie działa", "bytesTx": "Przesłane", - "bytesRx": "Received" + "bytesRx": "Odebrano" }, "uptimerobot": { "status": "Status", - "uptime": "Uptime", + "uptime": "Czas działania", "lastDown": "Ostatni downtime", "downDuration": "Długość downtime'u", - "sitesUp": "Sites Up", - "sitesDown": "Sites Down", - "paused": "Paused", + "sitesUp": "Działające strony", + "sitesDown": "Niedziałające strony", + "paused": "Zatrzymane", "notyetchecked": "Nie sprawdzono", - "up": "Up", + "up": "Działa", "seemsdown": "Możliwe, że wyłączony", - "down": "Down", - "unknown": "Unknown" + "down": "Nie działa", + "unknown": "Nieznane" }, "calendar": { "inCinemas": "W kinach", @@ -886,10 +893,10 @@ "totalfilesize": "Rozmiar całkowity" }, "mailcow": { - "domains": "Domains", + "domains": "Domeny", "mailboxes": "Skrzynki", "mails": "Poczta", - "storage": "Storage" + "storage": "Pamięć" }, "netdata": { "warnings": "Ostrzeżenia", @@ -898,12 +905,12 @@ "plantit": { "events": "Wydarzenia", "plants": "Rośliny", - "photos": "Photos", + "photos": "Zdjęcia", "species": "Gatunki" }, "gitea": { "notifications": "Powiadomienia", - "issues": "Issues", + "issues": "Zgłoszenia", "pulls": "Żądania Pull", "repositories": "Repozytoria" }, @@ -919,13 +926,13 @@ "galleries": "Galerie", "performers": "Artyści", "studios": "Studia", - "movies": "Movies", - "tags": "Tags", + "movies": "Filmy", + "tags": "Tagi", "oCount": "O Licznik" }, "tandoor": { - "users": "Users", - "recipes": "Recipes", + "users": "Użytkownicy", + "recipes": "Przepisy", "keywords": "Słowa kluczowe" }, "homebox": { @@ -933,18 +940,18 @@ "totalWithWarranty": "Z gwarancją", "locations": "Lokalizacje", "labels": "Etykiety", - "users": "Users", + "users": "Użytkownicy", "totalValue": "Wartość całkowita" }, "crowdsec": { - "alerts": "Alerts", + "alerts": "Alerty", "bans": "Bany" }, "wgeasy": { - "connected": "Connected", - "enabled": "Enabled", - "disabled": "Disabled", - "total": "Total" + "connected": "Połączonych", + "enabled": "Włączone", + "disabled": "Wyłączone", + "total": "Razem" }, "swagdashboard": { "proxied": "Proxy", @@ -966,7 +973,7 @@ }, "frigate": { "cameras": "Kamery", - "uptime": "Uptime", + "uptime": "Czas działania", "version": "Wersja" }, "linkwarden": { @@ -976,7 +983,7 @@ }, "zabbix": { "unclassified": "Niezaklasyfikowane", - "information": "Information", + "information": "Informacja", "warning": "Ostrzeżenie", "average": "Średnia", "high": "Wysokie", @@ -1007,14 +1014,14 @@ "beszel": { "name": "Nazwa", "systems": "Systemy", - "up": "Up", - "down": "Down", - "paused": "Paused", - "pending": "Pending", + "up": "Działa", + "down": "Nie działa", + "paused": "Wstrzymane", + "pending": "Oczekujące", "status": "Status", - "updated": "Updated", + "updated": "Zaktualizowane", "cpu": "Procesor", - "memory": "MEM", + "memory": "PAM", "disk": "Dysk", "network": "NET" }, @@ -1022,14 +1029,14 @@ "apps": "Aplikacje", "synced": "Synchronizowane", "outOfSync": "Bez synchronizacji", - "healthy": "Healthy", + "healthy": "Zdrowe", "degraded": "Zdegradowane", "progressing": "Postępujące", - "missing": "Missing", + "missing": "Brakujące", "suspended": "Zawieszone" }, "spoolman": { - "loading": "Loading" + "loading": "Ładowanie" }, "gitlab": { "groups": "Grupy", @@ -1039,9 +1046,9 @@ }, "apcups": { "status": "Status", - "load": "Load", - "bcharge": "Battery Charge", - "timeleft": "Time Left" + "load": "Obciążenie", + "bcharge": "Naładowanie baterii", + "timeleft": "Pozostały czas" }, "karakeep": { "bookmarks": "Zakładki", @@ -1052,11 +1059,11 @@ "tags": "Tagi" }, "slskd": { - "slskStatus": "Network", - "connected": "Connected", - "disconnected": "Disconnected", + "slskStatus": "Sieć", + "connected": "Połączono", + "disconnected": "Rozłączono", "updateStatus": "Aktualizacja", - "update_yes": "Available", + "update_yes": "Dostępne", "update_no": "Aktualny", "downloads": "Pobieranie", "uploads": "Przesyłanie", @@ -1069,65 +1076,65 @@ "other": "Inne" }, "checkmk": { - "serviceErrors": "Service issues", - "hostErrors": "Host issues" + "serviceErrors": "Problem z usługą", + "hostErrors": "Problemy hosta" }, "komodo": { - "total": "Total", - "running": "Running", - "stopped": "Stopped", - "down": "Down", - "unhealthy": "Unhealthy", - "unknown": "Unknown", + "total": "Razem", + "running": "Działające", + "stopped": "Zatrzymane", + "down": "Nie działa", + "unhealthy": "Uszkodzony", + "unknown": "Nieznane", "servers": "Serwery", - "stacks": "Stacks", - "containers": "Containers" + "stacks": "Stosy", + "containers": "Kontenery" }, "filebrowser": { - "available": "Available", - "used": "Used", - "total": "Total" + "available": "Dostępne", + "used": "Użyte", + "total": "Razem" }, "wallos": { - "activeSubscriptions": "Subscriptions", - "thisMonthlyCost": "This Month", - "nextMonthlyCost": "Next Month", - "previousMonthlyCost": "Prev. Month", - "nextRenewingSubscription": "Next Payment" + "activeSubscriptions": "Subskrypcje", + "thisMonthlyCost": "Ten Miesiąc", + "nextMonthlyCost": "Następny miesiąc", + "previousMonthlyCost": "Poprzedni miesiąc", + "nextRenewingSubscription": "Następna płatność" }, "unraid": { - "STARTED": "Started", - "STOPPED": "Stopped", - "NEW_ARRAY": "New Array", - "RECON_DISK": "Reconstructing Disk", - "DISABLE_DISK": "Disk Disabled", - "SWAP_DSBL": "Swap Disable", - "INVALID_EXPANSION": "Invalid Expansion", - "PARITY_NOT_BIGGEST": "Parity Not Biggest", - "TOO_MANY_MISSING_DISKS": "Too Many Missing Disks", - "NEW_DISK_TOO_SMALL": "New Disk Too Small", - "NO_DATA_DISKS": "No Data Disks", - "notifications": "Notifications", + "STARTED": "Rozpoczęte", + "STOPPED": "Zatrzymane", + "NEW_ARRAY": "Nowa macierz", + "RECON_DISK": "Odbudowa dysku", + "DISABLE_DISK": "Dysk wyłączony", + "SWAP_DSBL": "Przestrzeń wymiany wyłączona", + "INVALID_EXPANSION": "Nieprawidłowe rozszerzenie", + "PARITY_NOT_BIGGEST": "Parzystość nie największa", + "TOO_MANY_MISSING_DISKS": "Zbyt wiele brakujących dysków", + "NEW_DISK_TOO_SMALL": "Nowy dysk zbyt mały", + "NO_DATA_DISKS": "Brak dysków danych", + "notifications": "Powiadomienia", "status": "Status", "cpu": "CPU", - "memoryUsed": "Memory Used", - "memoryAvailable": "Memory Available", - "arrayUsed": "Array Used", - "arrayFree": "Array Free", - "poolUsed": "{{pool}} Used", - "poolFree": "{{pool}} Free" + "memoryUsed": "Użyta pamięć", + "memoryAvailable": "Dostępna pamięć", + "arrayUsed": "Użyto macierzy", + "arrayFree": "Wolne na macierzy", + "poolUsed": "Użyto {{pool}}", + "poolFree": "{{pool}} Wolne" }, "backrest": { - "num_plans": "Plans", - "num_success_30": "Successes", - "num_failure_30": "Failures", - "num_success_latest": "Succeeding", - "num_failure_latest": "Failing", - "bytes_added_30": "Bytes Added" + "num_plans": "Planowane", + "num_success_30": "Powodzenia", + "num_failure_30": "Niepowodzenia", + "num_success_latest": "Powodzenie", + "num_failure_latest": "Niepowodzenie", + "bytes_added_30": "Dodane bajty" }, "yourspotify": { - "songs": "Songs", - "time": "Time", - "artists": "Artists" + "songs": "Piosenki", + "time": "Czas", + "artists": "Wykonawcy" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 1be845d28..b54f832a9 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Novo", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/pt_BR/common.json b/public/locales/pt_BR/common.json index a51278acf..0192582c1 100644 --- a/public/locales/pt_BR/common.json +++ b/public/locales/pt_BR/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Ativo", "pending": "Pendente", - "down": "Inativo" + "down": "Inativo", + "ok": "Ok" }, "healthchecks": { "new": "Novo", @@ -792,6 +793,12 @@ "categories": "Categorias", "series": "Séries" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Fila de espera", "downloadBytesRemaining": "Restante", diff --git a/public/locales/ro/common.json b/public/locales/ro/common.json index c7a067b7c..19bfa637c 100644 --- a/public/locales/ro/common.json +++ b/public/locales/ro/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nou", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 9bbeea5af..6f6e96b25 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "В сети", "pending": "Ожидают", - "down": "Не в сети" + "down": "Не в сети", + "ok": "Ok" }, "healthchecks": { "new": "Новый", @@ -792,6 +793,12 @@ "categories": "Категории", "series": "Серии" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Очередь", "downloadBytesRemaining": "Осталось", diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json index ff13f1762..b6efa6deb 100644 --- a/public/locales/sk/common.json +++ b/public/locales/sk/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Beží", "pending": "Čakajúce", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nový", @@ -792,6 +793,12 @@ "categories": "Kategórie", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Poradie", "downloadBytesRemaining": "Zostávajúce", diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json index c76049ab2..2ea9965d6 100644 --- a/public/locales/sl/common.json +++ b/public/locales/sl/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Nov", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/sr/common.json b/public/locales/sr/common.json index 75f804ff3..c90945aaf 100644 --- a/public/locales/sr/common.json +++ b/public/locales/sr/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Горе", "pending": "На чекању", - "down": "Доле" + "down": "Доле", + "ok": "Ok" }, "healthchecks": { "new": "Сада", @@ -600,13 +601,13 @@ "total": "Укупно" }, "pangolin": { - "orgs": "Orgs", - "sites": "Sites", - "resources": "Resources", - "targets": "Targets", - "traffic": "Traffic", - "in": "In", - "out": "Out" + "orgs": "Организације", + "sites": "Сајтови", + "resources": "Ресурси", + "targets": "Циљеви", + "traffic": "Саобраћај", + "in": "Улазак", + "out": "Излазак" }, "peanut": { "battery_charge": "Напуњеност батерије", @@ -792,6 +793,12 @@ "categories": "Категорије", "series": "Серије" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Ред", "downloadBytesRemaining": "Преостало", diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index 215825556..70507cba5 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/te/common.json b/public/locales/te/common.json index eaf600456..11d09fee3 100644 --- a/public/locales/te/common.json +++ b/public/locales/te/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/th/common.json b/public/locales/th/common.json index 722c80909..2a0b44ab4 100644 --- a/public/locales/th/common.json +++ b/public/locales/th/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index 1f39661cd..e675e6017 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Çalışıyor", "pending": "Bekleyen", - "down": "Çalışmayan" + "down": "Çalışmayan", + "ok": "Ok" }, "healthchecks": { "new": "Yeni", @@ -792,6 +793,12 @@ "categories": "Kategoriler", "series": "Seriler" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Kuyruk", "downloadBytesRemaining": "Kalan", diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json index 70429a477..c45915eb3 100644 --- a/public/locales/uk/common.json +++ b/public/locales/uk/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "Новий", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index b9fea46d0..140c5ca7f 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "New", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/yue/common.json b/public/locales/yue/common.json index ff9318b8d..813cb7ed4 100644 --- a/public/locales/yue/common.json +++ b/public/locales/yue/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "新建立", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining", diff --git a/public/locales/zh-Hans/common.json b/public/locales/zh-Hans/common.json index e11fd97f7..394b84d8e 100644 --- a/public/locales/zh-Hans/common.json +++ b/public/locales/zh-Hans/common.json @@ -364,7 +364,7 @@ "version": "版本", "notesCount": "笔记", "dbSize": "数据库大小", - "unknown": "Unknown" + "unknown": "未知" }, "navidrome": { "nothing_streaming": "", @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "新建立", @@ -792,6 +793,12 @@ "categories": "分类", "series": "系列" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "队列", "downloadBytesRemaining": "剩余", @@ -800,7 +807,7 @@ }, "kavita": { "seriesCount": "系列", - "totalFiles": "Files" + "totalFiles": "文件" }, "azuredevops": { "result": "Result", @@ -1097,7 +1104,7 @@ }, "unraid": { "STARTED": "Started", - "STOPPED": "Stopped", + "STOPPED": "已停止", "NEW_ARRAY": "New Array", "RECON_DISK": "Reconstructing Disk", "DISABLE_DISK": "Disk Disabled", diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index ce31adf37..f65eff954 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -532,7 +532,8 @@ "child_bridges_status": "{{ok}}/{{total}}", "up": "Up", "pending": "Pending", - "down": "Down" + "down": "Down", + "ok": "Ok" }, "healthchecks": { "new": "新建", @@ -792,6 +793,12 @@ "categories": "Categories", "series": "Series" }, + "booklore": { + "libraries": "Libraries", + "books": "Books", + "reading": "Reading", + "finished": "Finished" + }, "jdownloader": { "downloadCount": "Queue", "downloadBytesRemaining": "Remaining",