Compare commits

..

72 Commits

Author SHA1 Message Date
shamoon 233721cc90 1.13.1
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-05-11 08:42:02 -07:00
shamoon d294a25145 Merge branch 'dev' 2026-05-11 08:13:31 -07:00
github-actions[bot] 0ff9af4c3c New Crowdin translations by GitHub Action (#6647)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-05-11 08:11:24 -07:00
shamoon a6c753c8aa Fix: include tasks params for PBS widget (#6655)
Lint / Linting Checks (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-05-11 00:04:33 -07:00
shamoon 5d71c3aa65 Fix: allow empty data for ntfy widget (#6653)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-05-10 07:28:31 -07:00
shamoon 1cc4608d72 Enhancement: support qBittorrent v5.2.0 api changes (#6652) 2026-05-10 06:51:22 -07:00
shamoon d65e5447e4 Add Issue Triage workflow
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
2026-05-08 16:21:48 -07:00
shamoon d3256596d8 Enhance release-drafter autolabeler rules 2026-05-08 16:19:33 -07:00
shamoon 5bfe18b3e5 Merge branch 'dev'
Tests / vitest (4) (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
2026-05-08 15:59:26 -07:00
github-actions[bot] 1ac5012c94 New Crowdin translations by GitHub Action (#6496)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-05-08 15:56:34 -07:00
shamoon 27b1c3f5f4 Merge branch 'dev' 2026-05-08 15:54:10 -07:00
shamoon bb765a6747 1.13.0 2026-05-08 15:53:50 -07:00
shamoon aab4f5125b Chore: add pnpm workspace allowBuilds config (#6645) 2026-05-08 15:09:17 -07:00
shamoon 6d1e56bca0 Update bug report template for issue closure notice 2026-05-08 13:36:24 -07:00
dependabot[bot] 4603e0ee57 Chore(deps): Bump release-drafter/release-drafter from 7.2.0 to 7.2.1 (#6641)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 16:54:39 +00:00
dependabot[bot] 9a983a5be1 Chore(deps): Bump pnpm/action-setup from 6.0.3 to 6.0.4 (#6642)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 09:50:03 -07:00
Thielquis 23e66a265d Fix: Make calendar hover event toggle explicit (#6639)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-05-07 07:15:25 -07:00
Rob van Oostenrijk ce8f020b7b Enhancement: Add node parameter to Technitium DNS Widget (#6638)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-05-06 23:58:48 -07:00
shamoon 72b0e493d0 Enhancement: Normalize non-200 proxy responses to error JSON (#6630)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-05-05 13:30:12 -07:00
dependabot[bot] ffb264cead Chore(deps): Bump next from 16.2.3 to 16.2.4 (#6622)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 04:20:22 +00:00
dependabot[bot] cd6e708af2 Chore(deps): Bump react from 19.2.4 to 19.2.5 (#6621)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-05-03 21:15:50 -07:00
dependabot[bot] 4e621aee8f Chore(deps-dev): Bump @eslint/js from 9.39.2 to 9.39.4 (#6623)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 04:15:05 +00:00
dependabot[bot] 93364b6a27 Chore(deps): Bump json-rpc-2.0 from 1.7.0 to 1.7.1 (#6624)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 04:08:43 +00:00
dependabot[bot] 3dffc38462 Chore(deps): Bump @headlessui/react from 2.2.9 to 2.2.10 (#6620)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-03 21:03:46 -07:00
dependabot[bot] b2a52ff2a9 Chore(deps): Bump pnpm/action-setup from 5.0.0 to 6.0.3 (#6614)
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 22:57:08 +00:00
dependabot[bot] 081b9ec8c3 Chore(deps): Bump crowdin/github-action from 2.16.0 to 2.16.2 (#6615)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 22:53:03 +00:00
dependabot[bot] 977be863c7 Chore(deps): Bump actions/setup-node from 6.3.0 to 6.4.0 (#6613)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 22:48:57 +00:00
shamoon 77c9ac4a2c Fix: add missing backdrop blur classes to safelist (#6617) 2026-04-30 14:52:32 -07:00
pigawatts b6b00aab86 Documentation: update watchtower links to active fork (#6608)
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
2026-04-28 16:27:42 -07:00
Jim Strang c3b4fc17c4 Feature: ntfy widget (#6601)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: Jim Strang <jimstrang@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-04-26 20:38:14 -07:00
dependabot[bot] 9b8e771268 Chore(deps-dev): Bump postcss from 8.5.8 to 8.5.10 in the npm_and_yarn group across 1 directory (#6596)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 18:48:21 -07:00
shamoon 44c1e8393e Enhancement: support custom labels for cpu temp sensors (#6595)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-24 10:20:15 -07:00
dependabot[bot] 7ed1260f0d Chore(deps): Bump actions/cache from 5.0.4 to 5.0.5 (#6593)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 17:39:26 +00:00
dependabot[bot] 96f4777637 Chore(deps): Bump peakoss/anti-slop from 0.2.1 to 0.3.0 (#6590)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 16:41:27 +00:00
dependabot[bot] f762135eb3 Chore(deps): Bump astral-sh/setup-uv from 8.0.0 to 8.1.0 (#6592)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 16:37:02 +00:00
finlay-mcaree d721bb661f Enhancement: add additional fields to Tailscale Widget (#6589)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-22 13:01:31 -07:00
dependabot[bot] bf55e8acab Chore(deps): Bump i18next-fs-backend from 2.6.1 to 2.6.4 in the npm_and_yarn group across 1 directory (#6588)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-22 11:17:35 -07:00
DieselTech f82e0d9519 Documentation: update mylar link (#6587)
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
2026-04-22 07:37:16 -07:00
shamoon 8322dd9016 Fix: use seconds for PBS since (#6583)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-19 07:53:25 -07:00
Jérémie Astori 10ade1d32f Fix: Swap cputemp and disk resources in glances loading state skeleton (#6577)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-17 23:18:46 -07:00
dependabot[bot] a0f9fbf614 Chore(deps): Bump actions/github-script from 8.0.0 to 9.0.0 (#6572)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 06:31:18 +00:00
dependabot[bot] 0c8fd6904e Chore(deps): Bump protobufjs from 7.5.4 to 7.5.5 in the npm_and_yarn group across 1 directory (#6575)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 23:27:05 -07:00
dependabot[bot] b2b299d634 Chore(deps): Bump actions/upload-pages-artifact from 4.0.0 to 5.0.0 (#6571)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 17:33:19 +00:00
dependabot[bot] b8672ddc9b Chore(deps): Bump release-drafter/release-drafter from 7.1.1 to 7.2.0 (#6570)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 17:29:04 +00:00
dependabot[bot] 03dd54ee2e Chore(deps): Bump docker/build-push-action from 7.0.0 to 7.1.0 (#6573)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 10:25:03 -07:00
dependabot[bot] 92ec6ccefc Chore(deps): Bump follow-redirects from 1.15.11 to 1.16.0 in the npm_and_yarn group across 1 directory (#6568)
Docker CI / Docker Build & Push (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 12:56:56 -07:00
Steven Harris 463e94d933 Fix: use v2 storage API for UniFi Drive widget (#6567) 2026-04-15 15:10:45 +00:00
shamoon a7bab17f97 Enhancement: support pyload API key, fix error message (#6558)
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
2026-04-12 11:00:54 -07:00
shamoon 1accddf29a Fix: prevent omada race conditions with auth and cookie caching (#6549)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-10 15:48:38 -07:00
dependabot[bot] 636d62106c Chore(deps): Bump next from 16.1.7 to 16.2.3 in the npm_and_yarn group across 1 directory (#6547)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 09:47:20 -07:00
shamoon d048888d99 Trigger Docker publish on fix/** branches 2026-04-10 09:24:05 -07:00
dependabot[bot] 24804f39fc Chore(deps): Bump docker/login-action from 4.0.0 to 4.1.0 (#6540)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 00:23:32 +00:00
dependabot[bot] 70f8c67d3c Chore(deps): Bump astral-sh/setup-uv from 7.6.0 to 8.0.0 (#6541)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 00:19:12 +00:00
dependabot[bot] 038f5c3b9f Chore(deps): Bump actions/deploy-pages from 4.0.5 to 5.0.0 (#6542)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 00:15:15 +00:00
dependabot[bot] ade4c733c7 Chore(deps): Bump codecov/codecov-action from 5.5.4 to 6.0.0 (#6543)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 17:11:26 -07:00
dependabot[bot] bdbd2e6ff0 Chore(deps): Bump vite from 7.3.1 to 7.3.2 in the npm_and_yarn group across 1 directory (#6539)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 22:44:10 -07:00
shamoon 5f4b0b4e33 Enhancement: Cache and reuse keep-alive HTTP(S) agents (#6536)
Release Drafter / Auto Label PR (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-07 08:04:35 -07:00
Gabe Dunn a56a05b553 Documentation: add reference to required settings to ingressroute docs (#6527)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-04-06 09:45:47 -07:00
shamoon 11d1ceb755 Enhancement: increase resources page size for pangolin widget (#6523)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-04-05 07:21:06 -07:00
dependabot[bot] 6df1cfdeba Chore(deps): Bump peakoss/anti-slop from a5a4b2440c9de6f65b64f0718a0136a1fdb04f6f to 85daca1880e9e1af197fc06ea03349daf08f4202 (#6507)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-05 04:07:54 +00:00
dependabot[bot] d64fbae776 Chore(deps): Update release-drafter/release-drafter requirement to 139054aeaa9adc52ab36ddf67437541f039b88e2 (#6504)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-05 04:04:05 +00:00
dependabot[bot] 5f7e5430c4 Chore(deps): Bump actions/configure-pages from 5.0.0 to 6.0.0 (#6506)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-05 03:59:04 +00:00
dependabot[bot] 3cc3986ec3 Chore(deps): Bump pnpm/action-setup from a8198c4bff370c8506180b035930dea56dbd5288 to fc06bc1257f339d1d5d8b3a19a8cae5388b55320 (#6505)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-05 03:55:12 +00:00
dependabot[bot] 5af0ab1686 Chore(deps): Bump astral-sh/setup-uv from 94527f2e458b27549849d47d273a16bec83a01e9 to 37802adc94f370d6bfd71619e3f0bf239e1f3b78 (#6503)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-04 20:49:43 -07:00
dependabot[bot] f0f5c3c15c Chore(deps-dev): Bump @tailwindcss/forms from 0.5.10 to 0.5.11 (#6500)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 22:02:55 +00:00
dependabot[bot] 2ed1da4411 Chore(deps): Bump i18next from 25.8.0 to 25.10.9 (#6501)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 21:58:21 +00:00
dependabot[bot] e1023466b1 Chore(deps-dev): Bump @eslint/compat from 2.0.2 to 2.0.3 (#6498)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 21:53:25 +00:00
dependabot[bot] 9fe5ad62f1 Chore(deps-dev): Bump postcss from 8.5.6 to 8.5.8 (#6499)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 21:47:23 +00:00
dependabot[bot] 173883000f Chore(deps): Bump dockerode from 4.0.7 to 4.0.10 (#6497)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 14:42:40 -07:00
shamoon d6e7e7e790 1.12.3
Crowdin Action / Crowdin Sync (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Repository Maintenance / Stale (push) Has been cancelled
Repository Maintenance / Lock Old Threads (push) Has been cancelled
Repository Maintenance / Close Answered Discussions (push) Has been cancelled
Repository Maintenance / Close Outdated Discussions (push) Has been cancelled
Repository Maintenance / Close Unsupported Feature Requests (push) Has been cancelled
2026-04-01 08:02:58 -07:00
shamoon 24cb274e03 Fix glances regex 2026-04-01 08:02:03 -07:00
shamoon af852e748a Normalize widget version in URLs 2026-04-01 08:00:20 -07:00
121 changed files with 3703 additions and 1003 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ body:
options: options:
- label: I confirm this was discussed, and the maintainers asked that I open an issue. - label: I confirm this was discussed, and the maintainers asked that I open an issue.
required: true required: true
- label: I am aware that if I create this issue without a discussion, it will be removed without a response. - label: I am aware that if I create this issue without a discussion, it will be closed without a response.
required: true required: true
- type: input - type: input
id: discussion id: discussion
+25 -19
View File
@@ -47,15 +47,34 @@ categories:
- 'documentation' - 'documentation'
autolabeler: autolabeler:
- label: 'bug'
title:
- '/^fix(\(.+\))?:/i'
body:
- '/- \[[xX]\] Bug fix \(non-breaking change which fixes an issue\)/'
- label: 'enhancement'
title:
- '/^(feature|enhancement)(\(.+\))?:/i'
body:
- '/- \[[xX]\] New service widget/'
- '/- \[[xX]\] New feature or enhancement \(non-breaking change which adds functionality\)/'
- label: 'documentation' - label: 'documentation'
files: title:
- 'docs/**' - '/^(documentation|docs)(\(.+\))?:/i'
- '*.md' body:
- '.github/**/*.md' - '/- \[[xX]\] Documentation only/'
- label: 'chore'
body:
- '/- \[[xX]\] Other \(please explain\)/'
- label: 'ci' - label: 'ci'
files: branch:
- '.github/workflows/**' - '/^(ci|workflow|actions)\//'
title:
- '/^(ci|workflow|actions)(\(.+\))?:/i'
- label: 'dependencies' - label: 'dependencies'
files: files:
@@ -64,19 +83,6 @@ autolabeler:
- 'pyproject.toml' - 'pyproject.toml'
- 'uv.lock' - 'uv.lock'
- label: 'feature'
files:
- 'src/components/**'
- 'src/widgets/**'
- 'src/pages/**'
- 'src/utils/**'
- label: 'chore'
files:
- 'Dockerfile*'
- 'docker-entrypoint.sh'
- 'k3d/**'
- label: 'translation' - label: 'translation'
files: files:
- 'public/locales/**' - 'public/locales/**'
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: crowdin action - name: crowdin action
uses: crowdin/github-action@7ca9c452bfe9197d3bb7fa83a4d7e2b0c9ae835d # v2 uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
with: with:
upload_translations: false upload_translations: false
download_translations: true download_translations: true
+7 -6
View File
@@ -7,6 +7,7 @@ on:
branches: branches:
- main - main
- feature/** - feature/**
- fix/**
- dev - dev
tags: [ 'v*.*.*' ] tags: [ 'v*.*.*' ]
pull_request: pull_request:
@@ -52,7 +53,7 @@ jobs:
latest=auto latest=auto
- name: Next.js build cache - name: Next.js build cache
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: .next/cache path: .next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }} key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }}
@@ -60,13 +61,13 @@ jobs:
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
with: with:
version: 10 version: 10
run_install: false run_install: false
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with: with:
node-version: 24 node-version: 24
cache: 'pnpm' cache: 'pnpm'
@@ -83,7 +84,7 @@ jobs:
- name: Log into registry ${{ env.REGISTRY }} - name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -91,7 +92,7 @@ jobs:
- name: Login to Docker Hub - name: Login to Docker Hub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -104,7 +105,7 @@ jobs:
- name: Build and push Docker image - name: Build and push Docker image
id: build-and-push id: build-and-push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
+5 -5
View File
@@ -25,7 +25,7 @@ jobs:
with: with:
python-version-file: ".python-version" python-version-file: ".python-version"
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- run: sudo apt-get install pngquant - run: sudo apt-get install pngquant
- name: Test Docs Build - name: Test Docs Build
run: uv run --frozen zensical build --clean run: uv run --frozen zensical build --clean
@@ -37,18 +37,18 @@ jobs:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with: with:
python-version-file: ".python-version" python-version-file: ".python-version"
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7 uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- run: sudo apt-get install pngquant - run: sudo apt-get install pngquant
- name: Build Docs - name: Build Docs
run: uv run --frozen zensical build --clean run: uv run --frozen zensical build --clean
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with: with:
path: site path: site
- uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
id: deployment id: deployment
+43
View File
@@ -0,0 +1,43 @@
name: Issue Triage
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
close-needs-discussion:
name: Issues Need Discussion
if: github.event.label.name == 'needs-discussion'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const issueNumber = context.payload.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: 'This issue is being closed because it was opened before a maintainer asked for an issue to be created. Please start with a discussion and follow the issue template; only open an issue when a maintainer asks you to do so.',
});
await github.rest.issues.update({
owner,
repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned',
});
await github.rest.issues.lock({
owner,
repo,
issue_number: issueNumber,
lock_reason: 'off-topic',
});
+2 -2
View File
@@ -23,13 +23,13 @@ jobs:
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
with: with:
version: 10 version: 10
run_install: false run_install: false
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with: with:
node-version: 24 node-version: 24
cache: 'pnpm' cache: 'pnpm'
+1 -1
View File
@@ -13,6 +13,6 @@ jobs:
anti-slop: anti-slop:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: peakoss/anti-slop@a5a4b2440c9de6f65b64f0718a0136a1fdb04f6f # v0 - uses: peakoss/anti-slop@57858eead489d08b255fab2af45a506c2ca6eab2 # v0
with: with:
max-failures: 4 max-failures: 4
+3 -3
View File
@@ -26,14 +26,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' - if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
uses: release-drafter/release-drafter@a6acf82562eee06318b77ab8cb0b11ed81c677a7 # v7 uses: release-drafter/release-drafter@563bf132657a13ded0b01fcb723c5a58cdd824e2 # v7
with: with:
config-name: release-drafter.yml config-name: release-drafter.yml
version: ${{ github.event.inputs.version }} version: ${{ github.event.inputs.version }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: github.event_name != 'workflow_dispatch' || github.event.inputs.version == '' - if: github.event_name != 'workflow_dispatch' || github.event.inputs.version == ''
uses: release-drafter/release-drafter@a6acf82562eee06318b77ab8cb0b11ed81c677a7 # v7 uses: release-drafter/release-drafter@563bf132657a13ded0b01fcb723c5a58cdd824e2 # v7
with: with:
config-name: release-drafter.yml config-name: release-drafter.yml
env: env:
@@ -47,7 +47,7 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: release-drafter/release-drafter/autolabeler@ebb69bb56f1b0ebd19897745035726b19bef973e - uses: release-drafter/release-drafter/autolabeler@563bf132657a13ded0b01fcb723c5a58cdd824e2
with: with:
config-name: release-drafter.yml config-name: release-drafter.yml
env: env:
+3 -3
View File
@@ -57,7 +57,7 @@ jobs:
name: 'Close Answered Discussions' name: 'Close Answered Discussions'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
script: | script: |
function sleep(ms) { function sleep(ms) {
@@ -113,7 +113,7 @@ jobs:
name: 'Close Outdated Discussions' name: 'Close Outdated Discussions'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
script: | script: |
function sleep(ms) { function sleep(ms) {
@@ -204,7 +204,7 @@ jobs:
name: 'Close Unsupported Feature Requests' name: 'Close Unsupported Feature Requests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
script: | script: |
function sleep(ms) { function sleep(ms) {
+3 -3
View File
@@ -15,11 +15,11 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 - uses: pnpm/action-setup@26f6d4f2c533a43e6b5da0b4a5dd983f98f7b49a # v6.0.4
with: with:
version: 9 version: 9
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with: with:
node-version: 20 node-version: 20
cache: pnpm cache: pnpm
@@ -28,7 +28,7 @@ jobs:
# Run Vitest directly so `--shard` is parsed as an option # Run Vitest directly so `--shard` is parsed as an option
- run: pnpm -s exec vitest run --coverage --shard ${{ matrix.shard }}/4 --pool forks - run: pnpm -s exec vitest run --coverage --shard ${{ matrix.shard }}/4 --pool forks
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info files: ./coverage/lcov.info
+1 -1
View File
@@ -122,7 +122,7 @@ Use the `gethomepage.dev/pod-selector` selector to specify the pod used for the
### Traefik IngressRoute support ### Traefik IngressRoute support
Homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set: If enabled (with `traefik: true` in kubernetes.yaml), homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set:
```yaml ```yaml
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
+3
View File
@@ -16,6 +16,7 @@ The Glances widget allows you to monitor the resources (CPU, memory, storage, te
cpu: true # optional, enabled by default, disable by setting to false cpu: true # optional, enabled by default, disable by setting to false
mem: true # optional, enabled by default, disable by setting to false mem: true # optional, enabled by default, disable by setting to false
cputemp: true # disabled by default cputemp: true # disabled by default
cpuSensorLabel: Package id # optional additional cputemp sensor label prefix
unit: imperial # optional for temp, default is metric unit: imperial # optional for temp, default is metric
uptime: true # disabled by default uptime: true # disabled by default
disk: / # disabled by default, use mount point of disk(s) in glances. Can also be a list (see below) disk: / # disabled by default, use mount point of disk(s) in glances. Can also be a list (see below)
@@ -24,6 +25,8 @@ The Glances widget allows you to monitor the resources (CPU, memory, storage, te
label: MyMachine # optional label: MyMachine # optional
``` ```
The built-in `cputemp` sensor matching already checks common prefixes such as `cpu_thermal`, `Core`, `Tctl`, and `Temperature`. Use `cpuSensorLabel` to add your own Glances sensor label prefix when your system reports CPU temperatures under a different name.
Multiple disks can be specified as: Multiple disks can be specified as:
```yaml ```yaml
+1 -1
View File
@@ -3,7 +3,7 @@ title: Mylar3
description: Mylar3 Widget Configuration description: Mylar3 Widget Configuration
--- ---
Learn more about [Mylar3](https://github.com/mylar3/mylar3). Learn more about [Mylar3](https://github.com/MylarComics/mylar3).
API must be enabled in Mylar3 settings. API must be enabled in Mylar3 settings.
+36
View File
@@ -0,0 +1,36 @@
---
title: ntfy
description: ntfy Widget Configuration
---
Learn more about [ntfy](https://github.com/binwiederhier/ntfy).
This widget shows the latest notification for a ntfy topic, including the title or body, priority level, and when it was received. Works with both self-hosted ntfy instances and the public [ntfy.sh](https://ntfy.sh) service.
Allowed fields: `["title", "message", "priority", "lastReceived", "tags"]`.
Default fields: `["title", "message", "priority", "lastReceived"]`.
If more than 4 fields are provided, only the first 4 are displayed.
## Authentication
ntfy supports both public and private topics. For private instances or access-controlled topics, you can authenticate using either a **Bearer token** (ntfy access token) or **Basic auth** (username/password).
| Auth Method | Config Fields | Notes |
| ------------ | ------------------------------ | --------------------------------- |
| None | _(omit key/username/password)_ | For public topics |
| Bearer token | `key` | ntfy access tokens (`tk_` prefix) |
| Basic auth | `username` + `password` | Username/password credentials |
See the [ntfy documentation](https://docs.ntfy.sh/config/#access-control) for details on access control.
```yaml
widget:
type: ntfy
url: http://ntfy.host.or.ip:port # required
topic: mytopic # required
# key: tk_accesstoken # optional — for token auth
# username: user # optional — for basic auth
# password: pass # optional — for basic auth
```
+1
View File
@@ -13,4 +13,5 @@ widget:
url: http://pyload.host.or.ip:port url: http://pyload.host.or.ip:port
username: username username: username
password: password # only needed if set password: password # only needed if set
key: pyloadapikey # only needed if set, takes precedence over username/password
``` ```
+1 -1
View File
@@ -9,7 +9,7 @@ You will need to generate an API access token from the [keys page](https://login
To find your device ID, go to the [machine overview page](https://login.tailscale.com/admin/machines) and select your machine. In the "Machine Details" section, copy your `ID`. It will end with `CNTRL`. To find your device ID, go to the [machine overview page](https://login.tailscale.com/admin/machines) and select your machine. In the "Machine Details" section, copy your `ID`. It will end with `CNTRL`.
Allowed fields: `["address", "last_seen", "expires"]`. Allowed fields: `[ "address", "last_seen", "expires", "user", "hostname", "name", "client_version", "os", "created", "authorized", "is_external", "update_available", "tags" ]`.
```yaml ```yaml
widget: widget:
+5
View File
@@ -14,6 +14,7 @@ widget:
type: technitium type: technitium
url: <url to dns server> url: <url to dns server>
key: biglongapitoken key: biglongapitoken
node: <node dns name or cluster> # optional, defaults to current node
range: LastDay # optional, defaults to LastHour range: LastDay # optional, defaults to LastHour
``` ```
@@ -21,6 +22,10 @@ widget:
This can be generated via the Technitium DNS Dashboard, and should be generated from a special API specific user. This can be generated via the Technitium DNS Dashboard, and should be generated from a special API specific user.
#### Node
`node` value determines which Technitium cluster node the statistics are returned for. Specifying a value of `cluster` returns aggregrate stats for all nodes in the cluster. Specify a node domain name to return specific node stats, no value returns stats for the node against which the API is executed.
#### Range #### Range
`range` value determines how far back of statistics to pull data for. The value comes directly from Technitium API documentation found [here](https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#dashboard-api-calls), defined as `"type"`. The value can be one of: `LastHour`, `LastDay`, `LastWeek`, `LastMonth`, `LastYear`. `range` value determines how far back of statistics to pull data for. The value comes directly from Technitium API documentation found [here](https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md#dashboard-api-calls), defined as `"type"`. The value can be one of: `LastHour`, `LastDay`, `LastWeek`, `LastMonth`, `LastYear`.
+2 -2
View File
@@ -3,9 +3,9 @@ title: Watchtower
description: Watchtower Widget Configuration description: Watchtower Widget Configuration
--- ---
Learn more about [Watchtower](https://github.com/containrrr/watchtower). Learn more about [Watchtower](https://github.com/nicholas-fedor/watchtower).
To use this widget, Watchtower needs to be configured to [enable metrics](https://containrrr.dev/watchtower/metrics/). To use this widget, Watchtower needs to be configured to [enable metrics](https://watchtower.nickfedor.com/dev/advanced-features/metrics-api/).
Allowed fields: `["containers_scanned", "containers_updated", "containers_failed"]`. Allowed fields: `["containers_scanned", "containers_updated", "containers_failed"]`.
+1
View File
@@ -116,6 +116,7 @@ nav:
- widgets/services/nextcloud.md - widgets/services/nextcloud.md
- widgets/services/nextdns.md - widgets/services/nextdns.md
- widgets/services/nginx-proxy-manager.md - widgets/services/nginx-proxy-manager.md
- widgets/services/ntfy.md
- widgets/services/nzbget.md - widgets/services/nzbget.md
- widgets/services/octoprint.md - widgets/services/octoprint.md
- widgets/services/omada.md - widgets/services/omada.md
+13 -20
View File
@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "1.12.2", "version": "1.13.1",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -14,27 +14,27 @@
"telemetry": "next telemetry disable" "telemetry": "next telemetry disable"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.9", "@headlessui/react": "^2.2.10",
"@kubernetes/client-node": "^1.0.0", "@kubernetes/client-node": "^1.0.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"dockerode": "^4.0.7", "dockerode": "^4.0.10",
"follow-redirects": "^1.15.11", "follow-redirects": "^1.16.0",
"gamedig": "^5.3.2", "gamedig": "^5.3.2",
"i18next": "^25.8.0", "i18next": "^25.10.9",
"ical.js": "^2.2.1", "ical.js": "^2.2.1",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"json-rpc-2.0": "^1.7.0", "json-rpc-2.0": "^1.7.1",
"luxon": "^3.6.1", "luxon": "^3.6.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"minecraftstatuspinger": "^1.2.2", "minecraftstatuspinger": "^1.2.2",
"next": "^16.1.7", "next": "^16.2.4",
"next-i18next": "^15.4.3", "next-i18next": "^15.4.3",
"ping": "^0.4.4", "ping": "^0.4.4",
"pretty-bytes": "^7.1.0", "pretty-bytes": "^7.1.0",
"raw-body": "^3.0.2", "raw-body": "^3.0.2",
"react": "^19.2.4", "react": "^19.2.5",
"react-dom": "^19.2.4", "react-dom": "^19.2.5",
"react-i18next": "^15.5.3", "react-i18next": "^15.5.3",
"react-icons": "^5.6.0", "react-icons": "^5.6.0",
"recharts": "^3.1.2", "recharts": "^3.1.2",
@@ -47,10 +47,10 @@
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.0.2", "@eslint/compat": "^2.0.3",
"@eslint/eslintrc": "^3.3.3", "@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.4",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.11",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/postcss": "^4.1.18",
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
@@ -64,7 +64,7 @@
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^28.1.0", "jsdom": "^28.1.0",
"postcss": "^8.5.6", "postcss": "^8.5.10",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-organize-imports": "^4.3.0",
"tailwind-scrollbar": "^4.0.2", "tailwind-scrollbar": "^4.0.2",
@@ -74,12 +74,5 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"osx-temperature-sensor": "^1.0.8" "osx-temperature-sensor": "^1.0.8"
},
"pnpm": {
"onlyBuiltDependencies": [
"@tailwindcss/oxide",
"osx-temperature-sensor",
"sharp"
]
} }
} }
+384 -350
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
packages:
- .
allowBuilds:
'@tailwindcss/oxide': true
core-js: false
cpu-features: false
esbuild: false
osx-temperature-sensor: true
protobufjs: false
sharp: true
ssh2: false
onlyBuiltDependencies:
- '@tailwindcss/oxide'
- osx-temperature-sensor
- protobufjs
- sharp
+29 -4
View File
@@ -67,9 +67,9 @@
"empty_data": "Substelsel status onbekend" "empty_data": "Substelsel status onbekend"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Gesond",
"degraded": "Degraded", "degraded": "Gedegradeer",
"no_data": "No storage data available" "no_data": "Geen bergingsdata beskikbaar nie"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -344,6 +344,16 @@
"address": "Adres", "address": "Adres",
"expires": "Verval", "expires": "Verval",
"never": "Nooit", "never": "Nooit",
"user": "Gebruiker",
"hostname": "Gasheernaam",
"name": "Naam",
"client_version": "Kliënt Weergawe",
"os": "OS",
"created": "Geskep",
"authorized": "Gemagtig",
"is_external": "Is Extern",
"update_available": "Opdatering Beskikbaar",
"tags": "Merkers",
"last_seen": "Laaste Gesien", "last_seen": "Laaste Gesien",
"now": "Nou", "now": "Nou",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Terug" "ago": "{{value}} Terug",
"true": "Ja",
"false": "Nee"
}, },
"technitium": { "technitium": {
"totalQueries": "Navraë", "totalQueries": "Navraë",
@@ -924,6 +936,19 @@
"warnings": "Waarskuwings", "warnings": "Waarskuwings",
"criticals": "Kritici" "criticals": "Kritici"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Gebeure", "events": "Gebeure",
"plants": "Plante", "plants": "Plante",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "عنوان", "address": "عنوان",
"expires": "تنتهي", "expires": "تنتهي",
"never": "مطلقاً", "never": "مطلقاً",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "آخر ظهور", "last_seen": "آخر ظهور",
"now": "الآن", "now": "الآن",
"years": "{{number}}س", "years": "{{number}}س",
@@ -352,7 +362,9 @@
"hours": "{{number}}س", "hours": "{{number}}س",
"minutes": "{{number}}د", "minutes": "{{number}}د",
"seconds": "{{number}}ث", "seconds": "{{number}}ث",
"ago": "منذ {{value}}" "ago": "منذ {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "تحذيرات", "warnings": "تحذيرات",
"criticals": "حرج" "criticals": "حرج"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "أحداث", "events": "أحداث",
"plants": "نباتات", "plants": "نباتات",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Адрес", "address": "Адрес",
"expires": "Изтича", "expires": "Изтича",
"never": "Никога", "never": "Никога",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Последно видян", "last_seen": "Последно видян",
"now": "Сега", "now": "Сега",
"years": "{{number}}г", "years": "{{number}}г",
@@ -352,7 +362,9 @@
"hours": "{{number}}ч", "hours": "{{number}}ч",
"minutes": "{{number}}м", "minutes": "{{number}}м",
"seconds": "{{number}}сек", "seconds": "{{number}}сек",
"ago": "преди {{value}}" "ago": "преди {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Предупреждения", "warnings": "Предупреждения",
"criticals": "Критични" "criticals": "Критични"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Събития", "events": "Събития",
"plants": "Растения", "plants": "Растения",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adreça", "address": "Adreça",
"expires": "Caduca", "expires": "Caduca",
"never": "Mai", "never": "Mai",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Vist per darrer cop", "last_seen": "Vist per darrer cop",
"now": "Ara", "now": "Ara",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Fa {{value}}" "ago": "Fa {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Consultes", "totalQueries": "Consultes",
@@ -924,6 +936,19 @@
"warnings": "Avisos", "warnings": "Avisos",
"criticals": "Crítics" "criticals": "Crítics"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Esdeveniments", "events": "Esdeveniments",
"plants": "Plantes", "plants": "Plantes",
+27 -2
View File
@@ -344,6 +344,16 @@
"address": "Adresa", "address": "Adresa",
"expires": "Vyprší", "expires": "Vyprší",
"never": "Nikdy", "never": "Nikdy",
"user": "Uživatel",
"hostname": "Název zařízení",
"name": "Název",
"client_version": "Verze klienta",
"os": "OS",
"created": "Vytvořeno",
"authorized": "Autorizováno",
"is_external": "Je externí",
"update_available": "Dostupná aktualizace",
"tags": "Štítky",
"last_seen": "Naposledy viděno", "last_seen": "Naposledy viděno",
"now": "Nyní", "now": "Nyní",
"years": "{{number}}r", "years": "{{number}}r",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Před {{value}}" "ago": "Před {{value}}",
"true": "Ano",
"false": "Ne"
}, },
"technitium": { "technitium": {
"totalQueries": "Dotazy", "totalQueries": "Dotazy",
@@ -690,7 +702,7 @@
}, },
"proxmoxbackupserver": { "proxmoxbackupserver": {
"datastore_usage": "Datové úložiště", "datastore_usage": "Datové úložiště",
"failed_tasks_24h": "Neúspěšné úlohy 24h", "failed_tasks_24h": "Neúspěšné úlohy za 24h",
"cpu_usage": "Využití procesoru", "cpu_usage": "Využití procesoru",
"memory_usage": "Využití paměti" "memory_usage": "Využití paměti"
}, },
@@ -924,6 +936,19 @@
"warnings": "Upozornění", "warnings": "Upozornění",
"criticals": "Kritické" "criticals": "Kritické"
}, },
"ntfy": {
"title": "Název",
"priority": "Priorita",
"lastReceived": "Poslední přijetí",
"message": "Zpráva",
"tags": "Štítky",
"none": "Žádné",
"min": "Minimální",
"low": "Nízká",
"default": "Výchozí",
"high": "Vysoká",
"urgent": "Urgentní"
},
"plantit": { "plantit": {
"events": "Události", "events": "Události",
"plants": "Rostliny", "plants": "Rostliny",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adresse", "address": "Adresse",
"expires": "Udløber", "expires": "Udløber",
"never": "Aldrig", "never": "Aldrig",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Sidst Set", "last_seen": "Sidst Set",
"now": "Nu", "now": "Nu",
"years": "{{number}}å", "years": "{{number}}å",
@@ -352,7 +362,9 @@
"hours": "{{number}}t", "hours": "{{number}}t",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Siden" "ago": "{{value}} Siden",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Advarsler", "warnings": "Advarsler",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Titel",
"priority": "Prioritet",
"lastReceived": "Sidst modtaget",
"message": "Besked",
"tags": "Tags",
"none": "Ingen",
"min": "Min",
"low": "Lav",
"default": "Standard",
"high": "Høj",
"urgent": "Akut"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adresse", "address": "Adresse",
"expires": "Läuft ab", "expires": "Läuft ab",
"never": "Nie", "never": "Nie",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Zuletzt gesehen", "last_seen": "Zuletzt gesehen",
"now": "Jetzt", "now": "Jetzt",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}min", "minutes": "{{number}}min",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Vor {{value}}" "ago": "Vor {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Anfragen", "totalQueries": "Anfragen",
@@ -924,6 +936,19 @@
"warnings": "Warnungen", "warnings": "Warnungen",
"criticals": "Kritisch" "criticals": "Kritisch"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Ereignisse", "events": "Ereignisse",
"plants": "Pflanzen", "plants": "Pflanzen",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Διεύθυνση", "address": "Διεύθυνση",
"expires": "Λήγει", "expires": "Λήγει",
"never": "Ποτέ", "never": "Ποτέ",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Τελευταία Σύνδεση", "last_seen": "Τελευταία Σύνδεση",
"now": "Τώρα", "now": "Τώρα",
"years": "{{number}}χρόνια", "years": "{{number}}χρόνια",
@@ -352,7 +362,9 @@
"hours": "{{number}}ώρες", "hours": "{{number}}ώρες",
"minutes": "{{number}}λεπτά", "minutes": "{{number}}λεπτά",
"seconds": "{{number}}δευτερόλεπτα", "seconds": "{{number}}δευτερόλεπτα",
"ago": "{{value}} πρίν" "ago": "{{value}} πρίν",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Dirección", "address": "Dirección",
"expires": "Caduca en", "expires": "Caduca en",
"never": "Nunca", "never": "Nunca",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Visto por última vez", "last_seen": "Visto por última vez",
"now": "Ahora", "now": "Ahora",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Hace {{value}}" "ago": "Hace {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Consultas", "totalQueries": "Consultas",
@@ -924,6 +936,19 @@
"warnings": "Advertencias", "warnings": "Advertencias",
"criticals": "Críticos" "criticals": "Críticos"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Eventos", "events": "Eventos",
"plants": "Plantas", "plants": "Plantas",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Ekitaldiak", "events": "Ekitaldiak",
"plants": "Landareak", "plants": "Landareak",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+70 -45
View File
@@ -67,9 +67,9 @@
"empty_data": "Statut du sous-système inconnu" "empty_data": "Statut du sous-système inconnu"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Sain",
"degraded": "Degraded", "degraded": "Dégradé",
"no_data": "No storage data available" "no_data": "Aucune données de stockage disponible"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@@ -115,12 +115,12 @@
"jellyfin": { "jellyfin": {
"playing": "En cours", "playing": "En cours",
"transcoding": "En cours d'encodage", "transcoding": "En cours d'encodage",
"bitrate": "Bitrate", "bitrate": "Débit",
"no_active": "No Active Streams", "no_active": "Aucune lecture en cours",
"movies": "Movies", "movies": "Films",
"series": "Series", "series": "Séries",
"episodes": "Episodes", "episodes": "Épisodes",
"songs": "Songs" "songs": "Chansons"
}, },
"esphome": { "esphome": {
"offline": "Hors ligne", "offline": "Hors ligne",
@@ -190,11 +190,11 @@
"plex_connection_error": "Vérifier la connexion à Plex" "plex_connection_error": "Vérifier la connexion à Plex"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Aucune lecture en cours",
"streams": "Streams", "streams": "Lectures",
"transcodes": "Transcodes", "transcodes": "Transcodages",
"directplay": "Direct Play", "directplay": "Lectures directes",
"bitrate": "Bitrate" "bitrate": "Débit"
}, },
"omada": { "omada": {
"connectedAp": "APs connectées", "connectedAp": "APs connectées",
@@ -295,12 +295,12 @@
"available": "Disponible" "available": "Disponible"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "En attente",
"approved": "Approved", "approved": "Validées",
"available": "Available", "available": "Disponibles",
"completed": "Completed", "completed": "Complétées",
"processing": "Processing", "processing": "En traitement",
"issues": "Open Issues" "issues": "Problèmes non résolus"
}, },
"netalertx": { "netalertx": {
"total": "Total", "total": "Total",
@@ -344,6 +344,16 @@
"address": "Adresse", "address": "Adresse",
"expires": "Expire", "expires": "Expire",
"never": "Jamais", "never": "Jamais",
"user": "Utilisateur",
"hostname": "Nom d'hôte",
"name": "Nom",
"client_version": "Version client",
"os": "OS",
"created": "Créé",
"authorized": "Autorisée",
"is_external": "Externe",
"update_available": "Mise à jour disponible",
"tags": "Tags",
"last_seen": "Vu pour la dernière fois", "last_seen": "Vu pour la dernière fois",
"now": "Maintenant", "now": "Maintenant",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Il y a {{value}}" "ago": "Il y a {{value}}",
"true": "Oui",
"false": "Non"
}, },
"technitium": { "technitium": {
"totalQueries": "Requêtes", "totalQueries": "Requêtes",
@@ -619,13 +631,13 @@
"total": "Total" "total": "Total"
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Organisations",
"sites": "Sites", "sites": "Sites",
"resources": "Ressources", "resources": "Ressources",
"targets": "Cibles", "targets": "Cibles",
"traffic": "Trafique", "traffic": "Trafique",
"in": "In", "in": "Entrant",
"out": "Out" "out": "Sortant"
}, },
"peanut": { "peanut": {
"battery_charge": "Charge de la batterie", "battery_charge": "Charge de la batterie",
@@ -724,8 +736,8 @@
"volumeAvailable": "Disponible" "volumeAvailable": "Disponible"
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Channels", "channels": "Chaînes",
"streams": "Streams" "streams": "Lectures"
}, },
"mylar": { "mylar": {
"series": "Séries", "series": "Séries",
@@ -816,10 +828,10 @@
"series": "Séries" "series": "Séries"
}, },
"booklore": { "booklore": {
"libraries": "Libraries", "libraries": "Bibliothèques",
"books": "Books", "books": "Livres",
"reading": "Reading", "reading": "En cours de lecture",
"finished": "Finished" "finished": "Terminés"
}, },
"jdownloader": { "jdownloader": {
"downloadCount": "File d'attente", "downloadCount": "File d'attente",
@@ -924,6 +936,19 @@
"warnings": "Avertissements", "warnings": "Avertissements",
"criticals": "Urgent" "criticals": "Urgent"
}, },
"ntfy": {
"title": "Titre",
"priority": "Priorité",
"lastReceived": "Dernière réception",
"message": "Message",
"tags": "Tags",
"none": "Aucun",
"min": "Min",
"low": "Bas",
"default": "Défaut",
"high": "Haut",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Événements", "events": "Événements",
"plants": "Plantes", "plants": "Plantes",
@@ -1160,30 +1185,30 @@
"artists": "Artistes" "artists": "Artistes"
}, },
"arcane": { "arcane": {
"containers": "Containers", "containers": "Conteneurs",
"images": "Images", "images": "Images",
"image_updates": "Image Updates", "image_updates": "Mises à jour d'images",
"images_unused": "Unused", "images_unused": "Inutilisées",
"environment_required": "Environment ID Required" "environment_required": "ID d'environnement requis"
}, },
"dockhand": { "dockhand": {
"running": "Running", "running": "En cours d'exécution",
"stopped": "Stopped", "stopped": "Arrêtés",
"cpu": "CPU", "cpu": "CPU",
"memory": "Memory", "memory": "Mémoire",
"images": "Images", "images": "Images",
"volumes": "Volumes", "volumes": "Volumes",
"events_today": "Events Today", "events_today": "Événements aujourd'hui",
"pending_updates": "Pending Updates", "pending_updates": "Mises à jour en attente",
"stacks": "Stacks", "stacks": "Stacks",
"paused": "Paused", "paused": "En pause",
"total": "Total", "total": "Total",
"environment_not_found": "Environment Not Found" "environment_not_found": "Environnement introuvable"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "Mangé",
"burned": "Burned", "burned": "Brûlé",
"remaining": "Remaining", "remaining": "Restant",
"steps": "Steps" "steps": "Pas"
} }
} }
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "כתובת", "address": "כתובת",
"expires": "תאריך תפוגה", "expires": "תאריך תפוגה",
"never": "אף פעם", "never": "אף פעם",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "נראה לאחרונה", "last_seen": "נראה לאחרונה",
"now": "כעת", "now": "כעת",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "שאילתות", "totalQueries": "שאילתות",
@@ -924,6 +936,19 @@
"warnings": "התראות", "warnings": "התראות",
"criticals": "קריטיים" "criticals": "קריטיים"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "אירועים", "events": "אירועים",
"plants": "צמחים", "plants": "צמחים",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+37 -12
View File
@@ -67,9 +67,9 @@
"empty_data": "Stanje podsustava nepoznato" "empty_data": "Stanje podsustava nepoznato"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Zdravo",
"degraded": "Degraded", "degraded": "Degradirano",
"no_data": "No storage data available" "no_data": "Nema dostupnih podataka spremišta"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -116,7 +116,7 @@
"playing": "Reprodukcija u tijeku", "playing": "Reprodukcija u tijeku",
"transcoding": "Prekodiranje", "transcoding": "Prekodiranje",
"bitrate": "Stopa bitova", "bitrate": "Stopa bitova",
"no_active": "No Active Streams", "no_active": "Nema aktivnih prijenosa",
"movies": "Filmovi", "movies": "Filmovi",
"series": "Serije", "series": "Serije",
"episodes": "Epizode", "episodes": "Epizode",
@@ -190,8 +190,8 @@
"plex_connection_error": "Provjeri Plex vezu" "plex_connection_error": "Provjeri Plex vezu"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Nema aktivnih prijenosa",
"streams": "Streams", "streams": "Prijenosi",
"transcodes": "Prekodiranja", "transcodes": "Prekodiranja",
"directplay": "Izravna reprodukcija", "directplay": "Izravna reprodukcija",
"bitrate": "Stopa bitova" "bitrate": "Stopa bitova"
@@ -344,6 +344,16 @@
"address": "Adresa", "address": "Adresa",
"expires": "Isteče", "expires": "Isteče",
"never": "Nikada", "never": "Nikada",
"user": "Korisnik",
"hostname": "Ime računala",
"name": "Ime",
"client_version": "Verzija klijenta",
"os": "OS",
"created": "Stvoreno",
"authorized": "Autorizirano",
"is_external": "Je eksterna",
"update_available": "Dostupna je nova verzija",
"tags": "Oznake",
"last_seen": "Zadnje viđeno", "last_seen": "Zadnje viđeno",
"now": "Sada", "now": "Sada",
"years": "{{number}} god", "years": "{{number}} god",
@@ -352,7 +362,9 @@
"hours": "{{number}} h", "hours": "{{number}} h",
"minutes": "{{number}} min", "minutes": "{{number}} min",
"seconds": "{{number}} s", "seconds": "{{number}} s",
"ago": "Prije {{value}}" "ago": "Prije {{value}}",
"true": "Da",
"false": "Ne"
}, },
"technitium": { "technitium": {
"totalQueries": "Upiti", "totalQueries": "Upiti",
@@ -624,8 +636,8 @@
"resources": "Resursi", "resources": "Resursi",
"targets": "Ciljevi", "targets": "Ciljevi",
"traffic": "Promet", "traffic": "Promet",
"in": "In", "in": "Ulaz",
"out": "Out" "out": "Izlaz"
}, },
"peanut": { "peanut": {
"battery_charge": "Napunjenost baterije", "battery_charge": "Napunjenost baterije",
@@ -725,7 +737,7 @@
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Kanali", "channels": "Kanali",
"streams": "Streams" "streams": "Prijenosi"
}, },
"mylar": { "mylar": {
"series": "Serije", "series": "Serije",
@@ -924,6 +936,19 @@
"warnings": "Upozorenja", "warnings": "Upozorenja",
"criticals": "Kritično" "criticals": "Kritično"
}, },
"ntfy": {
"title": "Naslov",
"priority": "Prioritet",
"lastReceived": "Zadnji primitak",
"message": "Poruka",
"tags": "Oznake",
"none": "Ništa",
"min": "Min.",
"low": "Nisko",
"default": "Standardno",
"high": "Visoko",
"urgent": "Hitno"
},
"plantit": { "plantit": {
"events": "Događaji", "events": "Događaji",
"plants": "Biljke", "plants": "Biljke",
@@ -1109,7 +1134,7 @@
"unhealthy": "Nezdravo", "unhealthy": "Nezdravo",
"unknown": "Nepoznato", "unknown": "Nepoznato",
"servers": "Serveri", "servers": "Serveri",
"stacks": "Stacks", "stacks": "Stogovi",
"containers": "Kontejneri" "containers": "Kontejneri"
}, },
"filebrowser": { "filebrowser": {
@@ -1175,7 +1200,7 @@
"volumes": "Jedinice memorije", "volumes": "Jedinice memorije",
"events_today": "Događanja danas", "events_today": "Događanja danas",
"pending_updates": "Aktualiziranja na čekanju", "pending_updates": "Aktualiziranja na čekanju",
"stacks": "Stacks", "stacks": "Stogovi",
"paused": "Pauzirano", "paused": "Pauzirano",
"total": "Ukupno", "total": "Ukupno",
"environment_not_found": "Okruženje nije pronađeno" "environment_not_found": "Okruženje nije pronađeno"
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Cím", "address": "Cím",
"expires": "Lejár", "expires": "Lejár",
"never": "Soha", "never": "Soha",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Utoljára látott", "last_seen": "Utoljára látott",
"now": "Most", "now": "Most",
"years": "{{number}}év", "years": "{{number}}év",
@@ -352,7 +362,9 @@
"hours": "{{number}}ó", "hours": "{{number}}ó",
"minutes": "{{number}}p", "minutes": "{{number}}p",
"seconds": "{{number}}mp", "seconds": "{{number}}mp",
"ago": "{{value}} Ezelőtt" "ago": "{{value}} Ezelőtt",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Lekérdezések", "totalQueries": "Lekérdezések",
@@ -924,6 +936,19 @@
"warnings": "Figyelmeztetések", "warnings": "Figyelmeztetések",
"criticals": "Kritikusok" "criticals": "Kritikusok"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Események", "events": "Események",
"plants": "Növények", "plants": "Növények",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Alamat", "address": "Alamat",
"expires": "Kedaluwarsa", "expires": "Kedaluwarsa",
"never": "Tidak Pernah", "never": "Tidak Pernah",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Terakhir terlihat", "last_seen": "Terakhir terlihat",
"now": "Sekarang", "now": "Sekarang",
"years": "{{number}}thn", "years": "{{number}}thn",
@@ -352,7 +362,9 @@
"hours": "{{number}}j", "hours": "{{number}}j",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}d", "seconds": "{{number}}d",
"ago": "{{value}} Yang Lalu" "ago": "{{value}} Yang Lalu",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Peringatan", "warnings": "Peringatan",
"criticals": "Kritis" "criticals": "Kritis"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Acara", "events": "Acara",
"plants": "Tanaman", "plants": "Tanaman",
+107 -82
View File
@@ -61,15 +61,15 @@
"wlan_devices": "Dispositivi WLAN", "wlan_devices": "Dispositivi WLAN",
"lan_users": "Utenti LAN", "lan_users": "Utenti LAN",
"wlan_users": "Utenti WLAN", "wlan_users": "Utenti WLAN",
"up": "UP", "up": "SU",
"down": "DOWN", "down": "DOWN",
"wait": "Please wait", "wait": "Attendere prego",
"empty_data": "Stato del sottosistema sconosciuto" "empty_data": "Stato del sottosistema sconosciuto"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Sano",
"degraded": "Degraded", "degraded": "Degradato",
"no_data": "No storage data available" "no_data": "Nessun dato di archiviazione disponibile"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -88,7 +88,7 @@
"partial": "Parziale" "partial": "Parziale"
}, },
"ping": { "ping": {
"error": "Error", "error": "Errore",
"ping": "Ping", "ping": "Ping",
"down": "Down", "down": "Down",
"up": "Up", "up": "Up",
@@ -96,11 +96,11 @@
}, },
"siteMonitor": { "siteMonitor": {
"http_status": "Stato HTTP", "http_status": "Stato HTTP",
"error": "Error", "error": "Errore",
"response": "Risposta", "response": "Risposta",
"down": "Down", "down": "Giù",
"up": "Up", "up": "Su",
"not_available": "Not Available" "not_available": "Non disponibile"
}, },
"emby": { "emby": {
"playing": "In riproduzione", "playing": "In riproduzione",
@@ -113,21 +113,21 @@
"songs": "Canzoni" "songs": "Canzoni"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "In riproduzione",
"transcoding": "Transcoding", "transcoding": "Transcodifica",
"bitrate": "Bitrate", "bitrate": "Velocità in bit",
"no_active": "No Active Streams", "no_active": "Nessuna Trasmissione Attiva",
"movies": "Movies", "movies": "Film",
"series": "Serie", "series": "Serie",
"episodes": "Episodes", "episodes": "Episodi",
"songs": "Songs" "songs": "Canzoni"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Disconnesso",
"offline_alt": "Offline", "offline_alt": "Disconnesso",
"online": "Online", "online": "Online",
"total": "Total", "total": "Totale",
"unknown": "Unknown" "unknown": "Sconosciuto"
}, },
"evcc": { "evcc": {
"pv_power": "Produzione", "pv_power": "Produzione",
@@ -148,7 +148,7 @@
"unread": "Non letto" "unread": "Non letto"
}, },
"fritzbox": { "fritzbox": {
"connectionStatus": "Status", "connectionStatus": "Stato",
"connectionStatusUnconfigured": "Non configurato", "connectionStatusUnconfigured": "Non configurato",
"connectionStatusConnecting": "Connessione in corso", "connectionStatusConnecting": "Connessione in corso",
"connectionStatusAuthenticating": "In fase di autenticazione", "connectionStatusAuthenticating": "In fase di autenticazione",
@@ -156,11 +156,11 @@
"connectionStatusDisconnecting": "Disconnessione in corso", "connectionStatusDisconnecting": "Disconnessione in corso",
"connectionStatusDisconnected": "Disconnesso", "connectionStatusDisconnected": "Disconnesso",
"connectionStatusConnected": "Connesso", "connectionStatusConnected": "Connesso",
"uptime": "Uptime", "uptime": "Tempo di funzionamento",
"maxDown": "Max. Down", "maxDown": "Max. Down",
"maxUp": "Max. Up", "maxUp": "Max. Up",
"down": "Down", "down": "Giù",
"up": "Up", "up": "Su",
"received": "Ricevuti", "received": "Ricevuti",
"sent": "Inviati", "sent": "Inviati",
"externalIPAddress": "IP Esterno", "externalIPAddress": "IP Esterno",
@@ -184,17 +184,17 @@
}, },
"tautulli": { "tautulli": {
"playing": "In riproduzione", "playing": "In riproduzione",
"transcoding": "Transcoding", "transcoding": "Transcodifica",
"bitrate": "Bitrate", "bitrate": "Velocità in bit",
"no_active": "No Active Streams", "no_active": "Nessuna Trasmissione Attiva",
"plex_connection_error": "Controllare la connessione a Plex" "plex_connection_error": "Controllare la connessione a Plex"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Nessuna Trasmissione Attiva",
"streams": "Streams", "streams": "Flussi",
"transcodes": "Transcodes", "transcodes": "Transcodifiche",
"directplay": "Direct Play", "directplay": "Riproduzione diretta",
"bitrate": "Bitrate" "bitrate": "Velocità in bit"
}, },
"omada": { "omada": {
"connectedAp": "AP Connessi", "connectedAp": "AP Connessi",
@@ -211,11 +211,11 @@
"plex": { "plex": {
"streams": "Trasmissioni attive", "streams": "Trasmissioni attive",
"albums": "Album", "albums": "Album",
"movies": "Movies", "movies": "Film",
"tv": "Programmi televisivi" "tv": "Programmi televisivi"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "Rapporto",
"queue": "Coda", "queue": "Coda",
"timeleft": "Tempo Rimanente" "timeleft": "Tempo Rimanente"
}, },
@@ -227,13 +227,13 @@
"transmission": { "transmission": {
"download": "Download", "download": "Download",
"upload": "Upload", "upload": "Upload",
"leech": "Leech", "leech": "In download",
"seed": "Seed" "seed": "Seed"
}, },
"qbittorrent": { "qbittorrent": {
"download": "Download", "download": "Download",
"upload": "Upload", "upload": "Upload",
"leech": "Leech", "leech": "In download",
"seed": "Seed" "seed": "Seed"
}, },
"qnap": { "qnap": {
@@ -344,6 +344,16 @@
"address": "Indirizzo", "address": "Indirizzo",
"expires": "Scade", "expires": "Scade",
"never": "Mai", "never": "Mai",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Ultima visualizzazione", "last_seen": "Ultima visualizzazione",
"now": "Adesso", "now": "Adesso",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}o", "hours": "{{number}}o",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Fa" "ago": "{{value}} Fa",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -430,21 +442,21 @@
"medusa": { "medusa": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued", "queued": "Queued",
"series": "Series" "series": "Serie"
}, },
"minecraft": { "minecraft": {
"players": "Giocatori", "players": "Giocatori",
"version": "Versione", "version": "Versione",
"status": "Status", "status": "Stato",
"up": "Online", "up": "Online",
"down": "Offline" "down": "Disconnesso"
}, },
"miniflux": { "miniflux": {
"read": "Letti", "read": "Letti",
"unread": "Unread" "unread": "Non letto"
}, },
"authentik": { "authentik": {
"users": "Users", "users": "Utenti",
"loginsLast24H": "Accessi (24h)", "loginsLast24H": "Accessi (24h)",
"failedLoginsLast24H": "Accessi Falliti (24h)" "failedLoginsLast24H": "Accessi Falliti (24h)"
}, },
@@ -456,19 +468,19 @@
}, },
"glances": { "glances": {
"cpu": "CPU", "cpu": "CPU",
"load": "Load", "load": "Carico",
"wait": "Please wait", "wait": "Attendere prego",
"temp": "TEMP", "temp": "TEMP",
"_temp": "Temp.", "_temp": "Temp.",
"warn": "Avviso", "warn": "Avviso",
"uptime": "UP", "uptime": "SU",
"total": "Total", "total": "Totale",
"free": "Free", "free": "Libero",
"used": "Used", "used": "Utilizzato",
"days": "d", "days": "g",
"hours": "h", "hours": "o",
"crit": "Critico", "crit": "Critico",
"read": "Read", "read": "Letti",
"write": "Scrittura", "write": "Scrittura",
"gpu": "GPU", "gpu": "GPU",
"mem": "Mem.", "mem": "Mem.",
@@ -489,57 +501,57 @@
"1-day": "Prevalentemente Soleggiato", "1-day": "Prevalentemente Soleggiato",
"1-night": "Prevalentemente Sereno", "1-night": "Prevalentemente Sereno",
"2-day": "Parzialmente Nuvoloso", "2-day": "Parzialmente Nuvoloso",
"2-night": "Partly Cloudy", "2-night": "Parzialmente Nuvoloso",
"3-day": "Nuvoloso", "3-day": "Nuvoloso",
"3-night": "Cloudy", "3-night": "Nuvoloso",
"45-day": "Nebbioso", "45-day": "Nebbioso",
"45-night": "Foggy", "45-night": "Nebbioso",
"48-day": "Foggy", "48-day": "Nebbioso",
"48-night": "Foggy", "48-night": "Nebbioso",
"51-day": "Pioggerella Leggera", "51-day": "Pioggerella Leggera",
"51-night": "Light Drizzle", "51-night": "Pioggerella Leggera",
"53-day": "Pioggerella", "53-day": "Pioggerella",
"53-night": "Drizzle", "53-night": "Pioggerella",
"55-day": "Pioggerella Pesante", "55-day": "Pioggerella Pesante",
"55-night": "Heavy Drizzle", "55-night": "Pioggerella Pesante",
"56-day": "Leggera Pioggia Gelata", "56-day": "Leggera Pioggia Gelata",
"56-night": "Light Freezing Drizzle", "56-night": "Leggera Pioggia Gelata",
"57-day": "Pioggerella Gelata", "57-day": "Pioggerella Gelata",
"57-night": "Freezing Drizzle", "57-night": "Pioggerella Gelata",
"61-day": "Pioggia Leggera", "61-day": "Pioggia Leggera",
"61-night": "Light Rain", "61-night": "Pioggia Leggera",
"63-day": "Pioggia", "63-day": "Pioggia",
"63-night": "Rain", "63-night": "Pioggia",
"65-day": "Pioggia Intensa", "65-day": "Pioggia Intensa",
"65-night": "Heavy Rain", "65-night": "Pioggia Intensa",
"66-day": "Grandine", "66-day": "Grandine",
"66-night": "Freezing Rain", "66-night": "Grandine",
"67-day": "Freezing Rain", "67-day": "Grandine",
"67-night": "Freezing Rain", "67-night": "Grandine",
"71-day": "Leggera Nevicata", "71-day": "Leggera Nevicata",
"71-night": "Light Snow", "71-night": "Leggera Nevicata",
"73-day": "Neve", "73-day": "Neve",
"73-night": "Snow", "73-night": "Neve",
"75-day": "Nevicata Intensa", "75-day": "Nevicata Intensa",
"75-night": "Heavy Snow", "75-night": "Nevicata Intensa",
"77-day": "Fiocchi di Neve", "77-day": "Fiocchi di Neve",
"77-night": "Snow Grains", "77-night": "Fiocchi di Neve",
"80-day": "Leggeri Rovesci", "80-day": "Leggeri Rovesci",
"80-night": "Light Showers", "80-night": "Leggeri Rovesci",
"81-day": "Rovesci", "81-day": "Rovesci",
"81-night": "Showers", "81-night": "Rovesci",
"82-day": "Intensi Rovesci", "82-day": "Intensi Rovesci",
"82-night": "Heavy Showers", "82-night": "Intensi Rovesci",
"85-day": "Rovesci di Neve", "85-day": "Rovesci di Neve",
"85-night": "Snow Showers", "85-night": "Rovesci di Neve",
"86-day": "Snow Showers", "86-day": "Rovesci di Neve",
"86-night": "Snow Showers", "86-night": "Rovesci di Neve",
"95-day": "Temporale", "95-day": "Temporale",
"95-night": "Thunderstorm", "95-night": "Temporale",
"96-day": "Temporale con grandine", "96-day": "Temporale con grandine",
"96-night": "Thunderstorm With Hail", "96-night": "Temporale con grandine",
"99-day": "Thunderstorm With Hail", "99-day": "Temporale con grandine",
"99-night": "Thunderstorm With Hail" "99-night": "Temporale con grandine"
}, },
"homebridge": { "homebridge": {
"available_update": "Sistema", "available_update": "Sistema",
@@ -924,6 +936,19 @@
"warnings": "Avvisi", "warnings": "Avvisi",
"criticals": "Critici" "criticals": "Critici"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Eventi", "events": "Eventi",
"plants": "Piante", "plants": "Piante",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "アドレス", "address": "アドレス",
"expires": "失効", "expires": "失効",
"never": "なし", "never": "なし",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "最終日時", "last_seen": "最終日時",
"now": "現在", "now": "現在",
"years": "{{number}}年", "years": "{{number}}年",
@@ -352,7 +362,9 @@
"hours": "{{number}}時間", "hours": "{{number}}時間",
"minutes": "{{number}}分", "minutes": "{{number}}分",
"seconds": "{{number}}秒", "seconds": "{{number}}秒",
"ago": "{{value}} 前" "ago": "{{value}} 前",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "警告", "warnings": "警告",
"criticals": "重大" "criticals": "重大"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "イベント", "events": "イベント",
"plants": "植物", "plants": "植物",
+29 -4
View File
@@ -67,9 +67,9 @@
"empty_data": "서브시스템 상태 알 수 없음" "empty_data": "서브시스템 상태 알 수 없음"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "정상",
"degraded": "Degraded", "degraded": "성능 저하",
"no_data": "No storage data available" "no_data": "저장소 데이터 없음"
}, },
"docker": { "docker": {
"rx": "수신", "rx": "수신",
@@ -344,6 +344,16 @@
"address": "주소", "address": "주소",
"expires": "만료", "expires": "만료",
"never": "없음", "never": "없음",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "마지막 접속", "last_seen": "마지막 접속",
"now": "지금", "now": "지금",
"years": "{{number}}년", "years": "{{number}}년",
@@ -352,7 +362,9 @@
"hours": "{{number}}시간", "hours": "{{number}}시간",
"minutes": "{{number}}분", "minutes": "{{number}}분",
"seconds": "{{number}}초", "seconds": "{{number}}초",
"ago": "{{value}} 전" "ago": "{{value}} 전",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "총 쿼리", "totalQueries": "총 쿼리",
@@ -924,6 +936,19 @@
"warnings": "경고", "warnings": "경고",
"criticals": "심각" "criticals": "심각"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "이벤트", "events": "이벤트",
"plants": "식물", "plants": "식물",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Sekarang", "now": "Sekarang",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Lepas" "ago": "{{value}} Lepas",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Amaran", "warnings": "Amaran",
"criticals": "Kritikal" "criticals": "Kritikal"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+51 -26
View File
@@ -67,9 +67,9 @@
"empty_data": "Subsysteem status onbekend" "empty_data": "Subsysteem status onbekend"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Gezond",
"degraded": "Degraded", "degraded": "Gedegradeerd",
"no_data": "No storage data available" "no_data": "Geen opslaggegevens beschikbaar"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -113,14 +113,14 @@
"songs": "Nummers" "songs": "Nummers"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "Speelt",
"transcoding": "Transcoding", "transcoding": "Bezig met transcoderen",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams", "no_active": "Geen Actieve Streams",
"movies": "Movies", "movies": "Films",
"series": "Series", "series": "Series",
"episodes": "Episodes", "episodes": "Afleveringen",
"songs": "Songs" "songs": "Nummers"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -190,10 +190,10 @@
"plex_connection_error": "Controleer Plex Connectie" "plex_connection_error": "Controleer Plex Connectie"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Geen Actieve Streams",
"streams": "Streams", "streams": "Streams",
"transcodes": "Transcodes", "transcodes": "Transcodes",
"directplay": "Direct Play", "directplay": "Directe Afspelen",
"bitrate": "Bitrate" "bitrate": "Bitrate"
}, },
"omada": { "omada": {
@@ -295,12 +295,12 @@
"available": "Beschikbaar" "available": "Beschikbaar"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "In afwachting",
"approved": "Approved", "approved": "Goedgekeurd",
"available": "Available", "available": "Beschikbaar",
"completed": "Completed", "completed": "Voltooid",
"processing": "Processing", "processing": "Verwerken",
"issues": "Open Issues" "issues": "Openstaande issues"
}, },
"netalertx": { "netalertx": {
"total": "Totaal", "total": "Totaal",
@@ -344,6 +344,16 @@
"address": "Adres", "address": "Adres",
"expires": "Verloopt", "expires": "Verloopt",
"never": "Nooit", "never": "Nooit",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Laatst Gezien", "last_seen": "Laatst Gezien",
"now": "Nu", "now": "Nu",
"years": "{{number}}j", "years": "{{number}}j",
@@ -352,7 +362,9 @@
"hours": "{{number}}u", "hours": "{{number}}u",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Geleden" "ago": "{{value}} Geleden",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Verzoeken", "totalQueries": "Verzoeken",
@@ -621,11 +633,11 @@
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Orgs",
"sites": "Sites", "sites": "Sites",
"resources": "Resources", "resources": "Bronnen",
"targets": "Targets", "targets": "Doelen",
"traffic": "Traffic", "traffic": "Netwerkverkeer",
"in": "In", "in": "In",
"out": "Out" "out": "Uit"
}, },
"peanut": { "peanut": {
"battery_charge": "Batterij opladen", "battery_charge": "Batterij opladen",
@@ -724,7 +736,7 @@
"volumeAvailable": "Beschikbaar" "volumeAvailable": "Beschikbaar"
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Channels", "channels": "Kanalen",
"streams": "Streams" "streams": "Streams"
}, },
"mylar": { "mylar": {
@@ -792,7 +804,7 @@
"gross_percent_today": "Vandaag", "gross_percent_today": "Vandaag",
"gross_percent_1y": "Een jaar", "gross_percent_1y": "Een jaar",
"gross_percent_max": "Altijd", "gross_percent_max": "Altijd",
"net_worth": "Net Worth" "net_worth": "Totaal eigen vermogen"
}, },
"audiobookshelf": { "audiobookshelf": {
"podcasts": "Podcasts", "podcasts": "Podcasts",
@@ -816,8 +828,8 @@
"series": "Series" "series": "Series"
}, },
"booklore": { "booklore": {
"libraries": "Libraries", "libraries": "Bibliotheken",
"books": "Books", "books": "Boeken",
"reading": "Reading", "reading": "Reading",
"finished": "Finished" "finished": "Finished"
}, },
@@ -924,6 +936,19 @@
"warnings": "Waarschuwingen", "warnings": "Waarschuwingen",
"criticals": "Kritiek" "criticals": "Kritiek"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Gebeurtenissen", "events": "Gebeurtenissen",
"plants": "Planten", "plants": "Planten",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adresse", "address": "Adresse",
"expires": "Utgår", "expires": "Utgår",
"never": "Aldri", "never": "Aldri",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Sist sett", "last_seen": "Sist sett",
"now": "Nå", "now": "Nå",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Advarsler", "warnings": "Advarsler",
"criticals": "Kritiske" "criticals": "Kritiske"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Begivenheter", "events": "Begivenheter",
"plants": "Planter", "plants": "Planter",
+29 -4
View File
@@ -67,9 +67,9 @@
"empty_data": "Status podsystemu nieznany" "empty_data": "Status podsystemu nieznany"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Zdrowy",
"degraded": "Degraded", "degraded": "Zdegradowany",
"no_data": "No storage data available" "no_data": "Brak danych o miejscu przechowywania"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@@ -344,6 +344,16 @@
"address": "Adres", "address": "Adres",
"expires": "Wygasa za", "expires": "Wygasa za",
"never": "Nigdy", "never": "Nigdy",
"user": "Użytkownik",
"hostname": "Host",
"name": "Nazwa",
"client_version": "Wersja klienta",
"os": "System operacyjny",
"created": "Utworzony",
"authorized": "Autoryzowany",
"is_external": "Jest zewnętrzny",
"update_available": "Dostępna aktualizacja",
"tags": "Tagi",
"last_seen": "Ostatnio dostępny", "last_seen": "Ostatnio dostępny",
"now": "Teraz", "now": "Teraz",
"years": "{{number}}rok/lat", "years": "{{number}}rok/lat",
@@ -352,7 +362,9 @@
"hours": "{{number}}godzin", "hours": "{{number}}godzin",
"minutes": "{{number}}miesięcy", "minutes": "{{number}}miesięcy",
"seconds": "{{number}}sekund", "seconds": "{{number}}sekund",
"ago": "{{value}} temu" "ago": "{{value}} temu",
"true": "Tak",
"false": "Nie"
}, },
"technitium": { "technitium": {
"totalQueries": "Zapytania", "totalQueries": "Zapytania",
@@ -924,6 +936,19 @@
"warnings": "Ostrzeżenia", "warnings": "Ostrzeżenia",
"criticals": "Krytyczny" "criticals": "Krytyczny"
}, },
"ntfy": {
"title": "Tytuł",
"priority": "Priorytet",
"lastReceived": "Ostatnio odebrane",
"message": "Wiadomość",
"tags": "Tagi",
"none": "Brak",
"min": "Min",
"low": "Niski",
"default": "Domyślny",
"high": "Wysoki",
"urgent": "Pilny"
},
"plantit": { "plantit": {
"events": "Wydarzenia", "events": "Wydarzenia",
"plants": "Rośliny", "plants": "Rośliny",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Endereço", "address": "Endereço",
"expires": "Expira", "expires": "Expira",
"never": "Nunca", "never": "Nunca",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Última Vez Visto", "last_seen": "Última Vez Visto",
"now": "Agora", "now": "Agora",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Atrás" "ago": "{{value}} Atrás",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Avisos", "warnings": "Avisos",
"criticals": "Críticos" "criticals": "Críticos"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Eventos", "events": "Eventos",
"plants": "Plantas", "plants": "Plantas",
+47 -22
View File
@@ -67,9 +67,9 @@
"empty_data": "Status do Subsistema desconhecido" "empty_data": "Status do Subsistema desconhecido"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Saudável",
"degraded": "Degraded", "degraded": "Degradado",
"no_data": "No storage data available" "no_data": "Sem dados de armazenamento disponíveis"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -114,9 +114,9 @@
}, },
"jellyfin": { "jellyfin": {
"playing": "Jogando", "playing": "Jogando",
"transcoding": "Transcoding", "transcoding": "Transcodificando",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams", "no_active": "Sem Transmissões Ativas",
"movies": "Filmes", "movies": "Filmes",
"series": "Séries", "series": "Séries",
"episodes": "Episódios", "episodes": "Episódios",
@@ -190,10 +190,10 @@
"plex_connection_error": "Verifique a conexão do Plex" "plex_connection_error": "Verifique a conexão do Plex"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Sem Transmissões Ativas",
"streams": "Streams", "streams": "Transmissões",
"transcodes": "Transcodes", "transcodes": "Transcodificações",
"directplay": "Direct Play", "directplay": "Reprodução direta",
"bitrate": "Bitrate" "bitrate": "Bitrate"
}, },
"omada": { "omada": {
@@ -344,6 +344,16 @@
"address": "Endereço", "address": "Endereço",
"expires": "Expira em", "expires": "Expira em",
"never": "Nunca", "never": "Nunca",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Visto por último", "last_seen": "Visto por último",
"now": "Agora", "now": "Agora",
"years": "{{number}}a", "years": "{{number}}a",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Atrás" "ago": "{{value}} Atrás",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Consultas", "totalQueries": "Consultas",
@@ -619,13 +631,13 @@
"total": "Total" "total": "Total"
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Organizações",
"sites": "Sites", "sites": "Sites",
"resources": "Recursos", "resources": "Recursos",
"targets": "Targets", "targets": "Alvos",
"traffic": "Traffic", "traffic": "Tráfego",
"in": "In", "in": "Em",
"out": "Out" "out": "Saída"
}, },
"peanut": { "peanut": {
"battery_charge": "Carga da bateria", "battery_charge": "Carga da bateria",
@@ -924,6 +936,19 @@
"warnings": "Alertas", "warnings": "Alertas",
"criticals": "Críticos" "criticals": "Críticos"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Eventos", "events": "Eventos",
"plants": "Plantas", "plants": "Plantas",
@@ -1164,11 +1189,11 @@
"images": "Imagens", "images": "Imagens",
"image_updates": "Atualizações de Imagem", "image_updates": "Atualizações de Imagem",
"images_unused": "Não utilizado", "images_unused": "Não utilizado",
"environment_required": "Environment ID Required" "environment_required": "ID do ambiente necessário"
}, },
"dockhand": { "dockhand": {
"running": "Executando", "running": "Executando",
"stopped": "Stopped", "stopped": "Parado",
"cpu": "CPU", "cpu": "CPU",
"memory": "Memória", "memory": "Memória",
"images": "Imagens", "images": "Imagens",
@@ -1178,12 +1203,12 @@
"stacks": "Pilhas", "stacks": "Pilhas",
"paused": "Pausado", "paused": "Pausado",
"total": "Total", "total": "Total",
"environment_not_found": "Environment Not Found" "environment_not_found": "Ambiente não encontrado"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "Comido",
"burned": "Burned", "burned": "Queimado",
"remaining": "Remaining", "remaining": "Restante",
"steps": "Steps" "steps": "Passos"
} }
} }
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adresă", "address": "Adresă",
"expires": "Expiră", "expires": "Expiră",
"never": "Niciodată", "never": "Niciodată",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Acum", "now": "Acum",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Critice" "criticals": "Critice"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Evenimente", "events": "Evenimente",
"plants": "Plante", "plants": "Plante",
+80 -55
View File
@@ -67,14 +67,14 @@
"empty_data": "Статус подсистемы неизвестен" "empty_data": "Статус подсистемы неизвестен"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Здоров",
"degraded": "Degraded", "degraded": "Деградация",
"no_data": "No storage data available" "no_data": "Нет доступных данных о хранилище"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
"tx": "TX", "tx": "TX",
"mem": "Память", "mem": "ОЗУ",
"cpu": "ЦП", "cpu": "ЦП",
"running": "Запущено", "running": "Запущено",
"offline": "Не в сети", "offline": "Не в сети",
@@ -184,15 +184,15 @@
}, },
"tautulli": { "tautulli": {
"playing": "Играет", "playing": "Играет",
"transcoding": "Транскодируется", "transcoding": "Перекодируется",
"bitrate": "Битрейт", "bitrate": "Битрейт",
"no_active": "Нет активных стримов", "no_active": "Нет активных потоков",
"plex_connection_error": "Проверка соединения Plex" "plex_connection_error": "Проверка соединения Plex"
}, },
"tracearr": { "tracearr": {
"no_active": "Нет активных потоков", "no_active": "Нет активных потоков",
"streams": "Потоки", "streams": "Потоки",
"transcodes": "Transcodes", "transcodes": "Перекодирования",
"directplay": "Прямое воспроизведение", "directplay": "Прямое воспроизведение",
"bitrate": "Битрейт" "bitrate": "Битрейт"
}, },
@@ -215,18 +215,18 @@
"tv": "Сериалы" "tv": "Сериалы"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "", "rate": "Скорость",
"queue": "Очередь", "queue": "Очередь",
"timeleft": "Осталось" "timeleft": "Осталось"
}, },
"rutorrent": { "rutorrent": {
"active": "Активно", "active": "Активно",
"upload": "Загрузка", "upload": "Отдача",
"download": "Скачивание" "download": "Скачивание"
}, },
"transmission": { "transmission": {
"download": "Скачивание", "download": "Скачивание",
"upload": "Загрузка", "upload": "Отдача",
"leech": "Лич", "leech": "Лич",
"seed": "Сид" "seed": "Сид"
}, },
@@ -295,7 +295,7 @@
"available": "Доступно" "available": "Доступно"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "Ожидают",
"approved": "Одобрено", "approved": "Одобрено",
"available": "Доступно", "available": "Доступно",
"completed": "Завершено", "completed": "Завершено",
@@ -344,6 +344,16 @@
"address": "Адрес", "address": "Адрес",
"expires": "Истекает", "expires": "Истекает",
"never": "Никогда", "never": "Никогда",
"user": "Пользователь",
"hostname": "Имя хоста",
"name": "Имя",
"client_version": "Версия клиента",
"os": "ОС",
"created": "Создано",
"authorized": "Авторизовано",
"is_external": "Внешний",
"update_available": "Доступно обновление",
"tags": "Теги",
"last_seen": "Последнее посещение", "last_seen": "Последнее посещение",
"now": "Только что", "now": "Только что",
"years": "{{number}}г", "years": "{{number}}г",
@@ -352,7 +362,9 @@
"hours": "{{number}}ч", "hours": "{{number}}ч",
"minutes": "{{number}}м", "minutes": "{{number}}м",
"seconds": "{{number}}с", "seconds": "{{number}}с",
"ago": "{{value}} назад" "ago": "{{value}} назад",
"true": "Да",
"false": "Нет"
}, },
"technitium": { "technitium": {
"totalQueries": "Запросы", "totalQueries": "Запросы",
@@ -620,12 +632,12 @@
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Orgs",
"sites": "Sites", "sites": "Сайты",
"resources": "Resources", "resources": "Ресурсы",
"targets": "Targets", "targets": "Цели",
"traffic": "Traffic", "traffic": "Трафик",
"in": "In", "in": "Входящий",
"out": "Out" "out": "Исходящий"
}, },
"peanut": { "peanut": {
"battery_charge": "Заряд батареи", "battery_charge": "Заряд батареи",
@@ -724,8 +736,8 @@
"volumeAvailable": "Доступно" "volumeAvailable": "Доступно"
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Channels", "channels": "Каналы",
"streams": "Streams" "streams": "Потоки"
}, },
"mylar": { "mylar": {
"series": "Серии", "series": "Серии",
@@ -816,10 +828,10 @@
"series": "Серии" "series": "Серии"
}, },
"booklore": { "booklore": {
"libraries": "Libraries", "libraries": "Библиотеки",
"books": "Books", "books": "Книги",
"reading": "Reading", "reading": "Читаю",
"finished": "Finished" "finished": "Завершено"
}, },
"jdownloader": { "jdownloader": {
"downloadCount": "Очередь", "downloadCount": "Очередь",
@@ -924,6 +936,19 @@
"warnings": "Предупреждения", "warnings": "Предупреждения",
"criticals": "Критические" "criticals": "Критические"
}, },
"ntfy": {
"title": "Название",
"priority": "Приоритет",
"lastReceived": "Last Received",
"message": "Сообщение",
"tags": "Теги",
"none": "Отсутствует",
"min": "Минимальный",
"low": "Низкий",
"default": "По-умолчанию",
"high": "Высокий",
"urgent": "Срочный"
},
"plantit": { "plantit": {
"events": "События", "events": "События",
"plants": "Растения", "plants": "Растения",
@@ -1062,7 +1087,7 @@
}, },
"gitlab": { "gitlab": {
"groups": "Группы", "groups": "Группы",
"issues": "Issues", "issues": "Задачи",
"merges": "Мердж-реквесты", "merges": "Мердж-реквесты",
"projects": "Проекты" "projects": "Проекты"
}, },
@@ -1125,26 +1150,26 @@
"nextRenewingSubscription": "Следующая оплата" "nextRenewingSubscription": "Следующая оплата"
}, },
"unraid": { "unraid": {
"STARTED": "Started", "STARTED": "Запущено",
"STOPPED": "Stopped", "STOPPED": "Остановлено",
"NEW_ARRAY": "Новый массив", "NEW_ARRAY": "Новый массив",
"RECON_DISK": "Reconstructing Disk", "RECON_DISK": "Восстановление Диска",
"DISABLE_DISK": "Disk Disabled", "DISABLE_DISK": "Диск отключен",
"SWAP_DSBL": "Swap Disable", "SWAP_DSBL": "Swap отключён",
"INVALID_EXPANSION": "Invalid Expansion", "INVALID_EXPANSION": "Неверное Расширение",
"PARITY_NOT_BIGGEST": "Parity Not Biggest", "PARITY_NOT_BIGGEST": "Parity Not Biggest",
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks", "TOO_MANY_MISSING_DISKS": "Слишком много отсутствующих дисков",
"NEW_DISK_TOO_SMALL": "New Disk Too Small", "NEW_DISK_TOO_SMALL": "Новый диск слишком мал",
"NO_DATA_DISKS": "No Data Disks", "NO_DATA_DISKS": "Нет дисков данных",
"notifications": "Уведомления", "notifications": "Уведомления",
"status": "Статус", "status": "Статус",
"cpu": "ЦП", "cpu": "ЦП",
"memoryUsed": "Использовано ОЗУ", "memoryUsed": "Использовано ОЗУ",
"memoryAvailable": "Memory Available", "memoryAvailable": "Доступная память",
"arrayUsed": "Array Used", "arrayUsed": "Array Used",
"arrayFree": "Array Free", "arrayFree": "Array Free",
"poolUsed": "{{pool}} Used", "poolUsed": "{{pool}} Использовано",
"poolFree": "{{pool}} Free" "poolFree": "{{pool}} Свободно"
}, },
"backrest": { "backrest": {
"num_plans": "Plans", "num_plans": "Plans",
@@ -1155,29 +1180,29 @@
"bytes_added_30": "Bytes Added" "bytes_added_30": "Bytes Added"
}, },
"yourspotify": { "yourspotify": {
"songs": "Songs", "songs": "Треков",
"time": "Время", "time": "Время",
"artists": "Artists" "artists": "Исполнителей"
}, },
"arcane": { "arcane": {
"containers": "Containers", "containers": "Контейнеров",
"images": "Images", "images": "Образов",
"image_updates": "Image Updates", "image_updates": "Обновлений",
"images_unused": "Unused", "images_unused": "Не используется",
"environment_required": "Environment ID Required" "environment_required": "Требуется ID окружения"
}, },
"dockhand": { "dockhand": {
"running": "Running", "running": "Запущено",
"stopped": "Stopped", "stopped": "Остановлено",
"cpu": "CPU", "cpu": "ЦП",
"memory": "Memory", "memory": "ОЗУ",
"images": "Images", "images": "Образов",
"volumes": "Volumes", "volumes": "Томов",
"events_today": "Events Today", "events_today": "Событий сегодня",
"pending_updates": "Pending Updates", "pending_updates": "Обновлений",
"stacks": "Stacks", "stacks": "Стеков",
"paused": "Paused", "paused": "На паузе",
"total": "Total", "total": "Всего",
"environment_not_found": "Среда не найдена" "environment_not_found": "Среда не найдена"
}, },
"sparkyfitness": { "sparkyfitness": {
+31 -6
View File
@@ -295,12 +295,12 @@
"available": "Dostupné" "available": "Dostupné"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "Čakajúce",
"approved": "Approved", "approved": "Schválené",
"available": "Available", "available": "Dostupné",
"completed": "Completed", "completed": "Dokončené",
"processing": "Processing", "processing": "Processing",
"issues": "Open Issues" "issues": "Neriešené problémy"
}, },
"netalertx": { "netalertx": {
"total": "Celkom", "total": "Celkom",
@@ -344,6 +344,16 @@
"address": "Adresa", "address": "Adresa",
"expires": "Vyprší", "expires": "Vyprší",
"never": "Nikdy", "never": "Nikdy",
"user": "Používateľ",
"hostname": "Názov hostiteľa",
"name": "Názov",
"client_version": "Verzia klienta",
"os": "Operačný systém",
"created": "Vytvorené",
"authorized": "Autorizované",
"is_external": "Je externý",
"update_available": "Dostupná aktualizácia",
"tags": "Štítky",
"last_seen": "Naposledy videné", "last_seen": "Naposledy videné",
"now": "Teraz", "now": "Teraz",
"years": "{{number}}r", "years": "{{number}}r",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Pred {{value}}" "ago": "Pred {{value}}",
"true": "Áno",
"false": "Nie"
}, },
"technitium": { "technitium": {
"totalQueries": "Požiadaviek", "totalQueries": "Požiadaviek",
@@ -924,6 +936,19 @@
"warnings": "Upozornenia", "warnings": "Upozornenia",
"criticals": "Kritické" "criticals": "Kritické"
}, },
"ntfy": {
"title": "Názov",
"priority": "Priorita",
"lastReceived": "Posledná prijatá",
"message": "Správa",
"tags": "Štítky",
"none": "Žiadna",
"min": "Min",
"low": "Nízka",
"default": "Predvolená",
"high": "Vysoká",
"urgent": "Naliehavé"
},
"plantit": { "plantit": {
"events": "Udalosti", "events": "Udalosti",
"plants": "Rastliny", "plants": "Rastliny",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Naslov", "address": "Naslov",
"expires": "Poteče", "expires": "Poteče",
"never": "Nikoli", "never": "Nikoli",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Viden", "last_seen": "Viden",
"now": "Sedaj", "now": "Sedaj",
"years": "{{number}}l", "years": "{{number}}l",
@@ -352,7 +362,9 @@
"hours": "{{number}}u", "hours": "{{number}}u",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} nazaj" "ago": "{{value}} nazaj",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Opozorila", "warnings": "Opozorila",
"criticals": "Kritično" "criticals": "Kritično"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Dogodki", "events": "Dogodki",
"plants": "Rastline", "plants": "Rastline",
+33 -8
View File
@@ -67,9 +67,9 @@
"empty_data": "Статус подсистема непознат" "empty_data": "Статус подсистема непознат"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Здрав",
"degraded": "Degraded", "degraded": "Деградирано",
"no_data": "No storage data available" "no_data": "Нема доступних података о складиштењу"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -344,6 +344,16 @@
"address": "Адреса", "address": "Адреса",
"expires": "Истиче", "expires": "Истиче",
"never": "Никада", "never": "Никада",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Последње виђен", "last_seen": "Последње виђен",
"now": "Сада", "now": "Сада",
"years": "{{number}}г", "years": "{{number}}г",
@@ -352,7 +362,9 @@
"hours": "{{number}}ч", "hours": "{{number}}ч",
"minutes": "{{number}}м", "minutes": "{{number}}м",
"seconds": "{{number}}с", "seconds": "{{number}}с",
"ago": "Пре {{value}}" "ago": "Пре {{value}}",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Упити", "totalQueries": "Упити",
@@ -924,6 +936,19 @@
"warnings": "Упозорења", "warnings": "Упозорења",
"criticals": "Критично" "criticals": "Критично"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Догађаји", "events": "Догађаји",
"plants": "Биљке", "plants": "Биљке",
@@ -1181,9 +1206,9 @@
"environment_not_found": "Окружење није пронађено" "environment_not_found": "Окружење није пронађено"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "Поједено",
"burned": "Burned", "burned": "Спаљено",
"remaining": "Remaining", "remaining": "Преостало",
"steps": "Steps" "steps": "Кораци"
} }
} }
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Adres", "address": "Adres",
"expires": "Geciken", "expires": "Geciken",
"never": "Asla", "never": "Asla",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Son Görülme", "last_seen": "Son Görülme",
"now": "Şimdi", "now": "Şimdi",
"years": "{{number}} Yıl", "years": "{{number}} Yıl",
@@ -352,7 +362,9 @@
"hours": "{{number}} Saat", "hours": "{{number}} Saat",
"minutes": "{{number}} Dakika", "minutes": "{{number}} Dakika",
"seconds": "{{number}} Saniye", "seconds": "{{number}} Saniye",
"ago": "{{value}} Önce" "ago": "{{value}} Önce",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Sorgular", "totalQueries": "Sorgular",
@@ -924,6 +936,19 @@
"warnings": "Uyarılar", "warnings": "Uyarılar",
"criticals": "Kritik" "criticals": "Kritik"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Etkinlikler", "events": "Etkinlikler",
"plants": "Bitkiler", "plants": "Bitkiler",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Адреса", "address": "Адреса",
"expires": "Дійсний до", "expires": "Дійсний до",
"never": "Ніколи", "never": "Ніколи",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Востаннє у мережі", "last_seen": "Востаннє у мережі",
"now": "Зараз", "now": "Зараз",
"years": "{{number}}р", "years": "{{number}}р",
@@ -352,7 +362,9 @@
"hours": "{{number}}год", "hours": "{{number}}год",
"minutes": "{{number}}хв", "minutes": "{{number}}хв",
"seconds": "{{number}}с", "seconds": "{{number}}с",
"ago": "{{value}} тому" "ago": "{{value}} тому",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Попередження", "warnings": "Попередження",
"criticals": "Критичні" "criticals": "Критичні"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Події", "events": "Події",
"plants": "Рослини", "plants": "Рослини",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "Address", "address": "Address",
"expires": "Expires", "expires": "Expires",
"never": "Never", "never": "Never",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "Last Seen", "last_seen": "Last Seen",
"now": "Now", "now": "Now",
"years": "{{number}}y", "years": "{{number}}y",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago" "ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "Warnings", "warnings": "Warnings",
"criticals": "Criticals" "criticals": "Criticals"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "Events", "events": "Events",
"plants": "Plants", "plants": "Plants",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "位址", "address": "位址",
"expires": "已失效", "expires": "已失效",
"never": "未曾", "never": "未曾",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "上次連線", "last_seen": "上次連線",
"now": "現在", "now": "現在",
"years": "{{number}} 年", "years": "{{number}} 年",
@@ -352,7 +362,9 @@
"hours": "{{number}} 小時", "hours": "{{number}} 小時",
"minutes": "{{number}} 分鐘", "minutes": "{{number}} 分鐘",
"seconds": "{{number}} 秒", "seconds": "{{number}} 秒",
"ago": "{{value}} 前" "ago": "{{value}} 前",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "警告", "warnings": "警告",
"criticals": "嚴重" "criticals": "嚴重"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "事件", "events": "事件",
"plants": "植物", "plants": "植物",
+32 -7
View File
@@ -228,7 +228,7 @@
"download": "下载", "download": "下载",
"upload": "", "upload": "",
"leech": "Leech", "leech": "Leech",
"seed": "Seed" "seed": "做种"
}, },
"qbittorrent": { "qbittorrent": {
"download": "下载速率", "download": "下载速率",
@@ -247,8 +247,8 @@
"deluge": { "deluge": {
"download": "下载", "download": "下载",
"upload": "上传", "upload": "上传",
"leech": "Leech", "leech": "下载中",
"seed": "Seed" "seed": "做种"
}, },
"develancacheui": { "develancacheui": {
"cachehitbytes": "缓存命中字节", "cachehitbytes": "缓存命中字节",
@@ -256,7 +256,7 @@
}, },
"downloadstation": { "downloadstation": {
"download": "Download", "download": "Download",
"upload": "Upload", "upload": "上传速率",
"leech": "", "leech": "",
"seed": "做种" "seed": "做种"
}, },
@@ -344,6 +344,16 @@
"address": "地址", "address": "地址",
"expires": "失效", "expires": "失效",
"never": "从不", "never": "从不",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "最后上线", "last_seen": "最后上线",
"now": "现在", "now": "现在",
"years": "{{number}}年", "years": "{{number}}年",
@@ -352,7 +362,9 @@
"hours": "{{number}}h", "hours": "{{number}}h",
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} 以前" "ago": "{{value}} 以前",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -458,7 +470,7 @@
"cpu": "CPU", "cpu": "CPU",
"load": "负载", "load": "负载",
"wait": "请稍候", "wait": "请稍候",
"temp": "转速", "temp": "温度",
"_temp": "Temp", "_temp": "Temp",
"warn": "Warn", "warn": "Warn",
"uptime": "运行时间", "uptime": "运行时间",
@@ -715,7 +727,7 @@
}, },
"komga": { "komga": {
"libraries": "书库", "libraries": "书库",
"series": "Series", "series": "系列",
"books": "Books" "books": "Books"
}, },
"diskstation": { "diskstation": {
@@ -924,6 +936,19 @@
"warnings": "警告", "warnings": "警告",
"criticals": "严重" "criticals": "严重"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "事件", "events": "事件",
"plants": "植物", "plants": "植物",
+26 -1
View File
@@ -344,6 +344,16 @@
"address": "位址", "address": "位址",
"expires": "已過期", "expires": "已過期",
"never": "未曾", "never": "未曾",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"last_seen": "上次連線", "last_seen": "上次連線",
"now": "現在", "now": "現在",
"years": "{{number}} 年", "years": "{{number}} 年",
@@ -352,7 +362,9 @@
"hours": "{{number}} 小時", "hours": "{{number}} 小時",
"minutes": "{{number}} 分鐘", "minutes": "{{number}} 分鐘",
"seconds": "{{number}} 秒", "seconds": "{{number}} 秒",
"ago": "{{value}} 前" "ago": "{{value}} 前",
"true": "Yes",
"false": "No"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Queries",
@@ -924,6 +936,19 @@
"warnings": "警告", "warnings": "警告",
"criticals": "嚴重" "criticals": "嚴重"
}, },
"ntfy": {
"title": "Title",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
"tags": "Tags",
"none": "None",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
},
"plantit": { "plantit": {
"events": "事件", "events": "事件",
"plants": "植物", "plants": "植物",
@@ -92,6 +92,23 @@ describe("pages/api/widgets/glances", () => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
}); });
it("falls back to version 3 when version is invalid", async () => {
getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances" });
httpProxy
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ total: 1 }))])
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ avg: 2 }))])
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ available: 3 }))]);
const req = { query: { index: "0", version: "3/../../secret-endpoint" } };
const res = createMockRes();
await handler(req, res);
expect(httpProxy).toHaveBeenCalledWith("http://glances/api/3/cpu", expect.any(Object));
expect(res.statusCode).toBe(200);
});
it("returns 400 when glances returns 401", async () => { it("returns 400 when glances returns 401", async () => {
getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances" }); getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances" });
httpProxy.mockResolvedValueOnce([401, null, Buffer.from("nope")]); httpProxy.mockResolvedValueOnce([401, null, Buffer.from("nope")]);
+7 -2
View File
@@ -11,12 +11,16 @@ import Resource from "../widget/resource";
import Resources from "../widget/resources"; import Resources from "../widget/resources";
import WidgetLabel from "../widget/widget_label"; import WidgetLabel from "../widget/widget_label";
const cpuSensorLabels = ["cpu_thermal", "Core", "Tctl", "Temperature"]; const defaultCpuSensorLabels = ["cpu_thermal", "Core", "Tctl", "Temperature"];
function convertToFahrenheit(t) { function convertToFahrenheit(t) {
return (t * 9) / 5 + 32; return (t * 9) / 5 + 32;
} }
function getCpuSensorLabels(options) {
return [...defaultCpuSensorLabels, options.cpuSensorLabel].filter(Boolean);
}
export default function Widget({ options }) { export default function Widget({ options }) {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
@@ -38,7 +42,6 @@ export default function Widget({ options }) {
<Resources options={options} additionalClassNames="information-widget-glances"> <Resources options={options} additionalClassNames="information-widget-glances">
{options.cpu !== false && <Resource icon={FiCpu} label={t("glances.wait")} percentage="0" />} {options.cpu !== false && <Resource icon={FiCpu} label={t("glances.wait")} percentage="0" />}
{options.mem !== false && <Resource icon={FaMemory} label={t("glances.wait")} percentage="0" />} {options.mem !== false && <Resource icon={FaMemory} label={t("glances.wait")} percentage="0" />}
{options.cputemp && <Resource icon={FaThermometerHalf} label={t("glances.wait")} percentage="0" />}
{options.disk && !Array.isArray(options.disk) && ( {options.disk && !Array.isArray(options.disk) && (
<Resource key={options.disk} icon={FiHardDrive} label={t("glances.wait")} percentage="0" /> <Resource key={options.disk} icon={FiHardDrive} label={t("glances.wait")} percentage="0" />
)} )}
@@ -47,6 +50,7 @@ export default function Widget({ options }) {
options.disk.map((disk) => ( options.disk.map((disk) => (
<Resource key={`disk_${disk}`} icon={FiHardDrive} label={t("glances.wait")} percentage="0" /> <Resource key={`disk_${disk}`} icon={FiHardDrive} label={t("glances.wait")} percentage="0" />
))} ))}
{options.cputemp && <Resource icon={FaThermometerHalf} label={t("glances.wait")} percentage="0" />}
{options.uptime && <Resource icon={FaRegClock} label={t("glances.wait")} percentage="0" />} {options.uptime && <Resource icon={FaRegClock} label={t("glances.wait")} percentage="0" />}
{options.label && <WidgetLabel label={options.label} />} {options.label && <WidgetLabel label={options.label} />}
</Resources> </Resources>
@@ -56,6 +60,7 @@ export default function Widget({ options }) {
const unit = options.units === "imperial" ? "fahrenheit" : "celsius"; const unit = options.units === "imperial" ? "fahrenheit" : "celsius";
let mainTemp = 0; let mainTemp = 0;
let maxTemp = 80; let maxTemp = 80;
const cpuSensorLabels = getCpuSensorLabels(options);
const cpuSensors = data.sensors?.filter( const cpuSensors = data.sensors?.filter(
(s) => cpuSensorLabels.some((label) => s.label.startsWith(label)) && s.type === "temperature_core", (s) => cpuSensorLabels.some((label) => s.label.startsWith(label)) && s.type === "temperature_core",
); );
@@ -120,6 +120,26 @@ describe("components/widgets/glances", () => {
expect(screen.getByRole("link")).toHaveClass("expanded"); expect(screen.getByRole("link")).toHaveClass("expanded");
}); });
it("renders temperature for custom cpu sensor labels", () => {
useSWR.mockReturnValue({
data: {
cpu: { total: 1 },
load: { min15: 1 },
mem: { available: 1, total: 1, percent: 1 },
fs: [],
sensors: [{ label: "Package id 0", type: "temperature_core", value: 42, warning: 90 }],
},
error: undefined,
});
renderWithProviders(<Glances options={{ cputemp: true, cpuSensorLabel: "Package id", url: "http://glances" }} />, {
settings: { target: "_self" },
});
expect(screen.getByText("42")).toBeInTheDocument();
expect(screen.getByText("glances.temp")).toBeInTheDocument();
});
it("renders disk resources for an array of mount points and filters missing mounts", () => { it("renders disk resources for an array of mount points and filters missing mounts", () => {
useSWR.mockReturnValue({ useSWR.mockReturnValue({
data: { data: {
+3
View File
@@ -19,7 +19,10 @@ const tailwindSafelist = [
"backdrop-blur-xs", "backdrop-blur-xs",
"backdrop-blur-sm", "backdrop-blur-sm",
"backdrop-blur-md", "backdrop-blur-md",
"backdrop-blur-lg",
"backdrop-blur-xl", "backdrop-blur-xl",
"backdrop-blur-2xl",
"backdrop-blur-3xl",
"backdrop-saturate-0", "backdrop-saturate-0",
"backdrop-saturate-50", "backdrop-saturate-50",
"backdrop-saturate-100", "backdrop-saturate-100",
+2 -1
View File
@@ -1,5 +1,6 @@
import { getPrivateWidgetOptions } from "utils/config/widget-helpers"; import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
import createLogger from "utils/logger"; import createLogger from "utils/logger";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http"; import { httpProxy } from "utils/proxy/http";
const logger = createLogger("glances"); const logger = createLogger("glances");
@@ -45,7 +46,7 @@ export default async function handler(req, res) {
const { index, cputemp: includeCpuTemp, uptime: includeUptime, disk: includeDisks, version } = req.query; const { index, cputemp: includeCpuTemp, uptime: includeUptime, disk: includeDisks, version } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("glances", index); const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
privateWidgetOptions.version = version ?? 3; privateWidgetOptions.version = parseVersionForUrl(version, 3);
try { try {
const cpuData = await retrieveFromGlancesAPI(privateWidgetOptions, "cpu"); const cpuData = await retrieveFromGlancesAPI(privateWidgetOptions, "cpu");
+4 -2
View File
@@ -10,6 +10,7 @@ import { getKubeConfig } from "utils/config/kubernetes";
import * as shvl from "utils/config/shvl"; import * as shvl from "utils/config/shvl";
import kubernetes from "utils/kubernetes/export"; import kubernetes from "utils/kubernetes/export";
import createLogger from "utils/logger"; import createLogger from "utils/logger";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
const logger = createLogger("service-helpers"); const logger = createLogger("service-helpers");
@@ -113,7 +114,7 @@ export async function servicesFromDocker() {
} }
let substitutedVal = substituteEnvironmentVars(containerLabels[label]); let substitutedVal = substituteEnvironmentVars(containerLabels[label]);
if (value === "widget.version" || /^widgets\[\d+\]\.version$/.test(value)) { if (value === "widget.version" || /^widgets\[\d+\]\.version$/.test(value)) {
substitutedVal = parseInt(substitutedVal, 10); substitutedVal = parseVersionForUrl(substitutedVal);
} }
shvl.set(constructedService, value, substitutedVal); shvl.set(constructedService, value, substitutedVal);
} }
@@ -590,7 +591,7 @@ export function cleanServiceGroups(groups) {
"vikunja", "vikunja",
].includes(type) ].includes(type)
) { ) {
if (version) widget.version = parseInt(version, 10); widget.version = parseVersionForUrl(version);
} }
if (type === "glances") { if (type === "glances") {
if (metric) widget.metric = metric; if (metric) widget.metric = metric;
@@ -664,6 +665,7 @@ export function cleanServiceGroups(groups) {
if (enableRecentEvents !== undefined) widget.enableRecentEvents = enableRecentEvents; if (enableRecentEvents !== undefined) widget.enableRecentEvents = enableRecentEvents;
} }
if (type === "technitium") { if (type === "technitium") {
if (node !== undefined) widget.node = node;
if (range !== undefined) widget.range = range; if (range !== undefined) widget.range = range;
} }
if (type === "lubelogger") { if (type === "lubelogger") {
+16
View File
@@ -12,6 +12,22 @@ export function formatApiCall(url, args) {
return url.replace(find, replace).replace(find, replace); return url.replace(find, replace).replace(find, replace);
} }
export function parseVersionForUrl(version, defaultValue = null) {
if (version === undefined || version === null || version === "") {
return defaultValue;
}
if (typeof version === "number") {
return Number.isInteger(version) && version >= 0 ? version : defaultValue;
}
if (typeof version === "string" && /^\d+$/.test(version)) {
return Number(version);
}
return defaultValue;
}
export function getURLSearchParams(widget, endpoint) { export function getURLSearchParams(widget, endpoint) {
const params = new URLSearchParams({ const params = new URLSearchParams({
group: widget.service_group, group: widget.service_group,
+15
View File
@@ -7,6 +7,7 @@ import {
getURLSearchParams, getURLSearchParams,
jsonArrayFilter, jsonArrayFilter,
jsonArrayTransform, jsonArrayTransform,
parseVersionForUrl,
sanitizeErrorURL, sanitizeErrorURL,
} from "./api-helpers"; } from "./api-helpers";
@@ -21,6 +22,20 @@ describe("utils/proxy/api-helpers", () => {
expect(formatApiCall("{a}-{a}-{missing}", { a: "x" })).toBe("x-x-"); expect(formatApiCall("{a}-{a}-{missing}", { a: "x" })).toBe("x-x-");
}); });
it("parseVersionForUrl accepts canonical non-negative integers", () => {
expect(parseVersionForUrl("3")).toBe(3);
expect(parseVersionForUrl(4)).toBe(4);
expect(parseVersionForUrl(undefined, 3)).toBe(3);
});
it("parseVersionForUrl rejects non-canonical values", () => {
expect(parseVersionForUrl("3/../../path", 3)).toBe(3);
expect(parseVersionForUrl("1e2", 3)).toBe(3);
expect(parseVersionForUrl("0x10", 3)).toBe(3);
expect(parseVersionForUrl(-1, 3)).toBe(3);
expect(parseVersionForUrl(Number.NaN, 3)).toBe(3);
});
it("getURLSearchParams includes group/service/index and optionally endpoint", () => { it("getURLSearchParams includes group/service/index and optionally endpoint", () => {
const widget = { service_group: "g", service_name: "s", index: "0" }; const widget = { service_group: "g", service_name: "s", index: "0" };
+4 -1
View File
@@ -7,7 +7,10 @@ export function setCookieHeader(url, params) {
const existingCookie = cookieJar.getCookieStringSync(url.toString()); const existingCookie = cookieJar.getCookieStringSync(url.toString());
if (existingCookie) { if (existingCookie) {
params.headers = params.headers ?? {}; params.headers = params.headers ?? {};
params.headers[params.cookieHeader ?? "Cookie"] = existingCookie; const cookieHeader = params.cookieHeader ?? "Cookie";
if (!params.headers[cookieHeader]) {
params.headers[cookieHeader] = existingCookie;
}
} }
} }
+12
View File
@@ -42,4 +42,16 @@ describe("utils/proxy/cookie-jar", () => {
expect(params.headers.Cookie).toContain("c=d"); expect(params.headers.Cookie).toContain("c=d");
}); });
it("does not overwrite an explicit cookie header", async () => {
const { addCookieToJar, setCookieHeader } = await import("./cookie-jar");
const url = new URL("http://example4.test/path");
addCookieToJar(url, { "set-cookie": ["sid=1; Path=/"] });
const params = { headers: { Cookie: "manual=1" } };
setCookieHeader(url, params);
expect(params.headers.Cookie).toBe("manual=1");
});
}); });
+14
View File
@@ -77,6 +77,12 @@ export default async function credentialedProxyHandler(req, res, map) {
} else { } else {
headers.Authorization = basicAuthHeader(widget); headers.Authorization = basicAuthHeader(widget);
} }
} else if (widget.type === "ntfy") {
if (widget.key) {
headers.Authorization = `Bearer ${widget.key}`;
} else if (widget.username && widget.password) {
headers.Authorization = basicAuthHeader(widget);
}
} else if (widget.type === "proxmox") { } else if (widget.type === "proxmox") {
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`; headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
} else if (widget.type === "proxmoxbackupserver") { } else if (widget.type === "proxmoxbackupserver") {
@@ -152,6 +158,14 @@ export default async function credentialedProxyHandler(req, res, map) {
if (status >= 400) { if (status >= 400) {
logger.error("HTTP Error %d calling %s", status, url.toString()); logger.error("HTTP Error %d calling %s", status, url.toString());
return res.status(status).json({
error: {
message: resultData?.error?.message ?? "HTTP Error",
url: sanitizeErrorURL(url),
...(resultData?.error?.rawError ? { rawError: resultData.error.rawError } : {}),
data: Buffer.isBuffer(resultData) ? Buffer.from(resultData).toString() : resultData,
},
});
} }
if (status === 200) { if (status === 200) {
@@ -34,6 +34,7 @@ vi.mock("widgets/widgets", () => ({
paperlessngx: { api: "{url}/api/{endpoint}" }, paperlessngx: { api: "{url}/api/{endpoint}" },
proxmox: { api: "{url}/api2/json/{endpoint}" }, proxmox: { api: "{url}/api2/json/{endpoint}" },
truenas: { api: "{url}/api/v2.0/{endpoint}" }, truenas: { api: "{url}/api/v2.0/{endpoint}" },
ntfy: { api: "{url}/{endpoint}" },
proxmoxbackupserver: { api: "{url}/api2/json/{endpoint}" }, proxmoxbackupserver: { api: "{url}/api2/json/{endpoint}" },
checkmk: { api: "{url}/{endpoint}" }, checkmk: { api: "{url}/{endpoint}" },
stocks: { api: "{url}/{endpoint}" }, stocks: { api: "{url}/{endpoint}" },
@@ -185,6 +186,51 @@ describe("utils/proxy/handlers/credentialed", () => {
expect(params.headers.Authorization).toBe("Bearer k"); expect(params.headers.Authorization).toBe("Bearer k");
}); });
it("uses Bearer auth for ntfy when key is provided", async () => {
getServiceWidget.mockResolvedValue({ type: "ntfy", url: "http://ntfy", topic: "alerts", key: "tk_test" });
httpProxy.mockResolvedValue([200, "application/json", { ok: true }]);
const req = { method: "GET", query: { group: "g", service: "s", endpoint: "alerts/json", index: 0 } };
const res = createMockRes();
await credentialedProxyHandler(req, res);
const [, params] = httpProxy.mock.calls.at(-1);
expect(params.headers.Authorization).toBe("Bearer tk_test");
});
it("uses Basic auth for ntfy when username/password are provided", async () => {
getServiceWidget.mockResolvedValue({
type: "ntfy",
url: "http://ntfy",
topic: "alerts",
username: "u",
password: "p",
});
httpProxy.mockResolvedValue([200, "application/json", { ok: true }]);
const req = { method: "GET", query: { group: "g", service: "s", endpoint: "alerts/json", index: 0 } };
const res = createMockRes();
await credentialedProxyHandler(req, res);
const [, params] = httpProxy.mock.calls.at(-1);
expect(params.headers.Authorization).toMatch(/^Basic /);
});
it("sends no auth header for ntfy when no credentials are configured", async () => {
getServiceWidget.mockResolvedValue({ type: "ntfy", url: "http://ntfy", topic: "alerts" });
httpProxy.mockResolvedValue([200, "application/json", { ok: true }]);
const req = { method: "GET", query: { group: "g", service: "s", endpoint: "alerts/json", index: 0 } };
const res = createMockRes();
await credentialedProxyHandler(req, res);
const [, params] = httpProxy.mock.calls.at(-1);
expect(params.headers.Authorization).toBeUndefined();
});
it.each([ it.each([
[{ type: "paperlessngx", url: "http://x", key: "k" }, { Authorization: "Token k" }], [{ type: "paperlessngx", url: "http://x", key: "k" }, { Authorization: "Token k" }],
[ [
@@ -204,6 +250,25 @@ describe("utils/proxy/handlers/credentialed", () => {
expect(params.headers).toEqual(expect.objectContaining(expected)); expect(params.headers).toEqual(expect.objectContaining(expected));
}); });
it("normalizes non-200 JSON responses into widget error payloads", async () => {
getServiceWidget.mockResolvedValue({ type: "paperlessngx", url: "http://x", key: "k" });
httpProxy.mockResolvedValue([401, "application/json", { detail: "Invalid token." }]);
const req = { method: "GET", query: { group: "g", service: "s", endpoint: "statistics", index: 0 } };
const res = createMockRes();
await credentialedProxyHandler(req, res);
expect(res.statusCode).toBe(401);
expect(res.body).toEqual({
error: {
message: "HTTP Error",
url: "http://x/api/statistics",
data: { detail: "Invalid token." },
},
});
});
it("uses basic auth for esphome when username/password are provided", async () => { it("uses basic auth for esphome when username/password are provided", async () => {
getServiceWidget.mockResolvedValue({ type: "esphome", url: "http://x", username: "u", password: "p" }); getServiceWidget.mockResolvedValue({ type: "esphome", url: "http://x", username: "u", password: "p" });
httpProxy.mockResolvedValue([200, "application/json", { ok: true }]); httpProxy.mockResolvedValue([200, "application/json", { ok: true }]);
+2 -2
View File
@@ -2,7 +2,7 @@ import cache from "memory-cache";
import getServiceWidget from "utils/config/service-helpers"; import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger"; import createLogger from "utils/logger";
import { asJson, formatApiCall } from "utils/proxy/api-helpers"; import { asJson, formatApiCall, parseVersionForUrl } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http"; import { httpProxy } from "utils/proxy/http";
import widgets from "widgets/widgets"; import widgets from "widgets/widgets";
@@ -56,7 +56,7 @@ async function getApiInfo(serviceWidget, apiName, serviceName) {
const json = asJson(data); const json = asJson(data);
if (json?.data?.[apiName]) { if (json?.data?.[apiName]) {
cgiPath = json.data[apiName].path; cgiPath = json.data[apiName].path;
maxVersion = json.data[apiName].maxVersion; maxVersion = parseVersionForUrl(json.data[apiName].maxVersion);
logger.debug( logger.debug(
`Detected ${serviceWidget.type}: apiName '${apiName}', cgiPath '${cgiPath}', and maxVersion ${maxVersion}`, `Detected ${serviceWidget.type}: apiName '${apiName}', cgiPath '${cgiPath}', and maxVersion ${maxVersion}`,
); );
+27 -7
View File
@@ -224,23 +224,43 @@ function homepageDNSLookupFn() {
}; };
} }
const homepageLookup = homepageDNSLookupFn();
const agentCache = new Map();
function getAgent(protocol, disableIpv6) {
const cacheKey = `${protocol}:${disableIpv6 ? "ipv4" : "auto"}`;
const cachedAgent = agentCache.get(cacheKey);
if (cachedAgent) {
return cachedAgent;
}
const agentOptions = {
keepAlive: true,
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
lookup: homepageLookup,
};
const agent =
protocol === "https:"
? new https.Agent({ ...agentOptions, rejectUnauthorized: false })
: new http.Agent(agentOptions);
agentCache.set(cacheKey, agent);
return agent;
}
export async function httpProxy(url, params = {}) { export async function httpProxy(url, params = {}) {
const constructedUrl = new URL(url); const constructedUrl = new URL(url);
const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true"; const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true";
const agentOptions = {
...(disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 }),
lookup: homepageDNSLookupFn(),
};
let request = null; let request = null;
if (constructedUrl.protocol === "https:") { if (constructedUrl.protocol === "https:") {
request = httpsRequest(constructedUrl, { request = httpsRequest(constructedUrl, {
agent: new https.Agent({ ...agentOptions, rejectUnauthorized: false }), agent: getAgent(constructedUrl.protocol, disableIpv6),
...params, ...params,
}); });
} else { } else {
request = httpRequest(constructedUrl, { request = httpRequest(constructedUrl, {
agent: new http.Agent(agentOptions), agent: getAgent(constructedUrl.protocol, disableIpv6),
...params, ...params,
}); });
} }
+16
View File
@@ -8,6 +8,7 @@ const { state, cache, logger, dns, net, cookieJar } = vi.hoisted(() => ({
body: Buffer.from(""), body: Buffer.from(""),
}, },
error: null, error: null,
lastAgent: null,
lastAgentOptions: null, lastAgentOptions: null,
lastRequestParams: null, lastRequestParams: null,
lastWrittenBody: null, lastWrittenBody: null,
@@ -59,6 +60,7 @@ vi.mock("follow-redirects", async () => {
state.lastWrittenBody = chunk; state.lastWrittenBody = chunk;
}); });
req.end = vi.fn(() => { req.end = vi.fn(() => {
state.lastAgent = params?.agent ?? null;
state.lastAgentOptions = params?.agent?.opts ?? null; state.lastAgentOptions = params?.agent?.opts ?? null;
if (state.error) { if (state.error) {
req.emit("error", state.error); req.emit("error", state.error);
@@ -104,6 +106,7 @@ describe("utils/proxy/http cachedRequest", () => {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
body: Buffer.from(""), body: Buffer.from(""),
}; };
state.lastAgent = null;
state.lastAgentOptions = null; state.lastAgentOptions = null;
state.lastRequestParams = null; state.lastRequestParams = null;
state.lastWrittenBody = null; state.lastWrittenBody = null;
@@ -307,6 +310,7 @@ describe("utils/proxy/http httpProxy", () => {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
body: Buffer.from("ok"), body: Buffer.from("ok"),
}; };
state.lastAgent = null;
state.lastAgentOptions = null; state.lastAgentOptions = null;
state.lastRequestParams = null; state.lastRequestParams = null;
state.lastWrittenBody = null; state.lastWrittenBody = null;
@@ -397,6 +401,7 @@ describe("utils/proxy/http httpProxy", () => {
await httpMod.httpProxy("http://example.com"); await httpMod.httpProxy("http://example.com");
expect(state.lastAgentOptions.keepAlive).toBe(true);
expect(state.lastAgentOptions.family).toBe(4); expect(state.lastAgentOptions.family).toBe(4);
expect(state.lastAgentOptions.autoSelectFamily).toBe(false); expect(state.lastAgentOptions.autoSelectFamily).toBe(false);
}); });
@@ -409,6 +414,17 @@ describe("utils/proxy/http httpProxy", () => {
expect(state.lastAgentOptions.rejectUnauthorized).toBe(false); expect(state.lastAgentOptions.rejectUnauthorized).toBe(false);
}); });
it("reuses the same keep-alive agent for repeated http requests", async () => {
const httpMod = await import("./http");
await httpMod.httpProxy("http://example.com/first");
const firstAgent = state.lastAgent;
await httpMod.httpProxy("http://example.com/second");
expect(state.lastAgentOptions.keepAlive).toBe(true);
expect(state.lastAgent).toBe(firstAgent);
});
it("returns a sanitized error response when the request fails", async () => { it("returns a sanitized error response when the request fails", async () => {
state.error = Object.assign(new Error("boom"), { code: "EHOSTUNREACH" }); state.error = Object.assign(new Error("boom"), { code: "EHOSTUNREACH" });
const httpMod = await import("./http"); const httpMod = await import("./http");
+12 -9
View File
@@ -8,6 +8,13 @@ export default function validateWidgetData(widget, endpoint, data) {
let dataParsed = data; let dataParsed = data;
let error; let error;
let mapping; let mapping;
const mappings = widgets[widget.type]?.mappings;
if (mappings) {
mapping = Object.values(mappings).find((m) => m.endpoint === endpoint);
}
if (mapping?.allowEmpty && Buffer.isBuffer(data) && data.length === 0) return true;
if (Buffer.isBuffer(data)) { if (Buffer.isBuffer(data)) {
try { try {
dataParsed = JSON.parse(data); dataParsed = JSON.parse(data);
@@ -23,15 +30,11 @@ export default function validateWidgetData(widget, endpoint, data) {
} }
if (dataParsed && Object.entries(dataParsed).length) { if (dataParsed && Object.entries(dataParsed).length) {
const mappings = widgets[widget.type]?.mappings; mapping?.validate?.forEach((key) => {
if (mappings) { if (dataParsed[key] === undefined) {
mapping = Object.values(mappings).find((m) => m.endpoint === endpoint); valid = false;
mapping?.validate?.forEach((key) => { }
if (dataParsed[key] === undefined) { });
valid = false;
}
});
}
} }
if (!valid) { if (!valid) {
+14 -1
View File
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
const { loggerError } = vi.hoisted(() => ({ const { loggerError } = vi.hoisted(() => ({
loggerError: vi.fn(), loggerError: vi.fn(),
@@ -18,6 +18,10 @@ vi.mock("widgets/widgets", () => ({
endpoint: "foo", endpoint: "foo",
validate: ["a", "b"], validate: ["a", "b"],
}, },
empty: {
endpoint: "empty",
allowEmpty: true,
},
}, },
}, },
}, },
@@ -26,6 +30,10 @@ vi.mock("widgets/widgets", () => ({
import validateWidgetData from "./validate-widget-data"; import validateWidgetData from "./validate-widget-data";
describe("utils/proxy/validate-widget-data", () => { describe("utils/proxy/validate-widget-data", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns false when buffer JSON cannot be parsed", () => { it("returns false when buffer JSON cannot be parsed", () => {
expect(validateWidgetData({ type: "test" }, "foo", Buffer.from("not json"))).toBe(false); expect(validateWidgetData({ type: "test" }, "foo", Buffer.from("not json"))).toBe(false);
expect(loggerError).toHaveBeenCalled(); expect(loggerError).toHaveBeenCalled();
@@ -41,4 +49,9 @@ describe("utils/proxy/validate-widget-data", () => {
expect(validateWidgetData({ type: "test" }, "foo", Buffer.from(JSON.stringify({ a: 1 })))).toBe(false); expect(validateWidgetData({ type: "test" }, "foo", Buffer.from(JSON.stringify({ a: 1 })))).toBe(false);
expect(loggerError).toHaveBeenCalled(); expect(loggerError).toHaveBeenCalled();
}); });
it("allows empty buffer responses for mappings that explicitly allow them", () => {
expect(validateWidgetData({ type: "test" }, "empty", Buffer.from(""))).toBe(true);
expect(loggerError).not.toHaveBeenCalled();
});
}); });
+3 -3
View File
@@ -39,8 +39,8 @@ export default function Event({ event, colorVariants, showDate = false, showTime
return event.url ? ( return event.url ? (
<a <a
className={classNames(className, "hover:bg-theme-300/50 dark:hover:bg-theme-800/20")} className={classNames(className, "hover:bg-theme-300/50 dark:hover:bg-theme-800/20")}
onMouseEnter={() => setHover(!hover)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(!hover)} onMouseLeave={() => setHover(false)}
key={key} key={key}
href={event.url} href={event.url}
target="_blank" target="_blank"
@@ -49,7 +49,7 @@ export default function Event({ event, colorVariants, showDate = false, showTime
{children} {children}
</a> </a>
) : ( ) : (
<div className={className} onMouseEnter={() => setHover(!hover)} onMouseLeave={() => setHover(!hover)} key={key}> <div className={className} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} key={key}>
{children} {children}
</div> </div>
); );
+50
View File
@@ -48,6 +48,56 @@ describe("widgets/calendar/event", () => {
expect(link.querySelector("svg")).toBeTruthy(); expect(link.querySelector("svg")).toBeTruthy();
}); });
it("keeps additional text visible after repeated mouse enter events", () => {
const date = DateTime.fromISO("2099-01-01T13:00:00.000Z").setZone("utc");
render(
<Event
event={{
title: "Primary",
additional: "More info",
date,
color: "gray",
url: "https://example.com",
}}
colorVariants={{ gray: "bg-gray-500" }}
/>,
);
const link = screen.getByRole("link", { name: /primary/i });
fireEvent.mouseEnter(link);
fireEvent.mouseEnter(link);
expect(screen.getByText("More info")).toBeInTheDocument();
expect(screen.queryByText("Primary")).toBeNull();
});
it("keeps title visible after repeated mouse leave events", () => {
const date = DateTime.fromISO("2099-01-01T13:00:00.000Z").setZone("utc");
render(
<Event
event={{
title: "Primary",
additional: "More info",
date,
color: "gray",
}}
colorVariants={{ gray: "bg-gray-500" }}
/>,
);
const event = screen.getByText("Primary").closest("div.flex");
fireEvent.mouseEnter(event);
expect(screen.getByText("More info")).toBeInTheDocument();
fireEvent.mouseLeave(event);
fireEvent.mouseLeave(event);
expect(screen.getByText("Primary")).toBeInTheDocument();
expect(screen.queryByText("More info")).toBeNull();
});
it("compareDateTimezone matches dates by day", () => { it("compareDateTimezone matches dates by day", () => {
const day = DateTime.fromISO("2099-01-01T00:00:00.000Z").setZone("utc"); const day = DateTime.fromISO("2099-01-01T00:00:00.000Z").setZone("utc");
expect(compareDateTimezone(day, { date: DateTime.fromISO("2099-01-01T23:59:00.000Z").setZone("utc") })).toBe(true); expect(compareDateTimezone(day, { date: DateTime.fromISO("2099-01-01T23:59:00.000Z").setZone("utc") })).toBe(true);
+1
View File
@@ -91,6 +91,7 @@ const components = {
nextcloud: dynamic(() => import("./nextcloud/component")), nextcloud: dynamic(() => import("./nextcloud/component")),
nextdns: dynamic(() => import("./nextdns/component")), nextdns: dynamic(() => import("./nextdns/component")),
npm: dynamic(() => import("./npm/component")), npm: dynamic(() => import("./npm/component")),
ntfy: dynamic(() => import("./ntfy/component")),
nzbget: dynamic(() => import("./nzbget/component")), nzbget: dynamic(() => import("./nzbget/component")),
octoprint: dynamic(() => import("./octoprint/component")), octoprint: dynamic(() => import("./octoprint/component")),
omada: dynamic(() => import("./omada/component")), omada: dynamic(() => import("./omada/component")),
+5 -3
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const statusMap = { const statusMap = {
@@ -19,11 +20,12 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const idKey = version === 3 ? "Id" : "id"; const idKey = apiVersion === 3 ? "Id" : "id";
const statusKey = version === 3 ? "Status" : "status"; const statusKey = apiVersion === 3 ? "Status" : "status";
const { data, error } = useWidgetAPI(service.widget, `${version}/containers`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/containers`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+4 -2
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const Chart = dynamic(() => import("../components/chart"), { ssr: false }); const Chart = dynamic(() => import("../components/chart"), { ssr: false });
@@ -16,14 +17,15 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/cpu`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/cpu`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
const { data: quicklookData, error: quicklookError } = useWidgetAPI(service.widget, `${version}/quicklook`); const { data: quicklookData, error: quicklookError } = useWidgetAPI(service.widget, `${apiVersion}/quicklook`);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
+3 -1
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false }); const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -16,6 +17,7 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, diskName] = widget.metric.split(":"); const [, diskName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState( const [dataPoints, setDataPoints] = useState(
@@ -23,7 +25,7 @@ export default function Component({ service }) {
); );
const [ratePoints, setRatePoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit)); const [ratePoints, setRatePoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/diskio`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/diskio`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+3 -1
View File
@@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const defaultInterval = 1000; const defaultInterval = 1000;
@@ -11,10 +12,11 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, fsName] = widget.metric.split("fs:"); const [, fsName] = widget.metric.split("fs:");
const diskUnits = widget.diskUnits === "bbytes" ? "common.bbytes" : "common.bytes"; const diskUnits = widget.diskUnits === "bbytes" ? "common.bbytes" : "common.bytes";
const { data, error } = useWidgetAPI(widget, `${version}/fs`, { const { data, error } = useWidgetAPI(widget, `${apiVersion}/fs`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+3 -1
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false }); const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -16,11 +17,12 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, gpuName] = widget.metric.split(":"); const [, gpuName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit)); const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(widget, `${version}/gpu`, { const { data, error } = useWidgetAPI(widget, `${apiVersion}/gpu`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+4 -2
View File
@@ -3,6 +3,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
function Swap({ quicklookData, className = "" }) { function Swap({ quicklookData, className = "" }) {
@@ -75,12 +76,13 @@ const defaultSystemInterval = 30000; // This data (OS, hostname, distribution) i
export default function Component({ service }) { export default function Component({ service }) {
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const { data: quicklookData, errorL: quicklookError } = useWidgetAPI(service.widget, `${version}/quicklook`, { const { data: quicklookData, errorL: quicklookError } = useWidgetAPI(service.widget, `${apiVersion}/quicklook`, {
refreshInterval, refreshInterval,
}); });
const { data: systemData, errorL: systemError } = useWidgetAPI(service.widget, `${version}/system`, { const { data: systemData, errorL: systemError } = useWidgetAPI(service.widget, `${apiVersion}/system`, {
refreshInterval: defaultSystemInterval, refreshInterval: defaultSystemInterval,
}); });
+3 -1
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false }); const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -17,10 +18,11 @@ export default function Component({ service }) {
const { widget } = service; const { widget } = service;
const { chart } = widget; const { chart } = widget;
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget; const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/mem`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/mem`, {
refreshInterval: Math.max(defaultInterval(chart), refreshInterval), refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
}); });
+5 -3
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false }); const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
@@ -17,15 +18,16 @@ export default function Component({ service }) {
const { widget } = service; const { widget } = service;
const { chart, metric } = widget; const { chart, metric } = widget;
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget; const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const rxKey = version === 3 ? "rx" : "bytes_recv"; const rxKey = apiVersion === 3 ? "rx" : "bytes_recv";
const txKey = version === 3 ? "tx" : "bytes_sent"; const txKey = apiVersion === 3 ? "tx" : "bytes_sent";
const [, interfaceName] = metric.split(":"); const [, interfaceName] = metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(widget, `${version}/network`, { const { data, error } = useWidgetAPI(widget, `${apiVersion}/network`, {
refreshInterval: Math.max(defaultInterval(chart), refreshInterval), refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
}); });
+4 -2
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "next-i18next";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const statusMap = { const statusMap = {
@@ -22,10 +23,11 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const memoryInfoKey = version === 3 ? 0 : "rss"; const memoryInfoKey = apiVersion === 3 ? 0 : "rss";
const { data, error } = useWidgetAPI(service.widget, `${version}/processlist`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/processlist`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+3 -1
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import Block from "../components/block"; import Block from "../components/block";
import Container from "../components/container"; import Container from "../components/container";
import { parseVersionForUrl } from "utils/proxy/api-helpers";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
const Chart = dynamic(() => import("../components/chart"), { ssr: false }); const Chart = dynamic(() => import("../components/chart"), { ssr: false });
@@ -16,11 +17,12 @@ export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { widget } = service; const { widget } = service;
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget; const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit, version = 3 } = widget;
const apiVersion = parseVersionForUrl(version, 3);
const [, sensorName] = widget.metric.split(":"); const [, sensorName] = widget.metric.split(":");
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit)); const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
const { data, error } = useWidgetAPI(service.widget, `${version}/sensors`, { const { data, error } = useWidgetAPI(service.widget, `${apiVersion}/sensors`, {
refreshInterval: Math.max(defaultInterval, refreshInterval), refreshInterval: Math.max(defaultInterval, refreshInterval),
}); });
+1 -1
View File
@@ -3,7 +3,7 @@ import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = { const widget = {
api: "{url}/api/{endpoint}", api: "{url}/api/{endpoint}",
proxyHandler: credentialedProxyHandler, proxyHandler: credentialedProxyHandler,
allowedEndpoints: /\d\/quicklook|diskio|cpu|fs|gpu|system|mem|network|processlist|sensors|containers/, allowedEndpoints: /^\d+\/(quicklook|diskio|cpu|fs|gpu|system|mem|network|processlist|sensors|containers)$/,
}; };
export default widget; export default widget;
+4
View File
@@ -8,6 +8,10 @@ describe("glances widget config", () => {
it("exports a valid widget config", () => { it("exports a valid widget config", () => {
expectWidgetConfigShape(widget); expectWidgetConfigShape(widget);
expect(widget.allowedEndpoints?.test("3/quicklook")).toBe(true); expect(widget.allowedEndpoints?.test("3/quicklook")).toBe(true);
expect(widget.allowedEndpoints?.test("12/cpu")).toBe(true);
expect(widget.allowedEndpoints?.test("unknown")).toBe(false); expect(widget.allowedEndpoints?.test("unknown")).toBe(false);
expect(widget.allowedEndpoints?.test("xxcpuyy")).toBe(false);
expect(widget.allowedEndpoints?.test("3/cpu/extra")).toBe(false);
expect(widget.allowedEndpoints?.test("membrane")).toBe(false);
}); });
}); });
+63
View File
@@ -0,0 +1,63 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next";
import useWidgetAPI from "utils/proxy/use-widget-api";
const priorityLabels = {
1: "min",
2: "low",
3: "default",
4: "high",
5: "urgent",
};
function Truncated({ text }) {
return (
<span className="grid w-full" title={text}>
<span className="truncate">{text}</span>
</span>
);
}
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: messagesData, error: messagesError } = useWidgetAPI(widget, "messages");
if (messagesError) {
return <Container service={service} error={messagesError} />;
}
if (!widget.fields || widget.fields.length === 0) {
widget.fields = ["title", "message", "priority", "lastReceived"];
} else if (widget.fields?.length > 4) {
widget.fields = widget.fields.slice(0, 4);
}
if (!messagesData) {
return (
<Container service={service}>
<Block label="ntfy.title" />
<Block label="ntfy.message" />
<Block label="ntfy.priority" />
<Block label="ntfy.lastReceived" />
<Block label="ntfy.tags" />
</Container>
);
}
return (
<Container service={service}>
<Block label="ntfy.title" value={<Truncated text={messagesData.title ?? t("ntfy.none")} />} />
<Block label="ntfy.message" value={<Truncated text={messagesData.message ?? t("ntfy.none")} />} />
<Block label="ntfy.priority" value={t(`ntfy.${priorityLabels[messagesData.priority] ?? "default"}`)} />
<Block
label="ntfy.lastReceived"
value={messagesData.time ? t("common.relativeDate", { value: messagesData.time * 1000 }) : t("ntfy.none")}
/>
<Block label="ntfy.tags" value={messagesData.tags?.length > 0 ? messagesData.tags.join(", ") : t("ntfy.none")} />
</Container>
);
}
+216
View File
@@ -0,0 +1,216 @@
// @vitest-environment jsdom
import { screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "test-utils/render-with-providers";
import { expectBlockValue } from "test-utils/widget-assertions";
const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
vi.mock("utils/proxy/use-widget-api", () => ({
default: useWidgetAPI,
}));
import Component from "./component";
describe("widgets/ntfy/component", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders placeholders while loading", () => {
useWidgetAPI.mockImplementation(() => ({ data: undefined, error: undefined }));
const { container } = renderWithProviders(<Component service={{ widget: { type: "ntfy" } }} />, {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("ntfy.title")).toBeInTheDocument();
expect(screen.getByText("ntfy.message")).toBeInTheDocument();
expect(screen.getByText("ntfy.priority")).toBeInTheDocument();
expect(screen.getByText("ntfy.lastReceived")).toBeInTheDocument();
});
it("renders message data with default fields", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: "Disk Alert",
message: "Disk usage at 90%",
priority: 4,
time: 1700000000,
tags: ["warning"],
},
error: undefined,
}));
const { container } = renderWithProviders(
<Component service={{ widget: { type: "ntfy", url: "https://ntfy.example.com", topic: "alerts" } }} />,
{ settings: { hideErrors: false } },
);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "ntfy.title", "Disk Alert");
expectBlockValue(container, "ntfy.message", "Disk usage at 90%");
expectBlockValue(container, "ntfy.priority", "ntfy.high");
});
it("shows placeholder for title when message has no title set", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: null,
message: "Simple notification",
priority: 3,
time: 1700000000,
tags: [],
},
error: undefined,
}));
const { container } = renderWithProviders(
<Component service={{ widget: { type: "ntfy", url: "https://ntfy.example.com", topic: "alerts" } }} />,
{ settings: { hideErrors: false } },
);
expectBlockValue(container, "ntfy.title", "ntfy.none");
expectBlockValue(container, "ntfy.message", "Simple notification");
expectBlockValue(container, "ntfy.priority", "ntfy.default");
});
it("renders no messages state", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: null,
message: null,
priority: 3,
time: null,
tags: [],
},
error: undefined,
}));
const { container } = renderWithProviders(
<Component service={{ widget: { type: "ntfy", url: "https://ntfy.example.com", topic: "alerts" } }} />,
{ settings: { hideErrors: false } },
);
expectBlockValue(container, "ntfy.title", "ntfy.none");
expectBlockValue(container, "ntfy.message", "ntfy.none");
expectBlockValue(container, "ntfy.lastReceived", "ntfy.none");
});
it("renders error when API fails", () => {
useWidgetAPI.mockImplementation(() => ({
data: undefined,
error: { message: "Request failed" },
}));
renderWithProviders(
<Component service={{ widget: { type: "ntfy", url: "https://ntfy.example.com", topic: "alerts" } }} />,
{ settings: { hideErrors: false } },
);
expect(screen.getByText("Request failed")).toBeInTheDocument();
});
it("renders optional tags field when included", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: "Alert",
message: "Test",
priority: 5,
time: 1700000000,
tags: ["warning", "skull"],
},
error: undefined,
}));
const service = {
widget: {
type: "ntfy",
url: "https://ntfy.example.com",
topic: "alerts",
fields: ["title", "priority", "lastReceived", "tags"],
},
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expectBlockValue(container, "ntfy.tags", "warning, skull");
expectBlockValue(container, "ntfy.priority", "ntfy.urgent");
});
it("caps visible blocks at 4 when more than 4 fields are configured", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: "Alert",
message: "Body",
priority: 3,
time: 1700000000,
tags: ["a"],
},
error: undefined,
}));
const service = {
widget: {
type: "ntfy",
url: "https://ntfy.example.com",
topic: "alerts",
fields: ["title", "message", "priority", "lastReceived", "tags"],
},
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
});
it("falls back to default priority label when priority is out of range", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: "Alert",
message: "Body",
priority: 99,
time: 1700000000,
tags: [],
},
error: undefined,
}));
const { container } = renderWithProviders(
<Component service={{ widget: { type: "ntfy", url: "https://ntfy.example.com", topic: "alerts" } }} />,
{ settings: { hideErrors: false } },
);
expectBlockValue(container, "ntfy.priority", "ntfy.default");
});
it("renders optional message field when included", () => {
useWidgetAPI.mockImplementation(() => ({
data: {
title: "Disk Alert",
message: "Disk usage at 90%",
priority: 4,
time: 1700000000,
tags: [],
},
error: undefined,
}));
const service = {
widget: {
type: "ntfy",
url: "https://ntfy.example.com",
topic: "alerts",
fields: ["title", "priority", "message"],
},
};
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
expectBlockValue(container, "ntfy.title", "Disk Alert");
expectBlockValue(container, "ntfy.message", "Disk usage at 90%");
});
});

Some files were not shown because too many files have changed in this diff Show More