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 08d482cf..c1466ea8 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 188df16b..aba28450 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 2a8b57e2..d15bfe51 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 2c0f564b..3948fc54 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 df47bbd8..28751110 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 cf61baf2..0c5320ee 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 0e1fcbcf..21819206 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 1dcacbf0..0fffc962 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 51cd4b5d..88748f17 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 06067bf6..6e1b1576 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 f5e9cf51..3c61aa00 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 166dc145..e7653c70 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 18ffc49b..f21d07d0 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 c8c57fbc..46d43e83 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 b1f73740..e80324b2 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 2d237a23..22d2134f 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 9109b4bd..c243c3ff 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 ab5e4d79..ce3dddf8 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 2a1949c1..7bfbd46c 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 45834bd6..ac082ab0 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 28751110..f9022680 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 2d477db5..79f2129d 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 79f2129d..b47e1b7c 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 f9022680..c2d3032b 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 0c5320ee..9b253506 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 c3be5c43..6280c852 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 c2d3032b..118dd67c 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 9b253506..9df7a23f 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 118dd67c..67c5a4c1 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 9df7a23f..f675ed81 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 67c5a4c1..be58b9c7 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 f675ed81..56b42b45 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 03b410f4..51a52523 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 97bba3be..96785af4 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 be58b9c7..9fe4f0a5 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 56b42b45..72f62031 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 12ceef56..712a5115 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 00000000..ebc5299e --- /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 528114ed..3f022b74 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 3918b9f3..3936d751 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 090973bd..4d2497a0 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 f082845b..b6bfb23b 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 1f618182..a67de624 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 b47e1b7c..dc141cd9 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 786db9a5..21885a62 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 033d3d81..176a8c4c 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 00000000..137aa1e5 --- /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 8ae84ee9..8110a7fc 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 b3865d3c..2a21cbfc 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 22d2134f..7adcd4c1 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 00000000..fc8b5348 --- /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 00000000..ccf2b2b6 --- /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 00000000..3ff862e3 --- /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 911be5fb..30bafd50 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 dcc0ba65..f7947de7 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 af5ffbd7..1c76f179 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 5412cb9d..4551ed13 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 16e34e68..a49aa6fc 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 455dfa40..7add4b38 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 080750a5..8d3a193e 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 e13b6339..50c1b7d1 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 db8b602e..6c5c2374 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 81befbef..1ee0ff90 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 561d5866..fe2b6bf6 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 7a5723ba..62b604ca 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 06f0dcef..0037bbba 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 01eaf7c4..80aa7fef 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 2eb8e190..9366c5f4 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 b8cd89e2..20d0ddc8 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 ea5cb9d3..67339b73 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 1484ab91..fe9208a4 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 cd7e2fff..dc64a89c 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 e031c2c9..ce48cec5 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 413231fd..30b7cf48 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 0463a362..aec5ec8a 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 bcf98bc7..d82c7f10 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 9e4180c7..89b83008 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 4fea074c..bfbb76f5 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 1039c2da..37b40108 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 38604af5..a9316578 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 f33699de..21869407 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 1be845d2..b54f832a 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 a51278ac..0192582c 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 c7a067b7..19bfa637 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 9bbeea5a..6f6e96b2 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 ff13f176..b6efa6de 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 c76049ab..2ea9965d 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 75f804ff..c90945aa 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 21582555..70507cba 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 eaf60045..11d09fee 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 722c8090..2a0b44ab 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 1f39661c..e675e601 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 70429a47..c45915eb 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 b9fea46d..140c5ca7 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 ff9318b8..813cb7ed 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 e11fd97f..394b84d8 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 ce31adf3..f65eff95 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",