Compare commits

...
73 Commits
Author SHA1 Message Date
Crowdin Bot eedf5315d6 New Crowdin translations by GitHub Action
Lint / Linting Checks (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-14 12:28:59 +00:00
Anirudh SharmaandCodex 6bf81a78f3 Fix: preserve dollar sequences in environment and file secrets (#7242)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Co-authored-by: Codex <codex@openai.com>
2026-10-13 06:44:40 -07:00
shamoon 4a38eea1b0 Fix api page tests creating routes
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-10 23:12:49 -07:00
shamoon 347d34c08c 2.5.0
Docs / Test Build Docs (push) Canceled after 0s
Docs / Build & Deploy Docs (push) Canceled after 0s
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-10 11:30:59 -07:00
github-actions[bot]andCrowdin Bot f63901f51b New Crowdin translations by GitHub Action (#7240)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-10-10 18:24:05 +00:00
shamoon 03ad0e5d30 Chorehancement: add fileflows widget v2 for 26.10 breaking changes (#7239) 2026-10-10 08:10:15 -07:00
shamoon 447c4f1467 Chore: remove custom Vitest JSX esbuild config (#7234)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-08 21:29:24 -07:00
dependabot[bot] f54bcf4ef3 Chore(deps): Bump source-map-js from 1.2.1 to 1.2.2 (#7229)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 18:01:38 +00:00
dependabot[bot] 739c2ae551 Chore(deps): Bump sharp from 0.35.4 to 0.35.5 (#7228)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 17:43:57 +00:00
dependabot[bot] 9c6e621951 Chore(deps): Bump http-cache-semantics from 4.2.0 to 4.3.0 (#7231)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 17:33:25 +00:00
dependabot[bot] 68db3aec7d Chore(deps): Bump js-yaml from 5.4.2 to 5.4.3 (#7230)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 17:25:22 +00:00
dependabot[bot] eb3f97a58f Chore(deps): Bump next from 16.3.6 to 16.3.8 (#7232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 17:17:15 +00:00
shamoon 97db55ab36 Fix: use x-total-count header for correct gitea totals (#7227) 2026-10-08 10:10:30 -07:00
dependabot[bot] fec0f5d8b2 Chore(deps): Bump release-drafter/release-drafter/autolabeler from 7.6.0 to 7.8.0 (#7226)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 16:49:47 +00:00
dependabot[bot] 1a4c4f0db5 Chore(deps): Bump release-drafter/release-drafter from 7.6.0 to 7.8.0 (#7225)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 09:41:50 -07:00
shamoon 904e895fec Chorehancement: limit log size + rotate (#7218)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-05 21:21:25 -07:00
shamoon 8ec79250e6 Chore: handle cloudflare breaking API change dropping connections (#7217) 2026-10-05 20:56:01 -07:00
github-actions[bot]andCrowdin Bot d41f56feb5 New Crowdin translations by GitHub Action (#7162)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-10-06 02:00:28 +00:00
shamoon 666869ca0c Update next-i18next.config.js
Docker CI / Docker Build & Push (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-10-01 20:13:43 -07:00
dependabot[bot] 6ba7e487d7 Chore(deps): Bump crowdin/github-action from 3.1.0 to 3.3.0 (#7204)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-02 03:10:16 +00:00
dependabot[bot] ed83113bf2 Chore(deps): Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 (#7203)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-02 03:03:04 +00:00
dependabot[bot] cdb8282dd7 Chore(deps): Bump @grpc/grpc-js from 1.14.4 to 1.14.5 (#7212)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-01 21:04:02 +00:00
dependabot[bot] b0f2561560 Chore(deps): Bump next from 16.3.3 to 16.3.6 (#7213)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-01 20:57:34 +00:00
dependabot[bot] d39b7da27a Chore(deps): Bump brace-expansion from 1.1.18 to 1.1.21 (#7211)
Signed-off-by: dependabot[bot] <support@github.com>
2026-10-01 13:49:45 -07:00
dependabot[bot] 82ccdd38df Chore(deps): Bump react-i18next from 17.0.12 to 17.0.15 (#7207)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 20:36:51 +00:00
dependabot[bot] 9c73c329c4 Chore(deps): Bump the react group with 2 updates (#7205)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 20:25:34 +00:00
dependabot[bot] dec0176d49 Chore(deps): Bump systeminformation from 5.33.1 to 5.33.13 (#7206)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 20:17:04 +00:00
dependabot[bot] 3dc3ccccc3 Chore(deps): Bump i18next from 26.3.6 to 26.4.2 (#7209)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 20:06:05 +00:00
dependabot[bot] 277d0e4405 Chore(deps-dev): Bump prettier from 3.9.6 to 3.9.9 (#7208)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 12:57:18 -07:00
shamoon 9ae584f0ec Feature: Bookorbit service widget (#7202)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-09-30 08:59:42 -07:00
dependabot[bot] 8d39113bbe Chore(deps): Bump undici from 8.10.0 to 8.11.2 (#7200)
Lint / Linting Checks (push) Canceled after 0s
Docker CI / Docker Build & Push (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 14:08:11 +00:00
dependabot[bot] 8f32d61920 Chore(deps): Bump ip-address from 10.5.0 to 10.7.2 (#7199)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 06:58:37 -07:00
d001910e86 Feature: qui widget (#7192)
Docker CI / Docker Build & Push (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Co-authored-by: yorah <yoram.harmelin@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-25 16:46:43 +00:00
shamoon 18a5c4ad73 Feature: feeds widget (#7170)
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Release Drafter / Auto Label PR (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
2026-09-24 21:47:15 -07:00
dependabot[bot] 83d7fe2556 Chore(deps): Bump crowdin/github-action from 3.0.2 to 3.1.0 (#7183)
Release Drafter / Auto Label PR (push) Canceled after 0s
Docker CI / Docker Build & Push (push) Canceled after 0s
Lint / Linting Checks (push) Canceled after 0s
Release Drafter / Update Release Draft (push) Canceled after 0s
Tests / vitest (1) (push) Canceled after 0s
Tests / vitest (2) (push) Canceled after 0s
Tests / vitest (3) (push) Canceled after 0s
Tests / vitest (4) (push) Canceled after 0s
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:17:23 +00:00
dependabot[bot] 9669a68995 Chore(deps): Bump codecov/codecov-action from 7.0.0 to 7.1.1 (#7184)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:11:27 +00:00
dependabot[bot] b1935ed8a0 Chore(deps): Bump docker/build-push-action from 7.3.0 to 7.4.0 (#7182)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 17:04:48 +00:00
dependabot[bot] c5a218c39a Chore(deps): Bump docker/setup-buildx-action from 4.3.0 to 4.4.1 (#7185)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 09:58:27 -07:00
dependabot[bot] 2e287402c4 Chore(deps): Bump docker/setup-qemu-action from 4.3.0 to 4.4.0 (#7181)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-24 09:51:54 -07:00
Andy Georgeandshamoon 40082f798d Documentation: Arcane widget documentation formatting (#7171)
Docker CI / Docker Build & Push (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
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
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-23 22:39:41 +00:00
shamoon db480ca991 Performance: reduce json data transformations (#7175) 2026-09-23 15:32:30 -07:00
shamoon a7a4378a40 Fix: ensure correct relative dates formatter uses calendar days (#7174)
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-09-23 11:41:55 -07:00
shamoon d1c1ee7a1e Feature: Plex mono font support, card + block styles (#7169)
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-09-22 21:58:11 -07:00
shamoon e0be80085e Feature: customapi info widget (#7168) 2026-09-22 21:03:51 -07:00
dependabot[bot] b8ed72a03f Chore(deps): Bump astral-sh/setup-uv from 10.0.1 to 10.1.0 (#7156)
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
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 21:29:47 +00:00
dependabot[bot] ebff6f4a7d Chore(deps): Bump crowdin/github-action from 3.0.1 to 3.0.2 (#7155)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 21:23:19 +00:00
dependabot[bot] e890b544dc Chore(deps): Bump pnpm/action-setup from 6.0.10 to 6.1.0 (#7154)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 14:16:45 -07:00
shamoon f7666df9d8 Move PBS since param to optional params
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-09-17 07:36:08 -07:00
shamoon 6b6926108e 2.4.0
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-09-17 00:14:55 -07:00
shamoon c391c91517 Merge branch 'dev' 2026-09-17 00:14:40 -07:00
github-actions[bot]andCrowdin Bot 941db5706c New Crowdin translations by GitHub Action (#7120)
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: Crowdin Bot <support+bot@crowdin.com>
2026-09-16 14:41:12 +00:00
dependabot[bot] 643ebe8c02 Chore(deps): Bump js-yaml from 5.4.1 to 5.4.2 (#7147)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 14:23:30 +00:00
dependabot[bot] 1021027b5a Chore(deps): Bump sharp from 0.35.3 to 0.35.4 (#7146)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 07:16:22 -07:00
shamoon 0e131aa0d4 Documentation: change npm credentials to 'view-only' user
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-09-16 07:15:09 -07:00
shamoon 3598869ef0 Fix: prevent mcp operations losing var placeholders (#7144) 2026-09-16 07:08:20 -07:00
shamoon c764d05a0c Fix: better handle PeaNUT missing data (#7139)
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-09-12 21:49:05 -07:00
Ben Belov fcd9a715d9 Enhancement: Add bearer authentication to whatsupdocker widget (#7134)
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-09-11 23:24:08 -07:00
dependabot[bot] eacc205249 Chore(deps): Bump crowdin/github-action from 3.0.0 to 3.0.1 (#7128)
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-09-10 16:57:51 +00:00
dependabot[bot] 4249a3fb95 Chore(deps): Bump actions/deploy-pages from 5.0.0 to 5.0.1 (#7127)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 16:50:37 +00:00
dependabot[bot] 0b98428ab5 Chore(deps): Bump docker/setup-qemu-action from 4.2.0 to 4.3.0 (#7129)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 09:44:14 -07:00
dependabot[bot] bbe52519bb Chore(deps): Bump js-yaml from 5.4.0 to 5.4.1 (#7119)
Docker CI / Docker Build & Push (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
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
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-09 15:12:08 -07:00
shamoon 541b2fe9f1 2.3.0
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-09-09 14:45:28 -07:00
574ba2c7cb New Crowdin translations by GitHub Action (#7090)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-09 14:43:53 -07:00
shamoon f78cab7557 Documentation: update jellyfin versioning 2026-09-09 14:43:02 -07:00
shamoon 2cd77eae1f Fixhancement: use ghostfolio baseCurrency for net worth (#7116)
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-09-08 09:25:58 -07:00
shamoon 6ab83b0b5f Tweak: handle QL selection without hover (#7105)
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-09-06 18:35:01 -07:00
shamoon 9aa5a79c41 Fix: dont fire search on empty field (#7101)
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
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (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-09-05 16:07:12 -07:00
shamoon 1c2cb76702 Enhancement: render more docker states (#7099)
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-09-05 10:24:59 -07:00
shamoon 7de1faa769 Performance: use bulk request for docker stats (#7098) 2026-09-05 10:08:20 -07:00
shamoon 1024a382ee Enhancement: allow OIDC auto-login (#7096) 2026-09-05 08:25:00 -07:00
DaddyBoardandshamoon 438788240b Performance: fetch all docker container statuses in one request (#7092)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-05 15:17:52 +00:00
dependabot[bot] df34927198 Chore(deps): Bump crowdin/github-action from 2.17.1 to 3.0.0 (#7091)
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-09-05 02:21:31 -07:00
shamoon ddc5adc91c Fix: handle literal null from crowdsec api alerts (#7088)
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-09-02 12:40:40 -07:00
192 changed files with 7030 additions and 2446 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: crowdin action
uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2
uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0
with:
upload_translations: false
download_translations: true
+4 -4
View File
@@ -62,7 +62,7 @@ jobs:
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
@@ -99,14 +99,14 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
- name: Setup Docker buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
+3 -3
View File
@@ -23,7 +23,7 @@ jobs:
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- run: sudo apt-get install pngquant
- name: Test Docs Build
run: uv run --frozen zensical build --clean
@@ -45,12 +45,12 @@ jobs:
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- run: sudo apt-get install pngquant
- name: Build Docs
run: uv run --frozen zensical build --clean
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
- uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
id: deployment
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
- name: Install pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
+3 -3
View File
@@ -26,14 +26,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7
uses: release-drafter/release-drafter@c345907820e0396f6f44bcfd9ac4763325f8feb0 # v7
with:
config-name: release-drafter.yml
version: ${{ github.event.inputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: github.event_name != 'workflow_dispatch' || github.event.inputs.version == ''
uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7
uses: release-drafter/release-drafter@c345907820e0396f6f44bcfd9ac4763325f8feb0 # v7
with:
config-name: release-drafter.yml
env:
@@ -47,7 +47,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter/autolabeler@eada3c96a64734dd381cfbda23511034e328ddb0
- uses: release-drafter/release-drafter/autolabeler@c345907820e0396f6f44bcfd9ac4763325f8feb0
with:
config-name: release-drafter.yml
env:
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 9
@@ -31,7 +31,7 @@ jobs:
# Run Vitest directly so `--shard` is parsed as an option
- run: pnpm -s exec vitest run --coverage --shard ${{ matrix.shard }}/4 --pool forks
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

+60
View File
@@ -430,6 +430,64 @@ headerStyle: clean
headerStyle: boxedWidgets
```
## Card Style
Service and bookmark cards use a drop shadow by default. The `hairline` style replaces it with a thin border that brightens on hover.
<img width="896" alt="default cards" src="../../assets/style-default.webp">
```yaml
# default, shadowed cards
```
---
<img width="896" alt="hairline cards" src="../../assets/card-style-hairline.webp">
```yaml
cardStyle: hairline
```
## Block Style
Widget blocks (the value / label pairs inside a service card) can use the `refined` style to make values heavier and easier to read.
<img width="896" alt="default blocks" src="../../assets/style-default.webp">
```yaml
# default, standard blocks
```
---
<img width="896" alt="refined blocks" src="../../assets/block-style-refined.webp">
```yaml
blockStyle: refined
```
## Font
Homepage uses Manrope by default. You can switch to IBM Plex Mono instead, either everywhere or for data only.
<img width="896" alt="IBM Plex Mono everywhere" src="../../assets/font-plex.webp">
```yaml
font: plex # the whole dashboard
```
---
<img width="896" alt="IBM Plex Mono for data only" src="../../assets/font-plex-data.webp">
```yaml
font: plexData # only values, stats, the clock and status tags
```
With `plexData`, labels and names stay in Manrope while numbers line up in columns.
Both fonts are bundled with Homepage, so no external requests are made.
## Base URL
In some proxy configurations, it may be necessary to set the documents base URL. You can do this by providing a `base` value, like so:
@@ -554,6 +612,8 @@ logpath: /logfile/path
By default, logs are sent both to `stdout` and to a file at the path specified. This can be changed by setting the `LOG_TARGETS` environment variable to one of `both` (default), `stdout` or `file`.
Log files rotate automatically when they reach 10 MiB. Up to five files are retained: the current `homepage.log` and four rotated files (`homepage1.log` through `homepage4.log`). The oldest rotated file is removed when a new one is created.
## Show Container Stats
You can show all docker or proxmox stats expanded in `settings.yaml`:
+5
View File
@@ -69,6 +69,11 @@ For OIDC login (overrides password login):
- `HOMEPAGE_OIDC_CLIENT_ID`
- `HOMEPAGE_OIDC_CLIENT_SECRET`
- Optional: `HOMEPAGE_OIDC_NAME` (display name), `HOMEPAGE_OIDC_SCOPE` (defaults to `openid email profile`)
- Optional: `HOMEPAGE_OIDC_AUTO_LOGIN=true` to skip the login page and send unauthenticated visitors straight to the provider
!!! tip
With auto-login enabled, visit `/auth/signin?autologin=0` to reach the login page without being redirected, e.g. in case something goes wrong!
!!! warning
+36
View File
@@ -0,0 +1,36 @@
---
title: Custom API
description: Custom API Information Widget Configuration
---
_(Find the Custom API service widget [here](../services/customapi.md))_
The Custom API information widget shows values from a custom self-hosted or third party API in your Homepage header, alongside an icon.
```yaml
- customapi:
url: http://custom.api.host.or.ip:port/path/to/exact/api/endpoint
icon: mdi-api # optional, see below
label: My API # optional, shown below the values
href: https://custom.api.host # optional, makes the widget a link
refreshInterval: 10000 # optional - in milliseconds, defaults to 10s
username: username # auth - optional
password: password # auth - optional
method: GET # optional, e.g. POST
headers: # optional, must be object
X-API-Token: token
requestBody: # optional, can be string or object
mappings:
- field: key
label: Field 1
- field: path.to.key2
label: Field 2
format: number # optional - defaults to text
- field: path.to.key3
label: Field 3
format: percent
```
The `icon` accepts the same values as [service icons](../../configs/services.md#icons), e.g. `mdi-api`, `si-github`, `sh-homepage`, a dashboard icon name like `homepage.png`, or a full URL. If no icon is set, a generic API icon is shown.
Mappings support the same `field`, `format`, `remap`, `scale`, `prefix` and `suffix` options as the [Custom API service widget](../services/customapi.md). The `additionalField` option and the `display` modes are not supported.
+1
View File
@@ -7,6 +7,7 @@ search:
You can also find a list of all available info widgets in the sidebar navigation.
- [Custom API](customapi.md)
- [Date & Time](datetime.md)
- [Glances](glances.md)
- [Greeting](greeting.md)
+2 -1
View File
@@ -6,6 +6,7 @@ description: Arcane Widget Configuration
Learn more about [Arcane](https://github.com/getarcaneapp/arcane).
**Allowed fields** (max 4): `running`, `stopped`, `total`, `images`, `images_used`, `images_unused`, `image_updates`.
**Default fields**: `running`, `stopped`, `total`, `image_updates`.
```yaml
@@ -17,4 +18,4 @@ widget:
fields: ["running", "stopped", "total", "image_updates"] # optional
```
Using an api key for the widget requires permissions for: `containers:list` `images:list and `image-updates:read`
Using an API key for the widget requires permissions for: `containers:list`, `images:list`, and `image-updates:read`.
+21
View File
@@ -0,0 +1,21 @@
---
title: BookOrbit
description: BookOrbit Widget Configuration
---
Learn more about [BookOrbit](https://github.com/bookorbit/bookorbit).
```yaml
widget:
type: bookorbit
url: http://bookorbit.host.or.ip:3000
username: username
password: password
fields: ["books", "authors", "reading", "finished"] # optional
```
## Fields
Allowed fields: `["books", "authors", "series", "storage", "reading", "finished"]` (maximum of 4).
Default fields: `["books", "authors", "reading", "finished"]`.
+2
View File
@@ -3,6 +3,8 @@ title: Custom API
description: Custom Widget Configuration from the API
---
_(Find the Custom API information widget [here](../info/customapi.md))_
This widget can show information from custom self-hosted or third party API.
Fields need to be defined in the `mappings` section YAML object to correlate with the value in the APIs JSON object. Final field definition needs to be the key with the desired value information.
+46
View File
@@ -0,0 +1,46 @@
---
title: Feed
description: RSS / Atom feed widget
---
This widget shows the latest items from an RSS 2.0 or Atom feed.
```yaml
widget:
type: feed
url: https://feeds.bbci.co.uk/news/rss.xml
maxItems: 5 # optional - defaults to 5
layout: list # optional - possible values list, grid - defaults to list
images: true # optional - set to false to hide images - defaults to true
```
## Layouts
`list` shows one row per item. Items with an image get a small thumbnail.
![Feed widget list layout](../../assets/widget_feed_list.webp)
![Feed widget list layout with thumbnails](../../assets/widget_feed_thumbnails.webp)
`grid` shows image tiles that re-flow to fit the width of the widget, e.g.:
```yaml
- NASA Image of the Day:
widget:
type: feed
url: https://www.nasa.gov/feeds/iotd-feed/
layout: grid
maxItems: 8
```
![Feed widget grid layout](../../assets/widget_feed_grid.webp)
## Images
Images are taken from the feed's `media:thumbnail` / `media:content` tags or image enclosures, falling back to the first image in the item's content. Some feeds do not include images at all.
Images are loaded by your browser directly from the feed's host, at whatever size the feed provides. Feeds that link full-size originals can be slow to load; set `images: false` for those.
## Notes
Feeds are fetched by homepage, not your browser, and cached for 10 minutes.
+10 -1
View File
@@ -5,10 +5,19 @@ description: Fileflows Widget Configuration
Learn more about [FileFlows](https://github.com/revenz/FileFlows).
Allowed fields: `["queue", "processing", "processed", "time"]`.
Allowed fields: `["queue", "processing", "processed", "time"]` (version 1) or `["queue", "processing", "processed", "failed"]` (version 2).
| FileFlows Version | Homepage Widget Version |
| ----------------- | ----------------------- |
| < 26.10 | 1 (default) |
| >= 26.10 | 2 |
Version 2 uses the ["Fenrus Dashboard Endpoint"](https://fileflows.com/docs/webconsole/config/settings/general#fenrus-dashboard-endpoint), which must be enabled. Use the token shown there as the `key`.
```yaml
widget:
type: fileflows
url: http://your.fileflows.host:port
version: 2 # optional, defaults to 1
key: yourfenrustoken # required for version 2
```
+3
View File
@@ -19,6 +19,7 @@ You can also find a list of all available service widgets in the sidebar navigat
- [Backrest](backrest.md)
- [Bazarr](bazarr.md)
- [Booklore](booklore.md)
- [BookOrbit](bookorbit.md)
- [Beszel](beszel.md)
- [Caddy](caddy.md)
- [Calendar](calendar.md)
@@ -39,6 +40,7 @@ You can also find a list of all available service widgets in the sidebar navigat
- [Emby](emby.md)
- [ESPHome](esphome.md)
- [EVCC](evcc.md)
- [Feed](feed.md)
- [Filebrowser](filebrowser.md)
- [Fileflows](fileflows.md)
- [Firefly III](firefly.md)
@@ -119,6 +121,7 @@ You can also find a list of all available service widgets in the sidebar navigat
- [PyLoad](pyload.md)
- [qBittorrent](qbittorrent.md)
- [QNAP](qnap.md)
- [qui](qui.md)
- [Radarr](radarr.md)
- [Readarr](readarr.md)
- [ROMM](romm.md)
+2 -2
View File
@@ -13,8 +13,8 @@ Allowed fields: `["movies", "series", "episodes", "songs", "albums"]`.
| Jellyfin Version | Homepage Widget Version |
| ---------------- | ----------------------- |
| < 10.12 | 1 (default) |
| >= 10.12 | 2 |
| < 12 (10.12) | 1 (default) |
| >= 12 (10.12) | 2 |
```yaml
widget:
+3 -3
View File
@@ -5,7 +5,7 @@ description: Nginx Proxy Manager Widget Configuration
Learn more about [Nginx Proxy Manager](https://nginxproxymanager.com/).
Login with the same admin username and password used to access the web UI.
Use a "view-only" user for the username and password.
Allowed fields: `["enabled", "disabled", "total"]`.
@@ -13,6 +13,6 @@ Allowed fields: `["enabled", "disabled", "total"]`.
widget:
type: npm
url: http://npm.host.or.ip
username: admin_username
password: admin_password
username: view_only_username
password: view_only_password
```
+30
View File
@@ -0,0 +1,30 @@
---
title: qui
description: qui Widget Configuration
---
Learn more about [qui](https://github.com/autobrr/qui).
Generate an API key in qui under **Settings → API Keys**.
The widget has two modes:
- **Aggregated (default):** omit `instance` to show combined stats across all qBittorrent instances monitored by qui.
- **Per-instance:** set `instance` to the qui ID (visible in the qui UI) to show just that one.
```yaml
widget:
type: qui
url: http://qui.host.or.ip:7476
key: quiapikeyquiapikeyquiapikey
instance: 1 # optional; omit for aggregated stats across all instances
fields: ["leech", "download", "seed", "upload"] # optional
```
## Fields
Allowed fields: `["leech", "download", "seed", "upload", "total", "errored", "ratio", "freeSpace"]` (maximum of 4).
Default fields: `["leech", "download", "seed", "upload"]`.
Note: `ratio` and `freeSpace` are only available with the 'single instance' version of the widget.
+1
View File
@@ -13,4 +13,5 @@ widget:
url: http://whatsupdocker:port
username: username # optional
password: password # optional
key: bearer-token # optional, takes precedence over username/password
```
+4
View File
@@ -44,6 +44,7 @@ nav:
- widgets/services/backrest.md
- widgets/services/bazarr.md
- widgets/services/booklore.md
- widgets/services/bookorbit.md
- widgets/services/beszel.md
- widgets/services/caddy.md
- widgets/services/calendar.md
@@ -65,6 +66,7 @@ nav:
- widgets/services/emby.md
- widgets/services/esphome.md
- widgets/services/evcc.md
- widgets/services/feed.md
- widgets/services/filebrowser.md
- widgets/services/fileflows.md
- widgets/services/firefly.md
@@ -148,6 +150,7 @@ nav:
- widgets/services/pyload.md
- widgets/services/qbittorrent.md
- widgets/services/qnap.md
- widgets/services/qui.md
- widgets/services/radarr.md
- widgets/services/readarr.md
- widgets/services/romm.md
@@ -194,6 +197,7 @@ nav:
- widgets/services/zabbix.md
- "Information Widgets":
- widgets/info/index.md
- widgets/info/customapi.md
- widgets/info/datetime.md
- widgets/info/glances.md
- widgets/info/greeting.md
+8
View File
@@ -106,6 +106,12 @@ function relativeDate(date, formatter) {
const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(delta));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
if (units[unitIndex] === "day") {
// compare calendar days, not elapsed 24h blocks
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
return formatter.format(Math.round((startOfDay(date) - startOfDay(new Date())) / 86400000), "day");
}
return formatter.format(Math.floor(delta / divisor), units[unitIndex]);
}
@@ -115,6 +121,8 @@ module.exports = {
locales: ["en"],
},
serializeConfig: false,
// see https://github.com/i18next/i18next/issues/2227
cacheInBuiltFormats: false,
use: [
{
init: (i18next) => {
+10 -9
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "2.2.0",
"version": "2.5.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -21,26 +21,27 @@
"dockerode": "^5.0.0",
"follow-redirects": "^1.16.0",
"gamedig": "^5.3.3",
"i18next": "^26.3.6",
"i18next": "^26.4.2",
"ical.js": "^2.2.1",
"js-yaml": "^5.4.0",
"js-yaml": "^5.4.3",
"json-rpc-2.0": "^1.7.1",
"luxon": "^3.7.2",
"memory-cache": "^0.2.0",
"minecraftstatuspinger": "^1.2.2",
"next": "^16.3.3",
"next": "^16.3.8",
"next-auth": "^4.24.15",
"next-i18next": "^16.0.8",
"ping": "^1.0.0",
"pretty-bytes": "^7.1.1",
"raw-body": "^4.0.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-i18next": "^17.0.12",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-i18next": "^17.0.15",
"react-icons": "^5.6.0",
"recharts": "^3.1.2",
"sax": "^1.6.1",
"swr": "^2.5.1",
"systeminformation": "^5.33.1",
"systeminformation": "^5.33.13",
"tough-cookie": "^6.0.2",
"urbackup-server-api": "^0.92.2",
"winston": "^3.19.0",
@@ -58,7 +59,7 @@
"eslint-config-prettier": "^10.1.8",
"jsdom": "^30.0.1",
"postcss": "^8.5.26",
"prettier": "^3.8.4",
"prettier": "^3.9.9",
"prettier-plugin-organize-imports": "^4.3.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.3.3",
+328 -325
View File
File diff suppressed because it is too large Load Diff
+33 -6
View File
@@ -21,7 +21,7 @@
"seconds": "s"
},
"auth": {
"signout": "Sign out"
"signout": "Teken uit"
},
"widget": {
"missing_type": "Ontbrekende legstuk-tipe: {{type}}",
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Soek…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Soekenjin: {{name}}",
"select_provider": "Kies soekenjin"
},
"resources": {
"cpu": "SVE",
@@ -99,7 +99,12 @@
"unhealthy": "Ongesond",
"not_found": "Nie Gevind Nie",
"exited": "Verlaat",
"partial": "Gedeeltelik"
"partial": "Gedeeltelik",
"created": "Geskep",
"paused": "Onderbreek",
"restarting": "Herlaai",
"removing": "Verwydering",
"dead": "Dood"
},
"ping": {
"error": "Fout",
@@ -251,6 +256,16 @@
"leech": "Seier",
"seed": "Vul"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "SVE Gebruik",
"memUsage": "MEM Gebruik",
@@ -474,7 +489,7 @@
"users": "Gebruikers",
"loginsLast24H": "Aantekenings (24h)",
"failedLoginsLast24H": "Mislukte Aantekenings (24h)",
"authorizationsLast24H": "Auths (24h)"
"authorizationsLast24H": "Magtigings (24h)"
},
"proxmox": {
"mem": "GEH",
@@ -775,7 +790,8 @@
"queue": "Tou",
"processing": "Verwerking",
"processed": "Verwerk",
"time": "Tyd"
"time": "Tyd",
"failed": "Failed"
},
"firefly": {
"networth": "Netto Waarde",
@@ -854,6 +870,14 @@
"reading": "Lees",
"finished": "Klaar"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Tou",
"downloadBytesRemaining": "Oorblywende",
@@ -1253,5 +1277,8 @@
"wanted": "Gesoek",
"queued": "In ry",
"leagues": "Ligas"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "غير سليم",
"not_found": "غير موجود",
"exited": "خرجت",
"partial": "جزئي"
"partial": "جزئي",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "خطأ",
@@ -251,6 +256,16 @@
"leech": "المُستهلكون",
"seed": "المُزوِّدين"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "استهلاك المعالج",
"memUsage": "استخدام الذاكرة العشوائية",
@@ -775,7 +790,8 @@
"queue": "الطابور",
"processing": "قيد المعالجة",
"processed": "مُعَالَج",
"time": "الوقت"
"time": "الوقت",
"failed": "Failed"
},
"firefly": {
"networth": "صافي القيمة",
@@ -854,6 +870,14 @@
"reading": "أقرأُه",
"finished": "منتهي"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "الطابور",
"downloadBytesRemaining": "متبقِ",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Нездравословен",
"not_found": "Не е открит",
"exited": "Напуснал",
"partial": "Частично"
"partial": "Частично",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Грешка",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU употреба",
"memUsage": "Упортеба памет",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Време"
"time": "Време",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+46 -19
View File
@@ -99,7 +99,12 @@
"unhealthy": "No saludable",
"not_found": "No trobat",
"exited": "Tancat",
"partial": "Parcial"
"partial": "Parcial",
"created": "Creat",
"paused": "Pausat",
"restarting": "Reiniciant",
"removing": "Eliminant",
"dead": "Mort"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Sangonera",
"seed": "Sembrat"
},
"qui": {
"leech": "Sangonera",
"download": "Baixada",
"seed": "Sembrat",
"upload": "Pujada",
"total": "Total",
"errored": "Errors",
"ratio": "Ràtio",
"freeSpace": "Lliure"
},
"qnap": {
"cpuUsage": "Ús de CPU",
"memUsage": "Ús de Memòria",
@@ -276,28 +291,28 @@
"seed": "Sembrat"
},
"sonarr": {
"wanted": "Volgut",
"wanted": "Pendents",
"queued": "En cua",
"series": "Sèries",
"queue": "Cua",
"unknown": "Desconegut"
},
"radarr": {
"wanted": "Volguts",
"wanted": "Pendents",
"missing": "Falten",
"queued": "Encuat",
"queued": "En cua",
"movies": "Pel·lícules",
"queue": "Cua",
"unknown": "Desconegut"
},
"lidarr": {
"wanted": "Volguts",
"queued": "Encuat",
"wanted": "Pendents",
"queued": "En cua",
"artists": "Artistes"
},
"readarr": {
"wanted": "Volguts",
"queued": "Encuat",
"wanted": "Pendents",
"queued": "En cua",
"books": "Llibres"
},
"bazarr": {
@@ -315,7 +330,7 @@
"available": "Disponible",
"completed": "Completat",
"processing": "Processant",
"issues": "Temes oberts"
"issues": "Incidències"
},
"netalertx": {
"total": "Total",
@@ -455,8 +470,8 @@
"domain_count": "Dominis"
},
"medusa": {
"wanted": "Volguts",
"queued": "Encuat",
"wanted": "Pendents",
"queued": "En cua",
"series": "Sèries"
},
"minecraft": {
@@ -762,8 +777,8 @@
},
"mylar": {
"series": "Sèries",
"issues": "Problemes",
"wanted": "Volguts"
"issues": "Números",
"wanted": "Pendents"
},
"photoprism": {
"albums": "Àlbums",
@@ -775,7 +790,8 @@
"queue": "Cua",
"processing": "Processant",
"processed": "Processat",
"time": "Temps"
"time": "Temps",
"failed": "Error"
},
"firefly": {
"networth": "Valor Net",
@@ -854,6 +870,14 @@
"reading": "Llegint",
"finished": "Acabats"
},
"bookorbit": {
"books": "Llibres",
"authors": "Autors",
"series": "Sèries",
"storage": "Emmagatzematge",
"reading": "Llegint",
"finished": "Acabats"
},
"jdownloader": {
"downloadCount": "Cua",
"downloadBytesRemaining": "Restant",
@@ -989,7 +1013,7 @@
},
"gitea": {
"notifications": "Notificacions",
"issues": "Problemes",
"issues": "Incidències",
"pulls": "Sol·licitud de Canvis",
"repositories": "Repositoris"
},
@@ -1119,7 +1143,7 @@
},
"gitlab": {
"groups": "Grups",
"issues": "Problemes",
"issues": "Incidències",
"merges": "Merge Requests",
"projects": "Projectes"
},
@@ -1156,7 +1180,7 @@
},
"checkmk": {
"serviceErrors": "Errors de servei",
"hostErrors": "Problemes de l'hoste"
"hostErrors": "Errors de l'hoste"
},
"komodo": {
"total": "Total",
@@ -1250,8 +1274,11 @@
"storage": "Emmagatzematge"
},
"sportarr": {
"wanted": "Volgut",
"queued": "Encuat",
"wanted": "Pendents",
"queued": "En cua",
"leagues": "Lligues"
},
"feed": {
"noItems": "Sense elements"
}
}
+31 -4
View File
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Hledat…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Vyhledávač: {{name}}",
"select_provider": "Vyberte vyhledávač"
},
"resources": {
"cpu": "Využití procesoru",
@@ -99,7 +99,12 @@
"unhealthy": "Nezdravý",
"not_found": "Nenalezen",
"exited": "Ukončen",
"partial": "Částečný"
"partial": "Částečný",
"created": "Vytvořeno",
"paused": "Pozastaveno",
"restarting": "Restartuji",
"removing": "Odebírání",
"dead": "Mrtvý"
},
"ping": {
"error": "Chyba",
@@ -251,6 +256,16 @@
"leech": "Leechované",
"seed": "Seedované"
},
"qui": {
"leech": "Leechované",
"download": "Stahování",
"seed": "Seedované",
"upload": "Nahravání",
"total": "Celkem",
"errored": "Chybné",
"ratio": "Poměr",
"freeSpace": "Volné"
},
"qnap": {
"cpuUsage": "Využití procesoru",
"memUsage": "Využití paměti",
@@ -775,7 +790,8 @@
"queue": "Fronta",
"processing": "Zpracovává se",
"processed": "Zpracováno",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Čisté jmění",
@@ -854,6 +870,14 @@
"reading": "Čtené",
"finished": "Přečtené"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Fronta",
"downloadBytesRemaining": "Zbývá",
@@ -1253,5 +1277,8 @@
"wanted": "Požadované",
"queued": "Ve frontě",
"leagues": "Ligy"
},
"feed": {
"noItems": "Žádné položky"
}
}
File diff suppressed because it is too large Load Diff
+80 -53
View File
@@ -21,7 +21,7 @@
"seconds": "s"
},
"auth": {
"signout": "Sign out"
"signout": "Abmelden"
},
"widget": {
"missing_type": "Fehlender Widget-Typ: {{type}}",
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Suche…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Suchmaschine: {{name}}",
"select_provider": "Suchmaschine auswählen"
},
"resources": {
"cpu": "CPU",
@@ -77,13 +77,13 @@
"no_data": "Keine Speicherdaten verfügbar"
},
"duplicati": {
"jobs": "Jobs",
"stored": "Stored",
"lastBackup": "Last Run",
"nextRun": "Next Run",
"running": "Running",
"warnings": "Warnings",
"errors": "Errors"
"jobs": "Aufgaben",
"stored": "Gespeichert",
"lastBackup": "Zuletzt ausgeführt",
"nextRun": "Nächste Ausführung",
"running": "Wird ausgeführt",
"warnings": "Warnungen",
"errors": "Fehler"
},
"docker": {
"rx": "RX",
@@ -99,7 +99,12 @@
"unhealthy": "Fehlerhaft",
"not_found": "Nicht gefunden",
"exited": "Beendet",
"partial": "Teilweise"
"partial": "Teilweise",
"created": "Erstellt am",
"paused": "Pausiert",
"restarting": "Wird neu gestartet",
"removing": "Wird entfernt",
"dead": "Tot"
},
"ping": {
"error": "Fehler",
@@ -135,7 +140,7 @@
"series": "Serien",
"episodes": "Episoden",
"songs": "Titel",
"albums": "Albums"
"albums": "Alben"
},
"esphome": {
"offline": "Offline",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU-Nutzung",
"memUsage": "Speichernutzung",
@@ -359,15 +374,15 @@
"address": "Adresse",
"expires": "Läuft ab",
"never": "Nie",
"user": "User",
"user": "Benutzer",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"client_version": "Client-Version",
"os": "Betriebssystem",
"created": "Erstellt am",
"authorized": "Autorisiert",
"is_external": "Ist extern",
"update_available": "Aktualisierung verfügbar",
"tags": "Tags",
"last_seen": "Zuletzt gesehen",
"now": "Jetzt",
@@ -378,8 +393,8 @@
"minutes": "{{number}}min",
"seconds": "{{number}}s",
"ago": "Vor {{value}}",
"true": "Yes",
"false": "No"
"true": "Ja",
"false": "Nein"
},
"technitium": {
"totalQueries": "Anfragen",
@@ -474,7 +489,7 @@
"users": "Benutzer",
"loginsLast24H": "Anmeldungen (24 h)",
"failedLoginsLast24H": "Fehlversuche (24 h)",
"authorizationsLast24H": "Auths (24h)"
"authorizationsLast24H": "Autorisierungen (24h)"
},
"proxmox": {
"mem": "RAM",
@@ -483,7 +498,7 @@
"vms": "VMs"
},
"pulse": {
"nodes": "Nodes",
"nodes": "Knoten",
"vms": "VMs",
"lxcs": "LXCs"
},
@@ -652,13 +667,13 @@
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"orgs": "Organisationen",
"sites": "Seiten",
"resources": "Ressourcen",
"targets": "Ziele",
"traffic": "Traffic",
"in": "In",
"out": "Out"
"in": "Eingehend",
"out": "Ausgehend"
},
"peanut": {
"battery_charge": "Akkuladung",
@@ -775,7 +790,8 @@
"queue": "Warteschlange",
"processing": "Wird verarbeitet",
"processed": "Wird verarbeitet",
"time": "Zeit"
"time": "Zeit",
"failed": "Failed"
},
"firefly": {
"networth": "Reinvermögen",
@@ -854,6 +870,14 @@
"reading": "Am Lesen",
"finished": "Fertig"
},
"bookorbit": {
"books": "Bücher",
"authors": "Autoren",
"series": "Serien",
"storage": "Storage",
"reading": "Reading",
"finished": "Beendet"
},
"jdownloader": {
"downloadCount": "Warteschlange",
"downloadBytesRemaining": "Verbleibend",
@@ -954,32 +978,32 @@
"storage": "Speicher"
},
"maintainerr": {
"itemsHandled": "Items Handled",
"episodesHandled": "Episodes Handled",
"moviesHandled": "Movies Handled",
"showsHandled": "Shows Handled",
"seasonsHandled": "Seasons Handled",
"reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity"
"itemsHandled": "Items bearbeitet",
"episodesHandled": "Episoden behandelt",
"moviesHandled": "Filme behandelt",
"showsHandled": "Serien behandelt",
"seasonsHandled": "Staffeln bearbeitet",
"reclaimable": "Zurückforderbar",
"movieReclaimable": "Film zurückforderbar",
"showReclaimable": "Serie zurückforderbar",
"totalCapacity": "Gesamte Kapazität"
},
"netdata": {
"warnings": "Warnungen",
"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"
"title": "Titel",
"priority": "Priorität",
"lastReceived": "Zuletzt erhalten",
"message": "Nachricht",
"tags": "Schlagwörter",
"none": "Keine",
"min": "Minimal",
"low": "Niedrig",
"default": "Standard",
"high": "Hoch",
"urgent": "Dringend"
},
"plantit": {
"events": "Ereignisse",
@@ -1093,8 +1117,8 @@
"beszel": {
"name": "Name",
"systems": "Systeme",
"up": "Up",
"down": "Down",
"up": "Online",
"down": "Offline",
"paused": "Pausiert",
"pending": "Wartend",
"status": "Status",
@@ -1244,14 +1268,17 @@
"steps": "Schritte"
},
"syncthing": {
"connected": "Connected",
"synced": "Synced",
"errors": "Errors",
"storage": "Storage"
"connected": "Verbunden",
"synced": "Synchronisiert",
"errors": "Fehler",
"storage": "Speicher"
},
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+306 -279
View File
@@ -14,14 +14,14 @@
"date": "{{value, date}}",
"relativeDate": "{{value, relativeDate}}",
"duration": "{{value, duration}}",
"months": "mo",
"days": "d",
"hours": "h",
"minutes": "m",
"seconds": "s"
"months": "Μ",
"days": "μ",
"hours": "ω",
"minutes": "λ",
"seconds": "δ"
},
"auth": {
"signout": "Sign out"
"signout": "Αποσύνδεση"
},
"widget": {
"missing_type": "Λείπει ο τύπος widget: {{type}}",
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Αναζήτηση…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Μηχανή αναζήτησης: {{name}}",
"select_provider": "Επιλέξτε τις μηχανές αναζήτησης"
},
"resources": {
"cpu": "Επεξεργαστής",
@@ -66,24 +66,24 @@
"wlan_devices": "WLAN Συσκευές",
"lan_users": "LAN Χρήστες",
"wlan_users": "WLAN Χρήστες",
"up": "UP",
"up": "ΕΠΑΝΩ",
"down": "ΚΑΤΩ",
"wait": "Please wait",
"wait": "Παρακαλώ περιμένετε",
"empty_data": "Άγνωστη κατάσταση υποσυστήματος"
},
"unifi_drive": {
"healthy": "Healthy",
"healthy": "Υγειές",
"degraded": "Degraded",
"no_data": "No storage data available"
"no_data": "Δεν υπάρχουν διαθέσιμα δεδομένα χώρου αποθήκευσης"
},
"duplicati": {
"jobs": "Jobs",
"stored": "Stored",
"lastBackup": "Last Run",
"nextRun": "Next Run",
"running": "Running",
"warnings": "Warnings",
"errors": "Errors"
"jobs": "Εργασίες",
"stored": "Αποθηκευμένα",
"lastBackup": "Τελευταία εκτέλεση",
"nextRun": "Επόμενη εκτέλεση",
"running": "Εκτελείται",
"warnings": "Προειδοποιήσεις",
"errors": "Σφάλματα"
},
"docker": {
"rx": "RX",
@@ -99,10 +99,15 @@
"unhealthy": "Άρρωστο",
"not_found": "Δεν βρέθηκε",
"exited": "Έκλεισε",
"partial": "Μερικό"
"partial": "Μερικό",
"created": "Δημιουργήθηκε",
"paused": "Σε παύση",
"restarting": "Γίνεται επανεκκίνηση",
"removing": "Αφαιρείται",
"dead": "Νεκρό"
},
"ping": {
"error": "Error",
"error": "Σφάλμα",
"ping": "Ping",
"down": "Ping down",
"up": "Ping up",
@@ -110,11 +115,11 @@
},
"siteMonitor": {
"http_status": "Κατάσταση HTTP",
"error": "Error",
"error": "Σφάλμα",
"response": "Απόκριση",
"down": "Down",
"up": "Up",
"not_available": "Not Available"
"down": "Κάτω",
"up": "Πάνω",
"not_available": "Μη Διαθέσιμο"
},
"emby": {
"playing": "Παίζει",
@@ -127,22 +132,22 @@
"songs": "Τραγούδια"
},
"jellyfin": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
"movies": "Movies",
"series": "Series",
"episodes": "Episodes",
"songs": "Songs",
"albums": "Albums"
"playing": "Γίνεται αναπαραγωγή",
"transcoding": "Επανακωδικοποίηση",
"bitrate": "Ρυθμός μετάδοσης",
"no_active": "Δεν Υπάρχουν Ενεργές Ροές",
"movies": "Ταινίες",
"series": "Σειρές",
"episodes": "Επεισόδια",
"songs": "Τραγούδια",
"albums": "Άλμπουμ"
},
"esphome": {
"offline": "Offline",
"offline_alt": "Offline",
"offline": "Εκτός σύνδεσης",
"offline_alt": "Χωρίς Σύνδεση",
"online": "Συνδεδεμένοι",
"total": "Total",
"unknown": "Unknown"
"total": "Σύνολο",
"unknown": "Άγνωστο"
},
"evcc": {
"pv_power": "Παραγωγή",
@@ -163,7 +168,7 @@
"unread": "Μη Διαβασμένο"
},
"fritzbox": {
"connectionStatus": "Status",
"connectionStatus": "Κατάσταση",
"connectionStatusUnconfigured": "Μη Ρυθμισμένο",
"connectionStatusConnecting": "Κατάσταση Σύνδεσης",
"connectionStatusAuthenticating": "Ταυτοποίηση",
@@ -171,16 +176,16 @@
"connectionStatusDisconnecting": "Αποσύνδεση",
"connectionStatusDisconnected": "Αποσυνδέθηκε",
"connectionStatusConnected": "Συνδέθηκε",
"uptime": "Uptime",
"uptime": "Συνεχής λειτουργία",
"maxDown": "Μέγιστο Download",
"maxUp": "Μέγιστο Upload",
"down": "Down",
"up": "Up",
"down": "Κάτω",
"up": "Πάνω",
"received": "Ληφθέντα",
"sent": "Απεσταλμένα",
"externalIPAddress": "Εξωτερική IP",
"externalIPv6Address": "Ext. IPv6",
"externalIPv6Prefix": "Ext. IPv6-Prefix"
"externalIPv6Address": "Εξωτ. IPv6",
"externalIPv6Prefix": "Εξωτ. IPv6-Prefix"
},
"caddy": {
"upstreams": "Upstreams",
@@ -198,10 +203,10 @@
"passes": "Περάσματα"
},
"tautulli": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
"playing": "Γίνεται αναπαραγωγή",
"transcoding": "Γίνεται επανακωδικοποίηση",
"bitrate": "Ρυθμός μετάδοσης",
"no_active": "Δεν Υπάρχουν Ενεργές Ροές",
"plex_connection_error": "Έλεγχος Σύνδεσης με Plex"
},
"tracearr": {
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Χρήση επεξεργαστή",
"memUsage": "Χρήση μνήμης",
@@ -288,16 +303,16 @@
"queued": "Queued",
"movies": "Movies",
"queue": "Queue",
"unknown": "Unknown"
"unknown": "Άγνωστο"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"wanted": "Επιθυμητό",
"queued": "Σε αναμονή",
"artists": "Καλλιτέχνες"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"wanted": "Επιθυμητό",
"queued": "Σε αναμονή",
"books": "Βιβλία"
},
"bazarr": {
@@ -310,16 +325,16 @@
"available": "Διαθέσιμο"
},
"seerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available",
"completed": "Completed",
"processing": "Processing",
"issues": "Open Issues"
"pending": "Σε εκκρεμότητα",
"approved": "Εγκρίθηκε",
"available": "Διαθέσιμα",
"completed": "Ολοκληρώθηκε",
"processing": "Σε επεξεργασία",
"issues": "Ανοιχτά θέματα"
},
"netalertx": {
"total": "Total",
"connected": "Connected",
"total": "Σύνολο",
"connected": "Σε σύνδεση",
"new_devices": "Νέες συσκευές",
"down_alerts": "Ειδοποιήσεις offline"
},
@@ -330,45 +345,45 @@
"gravity": "Gravity"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"queries": "Ερωτήματα",
"blocked": "Αποκλεισμένα",
"filtered": "Φιλτραρισμένα",
"latency": "Καθυστέρηση"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"upload": "Αποστολή",
"download": "Λήψη",
"ping": "Ping"
},
"portainer": {
"running": "Running",
"running": "Εκτελείται",
"stopped": "Σταματημένο",
"total": "Total"
"total": "Σύνολο"
},
"suwayomi": {
"download": "Downloaded",
"nondownload": "Non-Downloaded",
"read": "Read",
"unread": "Unread",
"downloadedread": "Downloaded & Read",
"downloadedunread": "Downloaded & Unread",
"nondownloadedread": "Non-Downloaded & Read",
"nondownloadedunread": "Non-Downloaded & Unread"
"download": "Ελήφθησαν",
"nondownload": "Δεν Ελήφθησαν",
"read": "Διαβάστηκε",
"unread": "Μη αναγνωσμένα",
"downloadedread": "Ληφθέν & Αναγνωσμένο",
"downloadedunread": "Ληφθέν & Μη Αναγνωσμένο",
"nondownloadedread": "Μη Ληφθέν & Αναγνωσμένο",
"nondownloadedunread": "Μη Ληφθέν & Μη Αναγνωσμένο"
},
"tailscale": {
"address": "Διεύθυνση",
"expires": "Λήγει",
"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",
"user": "Χρήστης",
"hostname": "Όνομα υπολογιστή",
"name": "Όνομα",
"client_version": "Έκδοση Πελάτη",
"os": "Λειτουργικό",
"created": "Δημιουργήθηκε",
"authorized": "Εξουσιοδοτημένο",
"is_external": "Είναι Εξωτερική",
"update_available": "Διαθέσιμη ενημέρωση",
"tags": "Ετικέτες",
"last_seen": "Τελευταία Σύνδεση",
"now": "Τώρα",
"years": "{{number}}χρόνια",
@@ -378,20 +393,20 @@
"minutes": "{{number}}λεπτά",
"seconds": "{{number}}δευτερόλεπτα",
"ago": "{{value}} πρίν",
"true": "Yes",
"false": "No"
"true": "Ναι",
"false": "Όχι"
},
"technitium": {
"totalQueries": "Queries",
"totalNoError": "Success",
"totalServerFailure": "Failures",
"totalNxDomain": "NX Domains",
"totalRefused": "Refused",
"totalAuthoritative": "Authoritative",
"totalQueries": "Ερωτήματα",
"totalNoError": "Επιτυχία",
"totalServerFailure": "Σφάλματα",
"totalNxDomain": "Τομείς NX",
"totalRefused": "Απορρίφθηκαν",
"totalAuthoritative": "Εξουσιοδοτημένο",
"totalRecursive": "Recursive",
"totalCached": "Cached",
"totalBlocked": "Blocked",
"totalDropped": "Dropped",
"totalBlocked": "Αποκλεισμένα",
"totalDropped": "Απορρίφθηκαν",
"totalClients": "Πελάτες"
},
"tdarr": {
@@ -524,75 +539,75 @@
"2-day": "Αραιές Νεφώσεις",
"2-night": "Partly Cloudy",
"3-day": "Συννεφιές",
"3-night": "Cloudy",
"3-night": "Συννεφιά",
"45-day": "Ομίχλη",
"45-night": "Foggy",
"48-day": "Foggy",
"48-night": "Foggy",
"45-night": "Ομίχλη",
"48-day": "Ομίχλη",
"48-night": "Ομίχλη",
"51-day": "Ψιλόβροχο",
"51-night": "Light Drizzle",
"51-night": "Ελαφρύ Ψιλόβροχο",
"53-day": "Ψιλόβροχο",
"53-night": "Drizzle",
"55-day": "Heavy Drizzle",
"55-night": "Heavy Drizzle",
"56-day": "Light Freezing Drizzle",
"56-night": "Light Freezing Drizzle",
"53-night": "Ψιλόβροχο",
"55-day": "Βαρύ Ψιλόβροχο",
"55-night": "Βαρύ Ψιλόβροχο",
"56-day": "Ελαφρύ Παγωμένο Ψιλόβροχο",
"56-night": "Ελαφρύ Παγωμένο Ψιλόβροχο",
"57-day": "Παγωμένο ψιχάλισμα",
"57-night": "Freezing Drizzle",
"57-night": "Παγωμένο ψιλόβροχο",
"61-day": "Ψιλόβροχο",
"61-night": "Light Rain",
"61-night": "Ελαφριά Βροχή",
"63-day": "Βροχή",
"63-night": "Rain",
"63-night": "Βροχή",
"65-day": "Δυνατή βροχή",
"65-night": "Heavy Rain",
"65-night": "Δυνατή βροχή",
"66-day": "Παγωμένη βροχή",
"66-night": "Freezing Rain",
"67-day": "Freezing Rain",
"67-night": "Freezing Rain",
"66-night": "Παγωμένη βροχή",
"67-day": "Παγωμένη βροχή",
"67-night": "Παγωμένη βροχή",
"71-day": "Ελαφριά Χιονόπτωση",
"71-night": "Light Snow",
"71-night": "Ελαφρά Χιονόπτωση",
"73-day": "Χιόνι",
"73-night": "Snow",
"73-night": "Χιόνι",
"75-day": "Ισχυρή χιονόπτωση",
"75-night": "Heavy Snow",
"75-night": "Ισχυρή χιονόπτωση",
"77-day": "Κόκκοι Χιονιού",
"77-night": "Snow Grains",
"77-night": "Χιονάκι",
"80-day": "Ασθενείς βροχές",
"80-night": "Light Showers",
"80-night": "Ασθενείς βροχές",
"81-day": "Βροχοπτώσεις",
"81-night": "Showers",
"81-night": "Βροχοπτώσεις",
"82-day": "Ισχυρές βροχοπτώσεις",
"82-night": "Heavy Showers",
"82-night": "Ισχυρές βροχοπτώσεις",
"85-day": "Χιονοπτώσεις",
"85-night": "Snow Showers",
"86-day": "Snow Showers",
"86-night": "Snow Showers",
"85-night": "Χιονοπτώσεις",
"86-day": "Χιονοπτώσεις",
"86-night": "Χιονοπτώσεις",
"95-day": "Καταιγίδα",
"95-night": "Thunderstorm",
"95-night": "Καταιγίδα",
"96-day": "Καταιγίδα Με Χαλάζι",
"96-night": "Thunderstorm With Hail",
"99-day": "Thunderstorm With Hail",
"99-night": "Thunderstorm With Hail"
"96-night": "Καταιγίδα Με Χαλάζι",
"99-day": "Καταιγίδα Με Χαλάζι",
"99-night": "Καταιγίδα Με Χαλάζι"
},
"homebridge": {
"available_update": "Σύστημα",
"updates": "Ενημερώσεις",
"update_available": "Διαθέσιμη ενημέρωση",
"up_to_date": "Ενημερωμένο",
"child_bridges": "Child Bridges",
"child_bridges": "Θυγατρικές Γέφυρες",
"child_bridges_status": "{{ok}}/{{total}}",
"up": "Up",
"pending": "Pending",
"down": "Down",
"ok": "Ok"
"up": "Πάνω",
"pending": "Σε εκκρεμότητα",
"down": "Κάτω",
"ok": "Οκ"
},
"healthchecks": {
"new": "New",
"up": "Up",
"grace": "In Grace Period",
"down": "Down",
"paused": "Paused",
"status": "Status",
"new": "Νέο",
"up": "Πάνω",
"grace": "Σε Περίοδο Χάριτος",
"down": "Κάτω",
"paused": "Σε παύση",
"status": "Κατάσταση",
"last_ping": "Τελευταίο Ping",
"never": "Δεν υπάρχουν ping ακόμα"
},
@@ -602,26 +617,26 @@
"containers_failed": "Απέτυχε"
},
"autobrr": {
"approvedPushes": "Approved",
"approvedPushes": "Εγκρίθηκε",
"rejectedPushes": "Απορρίφθηκε",
"filters": "Φίλτρα",
"indexers": "Indexers"
"indexers": "Ευρετήρια"
},
"tubearchivist": {
"downloads": "Queue",
"downloads": "Αναμονή",
"videos": "Βίντεο",
"channels": "Κανάλια",
"playlists": "Λίστες αναπαραγωγής"
},
"truenas": {
"load": "Φόρτος Συστήματος",
"uptime": "Uptime",
"alerts": "Alerts"
"uptime": "Συνεχής λειτουργία",
"alerts": "Ειδοποιήσεις"
},
"pyload": {
"speed": "Ταχύτητα",
"active": "Active",
"queue": "Queue",
"active": "Ενεργό",
"queue": "Αναμονή",
"total": "Total"
},
"gluetun": {
@@ -730,56 +745,57 @@
"immich": {
"users": "Users",
"photos": "Φωτογραφίες",
"videos": "Videos",
"videos": "Βίντεο",
"storage": "Αποθηκευτικός χώρος"
},
"uptimekuma": {
"up": "Online τοποθεσίες",
"down": "Offline τοποθεσίες",
"uptime": "Uptime",
"uptime": "Συνεχής λειτουργία",
"incident": "Περιστατικό",
"m": "m"
"m": "λ"
},
"atsumeru": {
"series": "Series",
"archives": "Archives",
"chapters": "Chapters",
"series": "Σειρές",
"archives": "Αρχεία",
"chapters": "Κεφάλαια",
"categories": "Κατηγορίες"
},
"komga": {
"libraries": "Libraries",
"series": "Series",
"books": "Books"
"libraries": "Βιβλιοθήκες",
"series": "Σειρές",
"books": "Βιβλία"
},
"diskstation": {
"days": "Days",
"uptime": "Uptime",
"volumeAvailable": "Available"
"days": "Ημέρες",
"uptime": "Συνεχής λειτουργία",
"volumeAvailable": "Διαθέσιμα"
},
"dispatcharr": {
"channels": "Channels",
"streams": "Streams"
"channels": "Κανάλια",
"streams": "Ροές"
},
"mylar": {
"series": "Series",
"issues": "Issues",
"wanted": "Wanted"
"series": "Σειρές",
"issues": "Προβλήματα",
"wanted": "Επιθυμητά"
},
"photoprism": {
"albums": "Albums",
"photos": "Photos",
"videos": "Videos",
"albums": "Άλμπουμ",
"photos": "Φωτογραφίες",
"videos": "Βίντεο",
"people": "Άνθρωποι"
},
"fileflows": {
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Ώρα"
"queue": "Αναμονή",
"processing": "Σε επεξεργασία",
"processed": "Επεξεργασμένα",
"time": "Ώρα",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
"budget": "Budget"
"networth": "Καθαρή αξία",
"budget": "Προϋπολογισμός"
},
"grafana": {
"dashboards": "Πίνακας Ελέγχου",
@@ -796,15 +812,15 @@
"numshares": "Κοινόχρηστα στοιχεία"
},
"kopia": {
"status": "Status",
"status": "Κατάσταση",
"size": "Μέγεθος",
"lastrun": "Τελευταία εκτέλεση",
"nextrun": "Επόμενη εκτέλεση",
"failed": "Failed"
"failed": "Απέτυχε"
},
"unmanic": {
"active_workers": "Ενεργοί χρήστες",
"total_workers": "Total Workers",
"total_workers": "Σύνολο Εργατών",
"records_total": "Μήκος Ουράς"
},
"pterodactyl": {
@@ -817,21 +833,21 @@
"targets_total": "Συνολικοί Στόχοι"
},
"gatus": {
"up": "Sites Up",
"down": "Sites Down",
"uptime": "Uptime"
"up": "Ιστοσελίδες Πάνω",
"down": "Ιστοσελίδες Κάτω",
"uptime": "Συνεχής λειτουργία"
},
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_today": "Σήμερα",
"gross_percent_1y": "Ένας χρόνος",
"gross_percent_max": "Διαχρονικά",
"net_worth": "Net Worth"
"net_worth": "Καθαρή αξία"
},
"audiobookshelf": {
"podcasts": "Podcasts",
"books": "Books",
"books": "Βιβλία",
"podcastsDuration": "Διάρκεια",
"booksDuration": "Duration"
"booksDuration": "Διάρκεια"
},
"homeassistant": {
"people_home": "Σύνολο ανθρώπων στο σπίτι",
@@ -840,33 +856,41 @@
},
"whatsupdocker": {
"monitoring": "Παρακολούθηση",
"updates": "Updates"
"updates": "Ενημερώσεις"
},
"calibreweb": {
"books": "Books",
"books": "Βιβλία",
"authors": "Συντάκτες",
"categories": "Categories",
"series": "Series"
"categories": "Κατηγορίες",
"series": "Σειρές"
},
"booklore": {
"libraries": "Libraries",
"libraries": "Βιβλιοθήκες",
"books": "Βιβλία",
"reading": "Ανάγνωση",
"finished": "Ολοκληρώθηκε"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size",
"downloadSpeed": "Speed"
"downloadCount": "Αναμονή",
"downloadBytesRemaining": "Απομένουν",
"downloadTotalBytes": "Μέγεθος",
"downloadSpeed": "Ταχύτητα"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
"seriesCount": "Σειρές",
"totalFiles": "Αρχεία"
},
"azuredevops": {
"result": "Αποτέλεσμα",
"status": "Status",
"status": "Κατάσταση",
"buildId": "Build ID",
"succeeded": "Πέτυχε",
"notStarted": "Δεν ξεκίνησε",
@@ -951,7 +975,7 @@
"domains": "Domains",
"mailboxes": "Mailboxes",
"mails": "Mails",
"storage": "Storage"
"storage": "Αποθηκευτικός χώρος"
},
"maintainerr": {
"itemsHandled": "Items Handled",
@@ -962,61 +986,61 @@
"reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity"
"totalCapacity": "Συνολική χωρητικότητα"
},
"netdata": {
"warnings": "Warnings",
"criticals": "Criticals"
"warnings": "Προειδοποιήσεις",
"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"
"title": "Τίτλος",
"priority": "Προτεραιότητα",
"lastReceived": "Τελευταία Ληφθέντα",
"message": "Μήνυμα",
"tags": "Ετικέτες",
"none": "Κανένα",
"min": "Ελάχιστο",
"low": "Χαμηλό",
"default": "Προεπιλογή",
"high": "Υψηλό",
"urgent": "Επείγον"
},
"plantit": {
"events": "Events",
"plants": "Plants",
"photos": "Photos",
"species": "Species"
"events": "Συμβάντα",
"plants": "Φυτά",
"photos": "Φωτογραφίες",
"species": "Είδη"
},
"gitea": {
"notifications": "Notifications",
"issues": "Issues",
"notifications": "Ειδοποιήσεις",
"issues": "Προβλήματα",
"pulls": "Pull Requests",
"repositories": "Repositories"
"repositories": "Αποθετήρια"
},
"stash": {
"scenes": "Scenes",
"scenesPlayed": "Scenes Played",
"playCount": "Total Plays",
"playDuration": "Time Watched",
"sceneSize": "Scenes Size",
"sceneDuration": "Scenes Duration",
"images": "Images",
"imageSize": "Images Size",
"galleries": "Galleries",
"performers": "Performers",
"studios": "Studios",
"movies": "Movies",
"tags": "Tags",
"oCount": "O Count"
"scenes": "Σκηνές",
"scenesPlayed": "Σκηνές Που Παίχτηκαν",
"playCount": "Συνολικές Αναπαραγωγές",
"playDuration": "Ώρα Παρακολούθησης",
"sceneSize": "Μέγεθος Σκηνών",
"sceneDuration": "Διάρκεια Σκηνών",
"images": "Εικόνες",
"imageSize": "Μέγεθος Εικόνων",
"galleries": "Συλλογές",
"performers": "Καλλιτέχνες",
"studios": "Στούντιο",
"movies": "Ταινίες",
"tags": "Ετικέτες",
"oCount": "Μετρητής O"
},
"tandoor": {
"users": "Users",
"recipes": "Recipes",
"keywords": "Keywords"
"users": "Χρήστες",
"recipes": "Συνταγές",
"keywords": "Λέξεις-κλειδιά"
},
"homebox": {
"items": "Items",
"totalWithWarranty": "With Warranty",
"items": "Στοιχεία",
"totalWithWarranty": "Με Εγγύηση",
"locations": "Locations",
"labels": "Labels",
"users": "Users",
@@ -1079,72 +1103,72 @@
"vikunja": {
"projects": "Active Projects",
"tasks7d": "Tasks Due This Week",
"tasksOverdue": "Overdue Tasks",
"tasksInProgress": "Tasks In Progress"
"tasksOverdue": "Εκπρόθεσμες Εργασίες",
"tasksInProgress": "Εργασίες Σε Εξέλιξη"
},
"headscale": {
"name": "Name",
"address": "Address",
"last_seen": "Last Seen",
"status": "Status",
"online": "Online",
"offline": "Offline"
"name": "Όνομα",
"address": "Διεύθυνση",
"last_seen": "Τελευταία Σύνδεση",
"status": "Κατάσταση",
"online": "Σε Σύνδεση",
"offline": "Χωρίς Σύνδεση"
},
"beszel": {
"name": "Name",
"systems": "Systems",
"up": "Up",
"down": "Down",
"paused": "Paused",
"pending": "Pending",
"status": "Status",
"updated": "Updated",
"name": "Όνομα",
"systems": "Συστήματα",
"up": "Πάνω",
"down": "Κάτω",
"paused": "Σε παύση",
"pending": "Σε εκκρεμότητα",
"status": "Κατάσταση",
"updated": "Ενημερώθηκε",
"cpu": "CPU",
"memory": "MEM",
"disk": "Disk",
"network": "NET"
"disk": "Δίσκος",
"network": "Δίκτυο"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"healthy": "Healthy",
"apps": "Εφαρμογές",
"synced": "Συγχρονισμένα",
"outOfSync": "Εκτός Συγχρονισμού",
"healthy": "Υγιή",
"degraded": "Degraded",
"progressing": "Progressing",
"missing": "Missing",
"suspended": "Suspended"
"progressing": "Σε εξέλιξη",
"missing": "Λείπουν",
"suspended": "Σε αναστολή"
},
"spoolman": {
"loading": "Loading"
"loading": "Γίνεται φόρτωση"
},
"gitlab": {
"groups": "Groups",
"issues": "Issues",
"merges": "Merge Requests",
"groups": "Ομάδες",
"issues": "Προβλήματα",
"merges": "Αιτήματα Συγχώνευσης",
"projects": "Projects"
},
"apcups": {
"status": "Status",
"load": "Load",
"bcharge": "Battery Charge",
"timeleft": "Time Left"
"status": "Κατάσταση",
"load": "Φόρτωση",
"bcharge": "Φόρτιση Μπαταρίας",
"timeleft": "Χρόνος που απομένει"
},
"karakeep": {
"bookmarks": "Bookmarks",
"favorites": "Favorites",
"archived": "Archived",
"highlights": "Highlights",
"lists": "Lists",
"tags": "Tags"
"bookmarks": "Σελιδοδείκτες",
"favorites": "Αγαπημένα",
"archived": "Αρχειοθετημένα",
"highlights": "Επισημάνσεις",
"lists": "Λίστες",
"tags": "Ετικέτες"
},
"slskd": {
"slskStatus": "Network",
"connected": "Connected",
"disconnected": "Disconnected",
"updateStatus": "Update",
"update_yes": "Available",
"update_no": "Up to Date",
"downloads": "Downloads",
"slskStatus": "Δίκτυο",
"connected": "Σε σύνδεση",
"disconnected": "Αποσυνδέθηκε",
"updateStatus": "Ενημέρωση",
"update_yes": "Διαθέσιμα",
"update_no": "Ενημερωμένο",
"downloads": "Λήψεις",
"uploads": "Uploads",
"sharedFiles": "Files"
},
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Malsana",
"not_found": "Ne trovita",
"exited": "Eliris",
"partial": "Parta"
"partial": "Parta",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "No saludable",
"not_found": "No encontrado",
"exited": "Terminado",
"partial": "Parcial"
"partial": "Parcial",
"created": "Creado",
"paused": "Pausado",
"restarting": "Reiniciando",
"removing": "Eliminando",
"dead": "Muerto"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Descargando",
"seed": "Semillas"
},
"qui": {
"leech": "Sanguijuela",
"download": "Descargar",
"seed": "Semilla",
"upload": "Subir",
"total": "Total",
"errored": "Con error",
"ratio": "Ratio",
"freeSpace": "Libre"
},
"qnap": {
"cpuUsage": "Uso de la CPU",
"memUsage": "Uso de la memoria",
@@ -775,7 +790,8 @@
"queue": "Cola",
"processing": "Procesando",
"processed": "Procesado",
"time": "Tiempo"
"time": "Tiempo",
"failed": "Fallido"
},
"firefly": {
"networth": "Patrimonio neto",
@@ -854,6 +870,14 @@
"reading": "Lectura",
"finished": "Finalizado"
},
"bookorbit": {
"books": "Libros",
"authors": "Autores",
"series": "Series",
"storage": "Almacenamiento",
"reading": "Lectura",
"finished": "Finalizado"
},
"jdownloader": {
"downloadCount": "En cola",
"downloadBytesRemaining": "Restante",
@@ -1253,5 +1277,8 @@
"wanted": "Buscado",
"queued": "En cola",
"leagues": "Ligas"
},
"feed": {
"noItems": "No hay elementos"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Mauvaise santé",
"not_found": "Inconnu",
"exited": "Arrêté",
"partial": "Partiel"
"partial": "Partiel",
"created": "Créé",
"paused": "En pause",
"restarting": "Redémarrage",
"removing": "Suppression",
"dead": "Mort"
},
"ping": {
"error": "Erreur",
@@ -251,6 +256,16 @@
"leech": "En téléchargement",
"seed": "En partage"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Utilisation CPU",
"memUsage": "RAM utilisée",
@@ -775,7 +790,8 @@
"queue": "File d'attente",
"processing": "En traitement",
"processed": "Traité",
"time": "Temps"
"time": "Temps",
"failed": "Failed"
},
"firefly": {
"networth": "Valeur Nette",
@@ -854,6 +870,14 @@
"reading": "En cours de lecture",
"finished": "Terminés"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "File d'attente",
"downloadBytesRemaining": "Restant",
@@ -1253,5 +1277,8 @@
"wanted": "Recherché",
"queued": "En file d'attente",
"leagues": "Ligues"
},
"feed": {
"noItems": "No items"
}
}
+87 -60
View File
@@ -21,7 +21,7 @@
"seconds": "שניות"
},
"auth": {
"signout": "Sign out"
"signout": "התנתק"
},
"widget": {
"missing_type": "סוג ווידג'ט חסר: {{type}}",
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "חיפוש…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "מנוע חיפוש: {{name}}",
"select_provider": "בחר מנוע חיפוש"
},
"resources": {
"cpu": "מעבד",
@@ -72,18 +72,18 @@
"empty_data": "מצב תת-מערכת לא ידוע"
},
"unifi_drive": {
"healthy": "Healthy",
"degraded": "Degraded",
"no_data": "No storage data available"
"healthy": "בריא",
"degraded": "ירוד",
"no_data": "אין אחסון זמין"
},
"duplicati": {
"jobs": "Jobs",
"stored": "Stored",
"jobs": "משימות",
"stored": "שמור",
"lastBackup": "Last Run",
"nextRun": "Next Run",
"running": "Running",
"warnings": "Warnings",
"errors": "Errors"
"warnings": "הזהרות",
"errors": "שגיאות"
},
"docker": {
"rx": "RX",
@@ -99,7 +99,12 @@
"unhealthy": "לא בריא",
"not_found": "לא נמצא",
"exited": "יצא",
"partial": "חלקי"
"partial": "חלקי",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "שגיאה",
@@ -127,15 +132,15 @@
"songs": "שירים"
},
"jellyfin": {
"playing": "Playing",
"playing": "מנגן",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
"movies": "Movies",
"series": "Series",
"episodes": "Episodes",
"songs": "Songs",
"albums": "Albums"
"no_active": "אין מדיה פעילה",
"movies": "סרטים",
"series": "סדרות",
"episodes": "פרקים",
"songs": "שירים",
"albums": "אלבומים"
},
"esphome": {
"offline": "מכובה",
@@ -251,6 +256,16 @@
"leech": "עלוקה",
"seed": "זורע"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "שימוש במעבד",
"memUsage": "שימוש בזיכרון",
@@ -310,11 +325,11 @@
"available": "זמין"
},
"seerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available",
"completed": "Completed",
"processing": "Processing",
"pending": "ממתין",
"approved": "מאושר",
"available": "זמין",
"completed": "הסתיים",
"processing": "מעבד",
"issues": "Open Issues"
},
"netalertx": {
@@ -359,16 +374,16 @@
"address": "כתובת",
"expires": "תאריך תפוגה",
"never": "אף פעם",
"user": "User",
"user": "משתמש",
"hostname": "Hostname",
"name": "Name",
"name": "שם",
"client_version": "Client Version",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"os": "מערכת הפעלה",
"created": "יוצר",
"authorized": "מורשה",
"is_external": "האם חיצוני",
"update_available": "עדכון זמין",
"tags": "תגיות",
"last_seen": "נראה לאחרונה",
"now": "כעת",
"years": "{{number}}y",
@@ -378,8 +393,8 @@
"minutes": "{{number}}m",
"seconds": "{{number}}s",
"ago": "{{value}} Ago",
"true": "Yes",
"false": "No"
"true": "כן",
"false": "לא"
},
"technitium": {
"totalQueries": "שאילתות",
@@ -484,7 +499,7 @@
},
"pulse": {
"nodes": "Nodes",
"vms": "VMs",
"vms": "מכונות וירטואליות",
"lxcs": "LXCs"
},
"glances": {
@@ -584,7 +599,7 @@
"up": "למעלה",
"pending": "ממתין",
"down": "למטה",
"ok": "Ok"
"ok": "אוקיי"
},
"healthchecks": {
"new": "חדש",
@@ -653,9 +668,9 @@
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"sites": "אתרים",
"resources": "משאבים",
"targets": "מטרות",
"traffic": "Traffic",
"in": "In",
"out": "Out"
@@ -757,7 +772,7 @@
"volumeAvailable": "זמין"
},
"dispatcharr": {
"channels": "Channels",
"channels": "ערוצים",
"streams": "Streams"
},
"mylar": {
@@ -775,7 +790,8 @@
"queue": "תור",
"processing": "בעיבוד",
"processed": "עובדו",
"time": "זמן"
"time": "זמן",
"failed": "Failed"
},
"firefly": {
"networth": "שווי נקי",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "תור",
"downloadBytesRemaining": "נותר",
@@ -1182,9 +1206,9 @@
"nextRenewingSubscription": "תשלום הבא"
},
"unraid": {
"STARTED": "Started",
"STOPPED": "Stopped",
"NEW_ARRAY": "New Array",
"STARTED": "התחיל",
"STOPPED": "נעצר",
"NEW_ARRAY": "מערך חדש",
"RECON_DISK": "Reconstructing Disk",
"DISABLE_DISK": "Disk Disabled",
"SWAP_DSBL": "Swap Disable",
@@ -1196,31 +1220,31 @@
"notifications": "Notifications",
"status": "Status",
"cpu": "CPU",
"memoryUsed": "Memory Used",
"memoryAvailable": "Memory Available",
"memoryUsed": "זיכרון בשימוש",
"memoryAvailable": "זיכרון זמין",
"arrayUsed": "Array Used",
"arrayFree": "Array Free",
"poolUsed": "{{pool}} Used",
"poolFree": "{{pool}} Free"
},
"backrest": {
"num_plans": "Plans",
"num_success_30": "Successes",
"num_failure_30": "Failures",
"num_plans": "תוכניות",
"num_success_30": "הצלחות",
"num_failure_30": "כשלונות",
"num_success_latest": "Succeeding",
"num_failure_latest": "Failing",
"bytes_added_30": "Bytes Added"
},
"yourspotify": {
"songs": "Songs",
"time": "Time",
"artists": "Artists"
"songs": "שירים",
"time": "זמן",
"artists": "אומנים"
},
"arcane": {
"containers": "Containers",
"images": "Images",
"image_updates": "Image Updates",
"images_unused": "Unused",
"images_unused": "לא בשימוש",
"environment_required": "Environment ID Required"
},
"dockhand": {
@@ -1230,7 +1254,7 @@
"memory": "Memory",
"images": "Images",
"volumes": "Volumes",
"events_today": "Events Today",
"events_today": "אירועים היום",
"pending_updates": "Pending Updates",
"stacks": "Stacks",
"paused": "Paused",
@@ -1238,20 +1262,23 @@
"environment_not_found": "Environment Not Found"
},
"sparkyfitness": {
"eaten": "Eaten",
"burned": "Burned",
"remaining": "Remaining",
"steps": "Steps"
"eaten": "נאכלו",
"burned": "נשרפו",
"remaining": "נותרו",
"steps": "צעדים"
},
"syncthing": {
"connected": "Connected",
"synced": "Synced",
"errors": "Errors",
"storage": "Storage"
"synced": "מסונכרן",
"errors": "שגיאות",
"storage": "אחסון"
},
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Nefunkcionalno",
"not_found": "Nepronađeno",
"exited": "Zatoreno",
"partial": "Djelomično"
"partial": "Djelomično",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Greška",
@@ -251,6 +256,16 @@
"leech": "Korištenje tuđeg sadržaja",
"seed": "Prenošenje preuzetog sadržaja"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Korištenje procesora",
"memUsage": "Korištenje memorije",
@@ -775,7 +790,8 @@
"queue": "Red čekanja",
"processing": "Obrada",
"processed": "Obrađeno",
"time": "Vrijeme"
"time": "Vrijeme",
"failed": "Failed"
},
"firefly": {
"networth": "Neto vrijednost",
@@ -854,6 +870,14 @@
"reading": "Čitanje",
"finished": "Završeno"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Red čekanja",
"downloadBytesRemaining": "Preostalo",
@@ -1253,5 +1277,8 @@
"wanted": "Željeno",
"queued": "U redu čekanja",
"leagues": "Lige"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Egészségtelen",
"not_found": "Nem található",
"exited": "Kilépett",
"partial": "Részleges"
"partial": "Részleges",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Hiba",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Processzor Használat",
"memUsage": "Memória Használat",
@@ -775,7 +790,8 @@
"queue": "Sorban",
"processing": "Feldolgozás alatt",
"processed": "Feldolgozott",
"time": "Idő"
"time": "Idő",
"failed": "Failed"
},
"firefly": {
"networth": "Nettó érték",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Tidak Lancar",
"not_found": "Tidak Ditemukan",
"exited": "Terkeluar",
"partial": "Sebagian"
"partial": "Sebagian",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Penggunaan CPU",
"memUsage": "Penggunaan MEM",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Waktu"
"time": "Waktu",
"failed": "Failed"
},
"firefly": {
"networth": "Kekayaan Bersih",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Non sano",
"not_found": "Non trovato",
"exited": "Uscito",
"partial": "Parziale"
"partial": "Parziale",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Errore",
@@ -251,6 +256,16 @@
"leech": "In download",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Utilizzo CPU",
"memUsage": "Utilizzo MEM",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Tempo"
"time": "Tempo",
"failed": "Failed"
},
"firefly": {
"networth": "Valore Netto",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+175 -148
View File
@@ -39,9 +39,9 @@
"wait": "お待ちください"
},
"search": {
"placeholder": "検索…",
"placeholder": "検索 ...",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"select_provider": "検索エンジンを選択"
},
"resources": {
"cpu": "CPU",
@@ -99,7 +99,12 @@
"unhealthy": "非健全",
"not_found": "不明",
"exited": "停止しました",
"partial": "部分的"
"partial": "部分的",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "エラー",
@@ -127,18 +132,18 @@
"songs": "曲"
},
"jellyfin": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
"movies": "Movies",
"series": "Series",
"episodes": "Episodes",
"songs": "Songs",
"albums": "Albums"
"playing": "再生中",
"transcoding": "変換中",
"bitrate": "ビットレート",
"no_active": "アクティブ・ストリーム無し",
"movies": "映画",
"series": "シリーズ",
"episodes": "エピソード",
"songs": "曲",
"albums": "アルバム"
},
"esphome": {
"offline": "",
"offline": "オフライン ",
"offline_alt": "オフライン",
"online": "オンライン",
"total": "Total",
@@ -236,21 +241,31 @@
},
"rutorrent": {
"active": "アクティブ",
"upload": "Upload",
"download": "Download"
"upload": "アップロード",
"download": "ダウンロード"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"download": "ダウンロード",
"upload": "アップロード",
"leech": "Leech",
"seed": "Seed"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"download": "ダウンロード",
"upload": "アップロード",
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU使用量",
"memUsage": "MEM使用量",
@@ -280,7 +295,7 @@
"queued": "待機中",
"series": "Series",
"queue": "Queue",
"unknown": "Unknown"
"unknown": "不明"
},
"radarr": {
"wanted": "Wanted",
@@ -360,15 +375,15 @@
"expires": "失効",
"never": "なし",
"user": "User",
"hostname": "Hostname",
"name": "Name",
"client_version": "Client Version",
"hostname": "ホスト名",
"name": "名前",
"client_version": "クライアントバージョン",
"os": "OS",
"created": "Created",
"authorized": "Authorized",
"created": "作成済み",
"authorized": "承認済み",
"is_external": "Is External",
"update_available": "Update Available",
"tags": "Tags",
"update_available": "アップデートが利用できます",
"tags": "タグ",
"last_seen": "最終日時",
"now": "現在",
"years": "{{number}}年",
@@ -378,11 +393,11 @@
"minutes": "{{number}}分",
"seconds": "{{number}}秒",
"ago": "{{value}} 前",
"true": "Yes",
"false": "No"
"true": "はい",
"false": "いいえ"
},
"technitium": {
"totalQueries": "Queries",
"totalQueries": "クエリ",
"totalNoError": "成功",
"totalServerFailure": "失敗",
"totalNxDomain": "NXドメイン",
@@ -390,7 +405,7 @@
"totalAuthoritative": "正式",
"totalRecursive": "再帰的",
"totalCached": "キャッシュ済み",
"totalBlocked": "Blocked",
"totalBlocked": "ブロック中",
"totalDropped": "ドロップ済み",
"totalClients": "クライアント"
},
@@ -406,13 +421,13 @@
"middleware": "ミドルウェア"
},
"trilium": {
"version": "Version",
"notesCount": "Notes",
"dbSize": "Database Size",
"unknown": "Unknown"
"version": "バージョン",
"notesCount": "メモ",
"dbSize": "データベースサイズ",
"unknown": "不明"
},
"navidrome": {
"nothing_streaming": "No Active Streams",
"nothing_streaming": "アクティブストリーム無し",
"please_wait": "お待ちください"
},
"npm": {
@@ -477,7 +492,7 @@
"authorizationsLast24H": "Auths (24h)"
},
"proxmox": {
"mem": "MEM",
"mem": "メモリ",
"cpu": "CPU",
"lxc": "LXC",
"vms": "VM"
@@ -489,17 +504,17 @@
},
"glances": {
"cpu": "CPU",
"load": "Load",
"wait": "Please wait",
"temp": "TEMP",
"load": "負荷",
"wait": "お待ちください",
"temp": "温度",
"_temp": "温度",
"warn": "警告",
"uptime": "UP",
"total": "Total",
"free": "Free",
"used": "Used",
"days": "d",
"hours": "h",
"uptime": "稼働時間",
"total": "合計",
"free": "空き容量",
"used": "使用済み",
"days": "日",
"hours": "時",
"crit": "クリティカル",
"read": "Read",
"write": "書き込み",
@@ -540,39 +555,39 @@
"57-day": "着氷性の小雨",
"57-night": "Freezing Drizzle",
"61-day": "小雨",
"61-night": "Light Rain",
"61-night": "小雨",
"63-day": "雨",
"63-night": "Rain",
"63-night": "雨",
"65-day": "大雨",
"65-night": "Heavy Rain",
"65-night": "大雨",
"66-day": "着氷性の雨",
"66-night": "Freezing Rain",
"67-day": "Freezing Rain",
"67-night": "Freezing Rain",
"66-night": "着氷性の雨",
"67-day": "着氷性の雨",
"67-night": "着氷性の雨",
"71-day": "小雪",
"71-night": "Light Snow",
"71-night": "小雪",
"73-day": "雪",
"73-night": "Snow",
"73-night": "雪",
"75-day": "大雪",
"75-night": "Heavy Snow",
"77-day": "霧雪",
"77-night": "Snow Grains",
"80-day": "弱いにわか雨",
"80-night": "Light Showers",
"80-night": "弱いにわか雨",
"81-day": "にわか雨",
"81-night": "Showers",
"81-night": "にわか雨",
"82-day": "強いにわか雨",
"82-night": "Heavy Showers",
"82-night": "強いにわか雨",
"85-day": "にわか雪",
"85-night": "Snow Showers",
"86-day": "Snow Showers",
"86-night": "Snow Showers",
"85-night": "にわか雪",
"86-day": "にわか雪",
"86-night": "にわか雪",
"95-day": "雷雨",
"95-night": "Thunderstorm",
"95-night": "雷雨",
"96-day": "雷雨・ひょう",
"96-night": "Thunderstorm With Hail",
"99-day": "Thunderstorm With Hail",
"99-night": "Thunderstorm With Hail"
"96-night": "雹を伴う雷雨",
"99-day": "雹を伴う雷雨",
"99-night": "雹を伴う雷雨"
},
"homebridge": {
"available_update": "システム",
@@ -640,7 +655,7 @@
"signalQuality": "クオリティ",
"symbolQuality": "Quality",
"networkRate": "Bitrate",
"clientIP": "クライアント IP"
"clientIP": "クライアントIP"
},
"scrutiny": {
"passed": "合格",
@@ -656,7 +671,7 @@
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"traffic": "トラフィック",
"in": "In",
"out": "Out"
},
@@ -669,7 +684,7 @@
"low_battery": "バッテリー残量低下"
},
"nextdns": {
"wait": "Please Wait",
"wait": "お待ちください",
"no_devices": "デバイス データを受信していません"
},
"mikrotik": {
@@ -690,7 +705,7 @@
"limit": "リミット"
},
"opnsense": {
"cpu": "CPU Load",
"cpu": "CPU負荷",
"memory": "アクティブ・メモリ",
"wanUpload": "WANアップロード",
"wanDownload": "WANダウンロード"
@@ -702,14 +717,14 @@
"layers": "レイヤー"
},
"octoprint": {
"printer_state": "Status",
"printer_state": "ステータス",
"temp_tool": "ツール温度",
"temp_bed": "ベッド温度",
"job_completion": "完了"
},
"cloudflared": {
"origin_ip": "オリジンIP",
"status": "Status"
"status": "ステータス"
},
"pfsense": {
"load": "読み込み平均",
@@ -728,17 +743,17 @@
"memory_usage": "メモリ"
},
"immich": {
"users": "Users",
"users": "ユーザ",
"photos": "写真",
"videos": "Videos",
"videos": "ビデオ",
"storage": "ストレージ"
},
"uptimekuma": {
"up": "サイトUp",
"down": "サイトDown",
"uptime": "Uptime",
"uptime": "稼働時間",
"incident": "事件",
"m": "m"
"m": "分"
},
"atsumeru": {
"series": "Series",
@@ -767,15 +782,16 @@
},
"photoprism": {
"albums": "Albums",
"photos": "Photos",
"videos": "Videos",
"photos": "写真",
"videos": "動画",
"people": "人"
},
"fileflows": {
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -879,12 +903,12 @@
},
"gamedig": {
"status": "Status",
"online": "Online",
"offline": "Offline",
"online": "オンライン",
"offline": "オフライン ",
"name": "名前",
"map": "マップ",
"currentPlayers": "現在のプレーヤー",
"players": "Players",
"players": "プレイヤー",
"maxPlayers": "最大プレイヤー数",
"bots": "ボット",
"ping": "Ping"
@@ -897,21 +921,21 @@
},
"mealie": {
"recipes": "レシピ",
"users": "Users",
"categories": "Categories",
"users": "ユーザ",
"categories": "カテゴリ",
"tags": "タグ"
},
"openmediavault": {
"downloading": "ダウンロード中",
"total": "Total",
"running": "Running",
"stopped": "Stopped",
"total": "合計",
"running": "実行中",
"stopped": "停止中",
"passed": "Passed",
"failed": "Failed"
},
"openwrt": {
"uptime": "Uptime",
"cpuLoad": "CPU 平均負荷(5 分)",
"cpuLoad": "CPU 平均負荷(5分)",
"up": "Up",
"down": "Down",
"bytesTx": "送信済み",
@@ -919,7 +943,7 @@
},
"uptimerobot": {
"status": "Status",
"uptime": "Uptime",
"uptime": "稼働時間",
"lastDown": "最後のダウンタイム",
"downDuration": "ダウンタイム時間",
"sitesUp": "Sites Up",
@@ -949,9 +973,9 @@
},
"mailcow": {
"domains": "Domains",
"mailboxes": "Mailboxes",
"mails": "Mails",
"storage": "Storage"
"mailboxes": "受信箱",
"mails": "メール",
"storage": "ストレージ"
},
"maintainerr": {
"itemsHandled": "Items Handled",
@@ -969,7 +993,7 @@
"criticals": "重大"
},
"ntfy": {
"title": "Title",
"title": "タイトル",
"priority": "Priority",
"lastReceived": "Last Received",
"message": "Message",
@@ -984,7 +1008,7 @@
"plantit": {
"events": "イベント",
"plants": "植物",
"photos": "Photos",
"photos": "写真",
"species": "種"
},
"gitea": {
@@ -1132,27 +1156,27 @@
"karakeep": {
"bookmarks": "Bookmarks",
"favorites": "Favorites",
"archived": "Archived",
"highlights": "Highlights",
"lists": "Lists",
"tags": "Tags"
"archived": "アーカイブ済み",
"highlights": "ハイライト",
"lists": "リスト",
"tags": "タグ"
},
"slskd": {
"slskStatus": "Network",
"connected": "Connected",
"disconnected": "Disconnected",
"updateStatus": "Update",
"update_yes": "Available",
"update_no": "Up to Date",
"downloads": "Downloads",
"uploads": "Uploads",
"sharedFiles": "Files"
"slskStatus": "ネットワーク",
"connected": "接続されています",
"disconnected": "切断されています",
"updateStatus": "更新",
"update_yes": "利用可能",
"update_no": "最新",
"downloads": "ダウンロード",
"uploads": "アップロード",
"sharedFiles": "ファイル"
},
"jellystat": {
"songs": "Songs",
"movies": "Movies",
"episodes": "Episodes",
"other": "Other"
"songs": "曲",
"movies": "映画",
"episodes": "エピソード",
"other": "その他"
},
"checkmk": {
"serviceErrors": "Service issues",
@@ -1193,11 +1217,11 @@
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks",
"NEW_DISK_TOO_SMALL": "New Disk Too Small",
"NO_DATA_DISKS": "No Data Disks",
"notifications": "Notifications",
"status": "Status",
"notifications": "通知",
"status": "ステータス",
"cpu": "CPU",
"memoryUsed": "Memory Used",
"memoryAvailable": "Memory Available",
"memoryUsed": "メモリ使用量",
"memoryAvailable": "使用可能なメモリ",
"arrayUsed": "Array Used",
"arrayFree": "Array Free",
"poolUsed": "{{pool}} Used",
@@ -1205,53 +1229,56 @@
},
"backrest": {
"num_plans": "Plans",
"num_success_30": "Successes",
"num_failure_30": "Failures",
"num_success_latest": "Succeeding",
"num_failure_latest": "Failing",
"bytes_added_30": "Bytes Added"
"num_success_30": "成功",
"num_failure_30": "失敗",
"num_success_latest": "成功",
"num_failure_latest": "失敗",
"bytes_added_30": "バイトが追加されました"
},
"yourspotify": {
"songs": "Songs",
"time": "Time",
"artists": "Artists"
"songs": "曲",
"time": "時間",
"artists": "アーティスト"
},
"arcane": {
"containers": "Containers",
"images": "Images",
"image_updates": "Image Updates",
"images_unused": "Unused",
"environment_required": "Environment ID Required"
"containers": "コンテナ",
"images": "イメージ",
"image_updates": "イメージの更新",
"images_unused": "未使用",
"environment_required": "Enviroment ID が必要です"
},
"dockhand": {
"running": "Running",
"stopped": "Stopped",
"running": "実行中",
"stopped": "停止中",
"cpu": "CPU",
"memory": "Memory",
"images": "Images",
"volumes": "Volumes",
"events_today": "Events Today",
"pending_updates": "Pending Updates",
"stacks": "Stacks",
"paused": "Paused",
"total": "Total",
"environment_not_found": "Environment Not Found"
"memory": "メモリ",
"images": "イメージ",
"volumes": "ボリューム",
"events_today": "今日のイベント",
"pending_updates": "保留中の更新",
"stacks": "スタック数",
"paused": "一時停止中",
"total": "合計",
"environment_not_found": "環境が見つかりません"
},
"sparkyfitness": {
"eaten": "Eaten",
"burned": "Burned",
"remaining": "Remaining",
"steps": "Steps"
"eaten": "摂取カロリー",
"burned": "消費カロリー",
"remaining": "残り",
"steps": "ステップ"
},
"syncthing": {
"connected": "Connected",
"synced": "Synced",
"errors": "Errors",
"storage": "Storage"
"connected": "接続されています",
"synced": "同期しました",
"errors": "エラー",
"storage": "ストレージ"
},
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
"wanted": "募集中",
"queued": "処理待ち",
"leagues": "リーグ"
},
"feed": {
"noItems": "No items"
}
}
+31 -4
View File
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "검색…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "검색 엔진: {{name}}",
"select_provider": "검색 엔진 선택"
},
"resources": {
"cpu": "CPU",
@@ -99,7 +99,12 @@
"unhealthy": "비정상",
"not_found": "찾을 수 없음",
"exited": "종료됨",
"partial": "부분적"
"partial": "부분적",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "오류",
@@ -251,6 +256,16 @@
"leech": "리치",
"seed": "시드"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU 사용량",
"memUsage": "메모리 사용량",
@@ -775,7 +790,8 @@
"queue": "대기열",
"processing": "처리 중",
"processed": "처리됨",
"time": "시간"
"time": "시간",
"failed": "Failed"
},
"firefly": {
"networth": "순자산",
@@ -854,6 +870,14 @@
"reading": "읽는 중",
"finished": "완료"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "대기열",
"downloadBytesRemaining": "남음",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Kurang sihat",
"not_found": "Tidak dijumpai",
"exited": "Dimatikan",
"partial": "Sebahagian"
"partial": "Sebahagian",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Ongezond",
"not_found": "Niet Gevonden",
"exited": "Gestopt",
"partial": "Gedeeltelijk"
"partial": "Gedeeltelijk",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Fout",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Delen"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Verbruik",
"memUsage": "MEM Gebruik",
@@ -775,7 +790,8 @@
"queue": "Wachtrij",
"processing": "Verwerken",
"processed": "Verwerkt",
"time": "Tijd"
"time": "Tijd",
"failed": "Failed"
},
"firefly": {
"networth": "Totaal eigen vermogen",
@@ -854,6 +870,14 @@
"reading": "Lezen",
"finished": "Voltooid"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Wachtrij",
"downloadBytesRemaining": "Resterend",
@@ -1253,5 +1277,8 @@
"wanted": "Gewenst",
"queued": "In de wachtrij",
"leagues": "Competities"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Usunn",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Delvis"
"partial": "Delvis",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Bruk",
"memUsage": "Minnebruk",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Niezdrowy",
"not_found": "Nie znaleziono",
"exited": "Zakończony",
"partial": "Częściowy"
"partial": "Częściowy",
"created": "Utworzony",
"paused": "Wstrzymany",
"restarting": "Ponowne uruchamianie",
"removing": "Usuwanie",
"dead": "Martwy"
},
"ping": {
"error": "Błąd",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Pobieranie",
"seed": "Seed",
"upload": "Wysyłanie",
"total": "Razem",
"errored": "Z błędami",
"ratio": "Ratio",
"freeSpace": "Wolne"
},
"qnap": {
"cpuUsage": "Użycie CPU",
"memUsage": "Użycie pamięci",
@@ -775,7 +790,8 @@
"queue": "W kolejce",
"processing": "Przetwarzane",
"processed": "Przetworzone",
"time": "Czas"
"time": "Czas",
"failed": "Failed"
},
"firefly": {
"networth": "Wartość netto",
@@ -854,6 +870,14 @@
"reading": "Czytane",
"finished": "Skończone"
},
"bookorbit": {
"books": "Książki",
"authors": "Autorzy",
"series": "Serie",
"storage": "Pamięć",
"reading": "Czytane",
"finished": "Skończone"
},
"jdownloader": {
"downloadCount": "W kolejce",
"downloadBytesRemaining": "Pozostało",
@@ -1253,5 +1277,8 @@
"wanted": "Poszukiwane",
"queued": "W kolejce",
"leagues": "Ligi"
},
"feed": {
"noItems": "Brak elementów"
}
}
+32 -5
View File
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Pesquisar…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Motor de Pesquisa: {{name}}",
"select_provider": "Seleciona o motor de pesquisa"
},
"resources": {
"cpu": "CPU",
@@ -99,7 +99,12 @@
"unhealthy": "Não-saudável",
"not_found": "Não Encontrado",
"exited": "Saiu",
"partial": "Parcial"
"partial": "Parcial",
"created": "Criado",
"paused": "Pausado",
"restarting": "A reiniciar",
"removing": "A remover",
"dead": "Morto"
},
"ping": {
"error": "Erro",
@@ -251,6 +256,16 @@
"leech": "Fuga",
"seed": "Semear"
},
"qui": {
"leech": "Fuga",
"download": "Transferir",
"seed": "Semear",
"upload": "Enviar",
"total": "Total",
"errored": "Erro",
"ratio": "Rácio",
"freeSpace": "Livre"
},
"qnap": {
"cpuUsage": "Utilização do CPU",
"memUsage": "Utilização de Memória",
@@ -474,7 +489,7 @@
"users": "Utilizadores",
"loginsLast24H": "Inícios de Sessão (24h)",
"failedLoginsLast24H": "Inícios de Sessão Falhados (24h)",
"authorizationsLast24H": "Auths (24h)"
"authorizationsLast24H": "Autenticações (24h)"
},
"proxmox": {
"mem": "MEM",
@@ -775,7 +790,8 @@
"queue": "Fila de espera",
"processing": "A Processar",
"processed": "Processado",
"time": "Hora"
"time": "Hora",
"failed": "Failed"
},
"firefly": {
"networth": "Valor Líquido",
@@ -854,6 +870,14 @@
"reading": "A ler",
"finished": "Concluído"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Fila de espera",
"downloadBytesRemaining": "Restante",
@@ -1253,5 +1277,8 @@
"wanted": "Procurado",
"queued": "Em fila de espera",
"leagues": "Ligas"
},
"feed": {
"noItems": "Sem itens"
}
}
+45 -18
View File
@@ -21,7 +21,7 @@
"seconds": "s"
},
"auth": {
"signout": "Sign out"
"signout": "Encerrar Sessão"
},
"widget": {
"missing_type": "Tipo de Widget ausente: {{type}}",
@@ -40,8 +40,8 @@
},
"search": {
"placeholder": "Pesquisar…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"provider": "Motor de pesquisa: {{name}}",
"select_provider": "Selecionar motor de busca"
},
"resources": {
"cpu": "CPU",
@@ -99,7 +99,12 @@
"unhealthy": "Não-saudável",
"not_found": "Não Encontrado",
"exited": "Encerrado",
"partial": "Parcial"
"partial": "Parcial",
"created": "Criado",
"paused": "Pausado",
"restarting": "Reiniciando",
"removing": "Removendo",
"dead": "Inativo"
},
"ping": {
"error": "Erro",
@@ -135,7 +140,7 @@
"series": "Séries",
"episodes": "Episódios",
"songs": "Músicas",
"albums": "Albums"
"albums": "Álbuns"
},
"esphome": {
"offline": "Offline",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Uso de CPU",
"memUsage": "Uso de Memória",
@@ -775,7 +790,8 @@
"queue": "Fila de espera",
"processing": "Processando",
"processed": "Processado",
"time": "Hora"
"time": "Hora",
"failed": "Failed"
},
"firefly": {
"networth": "Valor Líquido",
@@ -854,6 +870,14 @@
"reading": "Lendo",
"finished": "Finalizado"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Fila de espera",
"downloadBytesRemaining": "Restante",
@@ -954,15 +978,15 @@
"storage": "Armazenamento"
},
"maintainerr": {
"itemsHandled": "Items Handled",
"episodesHandled": "Episodes Handled",
"moviesHandled": "Movies Handled",
"showsHandled": "Shows Handled",
"seasonsHandled": "Seasons Handled",
"reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity"
"itemsHandled": "Itens manuseados",
"episodesHandled": "Episódios tratados",
"moviesHandled": "Filmes tratados",
"showsHandled": "Séries tratados",
"seasonsHandled": "Temporadas tratadas",
"reclaimable": "Reclamável",
"movieReclaimable": "Filme Reclamável",
"showReclaimable": "Show Reclamável",
"totalCapacity": "Capacidade total"
},
"netdata": {
"warnings": "Alertas",
@@ -1250,8 +1274,11 @@
"storage": "Armazenagem"
},
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
"wanted": "Desejado",
"queued": "Na fila",
"leagues": "Ligas"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Nesănătos",
"not_found": "Negăsit",
"exited": "Ieşit",
"partial": "Parțial"
"partial": "Parțial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Timp"
"time": "Timp",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Нездоровый",
"not_found": "Не найдено",
"exited": "Вышел",
"partial": "Частичный"
"partial": "Частичный",
"created": "Создан",
"paused": "Приостановлен",
"restarting": "Перезапуск",
"removing": "Удаление",
"dead": "Мёртв"
},
"ping": {
"error": "Ошибка",
@@ -251,6 +256,16 @@
"leech": "Лич",
"seed": "Сид"
},
"qui": {
"leech": "Лич",
"download": "Скачивание",
"seed": "Сид",
"upload": "Загрузка",
"total": "Всего",
"errored": "Ошибок",
"ratio": "Соотношение",
"freeSpace": "Свободно"
},
"qnap": {
"cpuUsage": "Использование ЦП",
"memUsage": "Использование ОЗУ",
@@ -775,7 +790,8 @@
"queue": "Очередь",
"processing": "Обрабатывается",
"processed": "Обработано",
"time": "Время"
"time": "Время",
"failed": "Неудачно"
},
"firefly": {
"networth": "Общая средства",
@@ -854,6 +870,14 @@
"reading": "Читаю",
"finished": "Завершено"
},
"bookorbit": {
"books": "Книги",
"authors": "Авторы",
"series": "Серии",
"storage": "Память",
"reading": "Чтение",
"finished": "Завершено"
},
"jdownloader": {
"downloadCount": "Очередь",
"downloadBytesRemaining": "Осталось",
@@ -1253,5 +1277,8 @@
"wanted": "Требуется",
"queued": "В очереди",
"leagues": "Лиги"
},
"feed": {
"noItems": "Ничего нет"
}
}
+30 -3
View File
@@ -72,7 +72,7 @@
"empty_data": "Stav podsystému neznámy"
},
"unifi_drive": {
"healthy": "Healthy",
"healthy": "Zdravý",
"degraded": "Degraded",
"no_data": "No storage data available"
},
@@ -99,7 +99,12 @@
"unhealthy": "Nezdravý",
"not_found": "Nenájdené",
"exited": "Ukončené",
"partial": "Čiastočný"
"partial": "Čiastočný",
"created": "Vytvorené",
"paused": "Pozastavené",
"restarting": "Reštartuje sa",
"removing": "Odstraňuje sa",
"dead": "Mŕtve"
},
"ping": {
"error": "Chyba",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Využitie CPU",
"memUsage": "Využitie pamäte",
@@ -775,7 +790,8 @@
"queue": "Poradie",
"processing": "Processing",
"processed": "Spracované",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Úložisko",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Poradie",
"downloadBytesRemaining": "Zostávajúce",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "V poradí",
"leagues": "Leagues"
},
"feed": {
"noItems": "Žiadne položky"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Ni zdrav",
"not_found": "Ni najden",
"exited": "V izhodu",
"partial": "Delni"
"partial": "Delni",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU",
"memUsage": "MEM",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Neto vrednost",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Нездравих",
"not_found": "Није пронађено",
"exited": "Напуштених",
"partial": "Делимично"
"partial": "Делимично",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Грешка",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Искоришћеност процесора",
"memUsage": "Заузеће меморије",
@@ -775,7 +790,8 @@
"queue": "Ред",
"processing": "Обрада",
"processed": "Обрада",
"time": "Време"
"time": "Време",
"failed": "Failed"
},
"firefly": {
"networth": "Нето вредност",
@@ -854,6 +870,14 @@
"reading": "Читање",
"finished": "Завршено"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Ред",
"downloadBytesRemaining": "Преостало",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial"
"partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "Leech",
"seed": "Seed"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Sağlıksız",
"not_found": "Bulunamadı",
"exited": "Kapandı",
"partial": "Kısmi"
"partial": "Kısmi",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Hata",
@@ -251,6 +256,16 @@
"leech": "İndirilen",
"seed": "Gönderilen"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "İşlemci Kullanımı",
"memUsage": "Bellek Kullanımı",
@@ -775,7 +790,8 @@
"queue": "Kuyruk",
"processing": "İşleniyor",
"processed": "İşlendi",
"time": "Zaman"
"time": "Zaman",
"failed": "Failed"
},
"firefly": {
"networth": "Net değer",
@@ -854,6 +870,14 @@
"reading": "Okunuyor",
"finished": "Bitti"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Kuyruk",
"downloadBytesRemaining": "Kalan",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+34 -7
View File
@@ -21,7 +21,7 @@
"seconds": "с"
},
"auth": {
"signout": "Sign out"
"signout": "Вихід"
},
"widget": {
"missing_type": "Відсутній тип віджета: {{type}}",
@@ -41,7 +41,7 @@
"search": {
"placeholder": "Пошук…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
"select_provider": "Вибрати пошукову систему"
},
"resources": {
"cpu": "ЦП",
@@ -83,7 +83,7 @@
"nextRun": "Next Run",
"running": "Running",
"warnings": "Warnings",
"errors": "Errors"
"errors": "Помилки"
},
"docker": {
"rx": "RX",
@@ -99,7 +99,12 @@
"unhealthy": "Нездоровий",
"not_found": "Не знайдено",
"exited": "Вийшов",
"partial": "Частковий"
"partial": "Частковий",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Помилка",
@@ -135,7 +140,7 @@
"series": "Серіали",
"episodes": "Серії",
"songs": "Пiснi",
"albums": "Albums"
"albums": "Альбоми"
},
"esphome": {
"offline": "Офлайн",
@@ -251,6 +256,16 @@
"leech": "Ліч",
"seed": "Сід"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Використання CPU",
"memUsage": "Використання пам'яті",
@@ -775,7 +790,8 @@
"queue": "Черга",
"processing": "Обробка",
"processed": "Оброблено",
"time": "Час"
"time": "Час",
"failed": "Failed"
},
"firefly": {
"networth": "Чисті Активи",
@@ -854,6 +870,14 @@
"reading": "Читаю",
"finished": "Завершено"
},
"bookorbit": {
"books": "Книжки",
"authors": "Автори",
"series": "Серії",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Черга",
"downloadBytesRemaining": "Залишилось",
@@ -1252,6 +1276,9 @@
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
"leagues": "Ліги"
},
"feed": {
"noItems": "Немає елементів"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "Bất thường",
"not_found": "Không tìm thấy",
"exited": "Đã thoát",
"partial": "Một phần"
"partial": "Một phần",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Lỗi",
@@ -251,6 +256,16 @@
"leech": "Leecher",
"seed": "Seeder"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "Mức sử dụng CPU",
"memUsage": "Mức sử dụng bộ nhớ",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Queue",
"downloadBytesRemaining": "Remaining",
@@ -1253,5 +1277,8 @@
"wanted": "Cần tải",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "不健康的",
"not_found": "未找到",
"exited": "已退出",
"partial": "部分"
"partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "未完成下載",
"seed": "已完成下載"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU 使用率",
"memUsage": "記憶體使用率",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "淨值",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "佇列",
"downloadBytesRemaining": "剩餘大小",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "不健康",
"not_found": "未找到",
"exited": "已退出",
"partial": "部分"
"partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "错误",
@@ -251,6 +256,16 @@
"leech": "下载中",
"seed": "做种"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "处理器",
"memUsage": "内存使用",
@@ -775,7 +790,8 @@
"queue": "队列",
"processing": "处理中",
"processed": "已处理",
"time": "时间"
"time": "时间",
"failed": "Failed"
},
"firefly": {
"networth": "净值",
@@ -854,6 +870,14 @@
"reading": "阅读中",
"finished": "已读完"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "队列",
"downloadBytesRemaining": "剩余",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+29 -2
View File
@@ -99,7 +99,12 @@
"unhealthy": "不健康的",
"not_found": "找不到",
"exited": "已退出",
"partial": "部分"
"partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
},
"ping": {
"error": "Error",
@@ -251,6 +256,16 @@
"leech": "未完成下載",
"seed": "已完成下載"
},
"qui": {
"leech": "Leech",
"download": "Download",
"seed": "Seed",
"upload": "Upload",
"total": "Total",
"errored": "Errored",
"ratio": "Ratio",
"freeSpace": "Free"
},
"qnap": {
"cpuUsage": "CPU 使用率",
"memUsage": "記憶體使用率",
@@ -775,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "淨值",
@@ -854,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "佇列",
"downloadBytesRemaining": "剩餘大小",
@@ -1253,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
@@ -0,0 +1,60 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { getDockerStats, logger } = vi.hoisted(() => ({
getDockerStats: vi.fn(),
logger: { error: vi.fn() },
}));
vi.mock("utils/docker/stats", () => ({ getDockerStats }));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/stats";
describe("pages/api/docker/stats", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns the stats map for the requested server", async () => {
getDockerStats.mockResolvedValue({ stats: { app: { cpu: 20, mem: 900, rx: 4, tx: 6 } } });
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(getDockerStats).toHaveBeenCalledWith("local");
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: { app: { cpu: 20, mem: 900, rx: 4, tx: 6 } } });
});
it("returns 500 when the stats lookup reports an error", async () => {
getDockerStats.mockResolvedValue({ error: "query failed" });
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("logs and returns 500 when the stats lookup throws", async () => {
getDockerStats.mockRejectedValue(new Error("boom"));
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -1,153 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, logger } = vi.hoisted(() => {
const state = {
docker: null,
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor() {
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/stats/[...service]";
describe("pages/api/docker/stats/[...service]", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.docker = {
listContainers: vi.fn(),
getContainer: vi.fn(),
listTasks: vi.fn(),
};
});
it("returns 400 when container name/server params are missing", async () => {
const req = { query: { service: [] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ error: "docker query parameters are required" });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.docker.listContainers.mockResolvedValue(Buffer.from("bad"));
const req = { query: { service: ["c", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("returns stats for an existing container", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/myapp"], Id: "cid1" }]);
const containerStats = { cpu_stats: { cpu_usage: { total_usage: 1 } } };
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockResolvedValue(containerStats),
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: containerStats });
});
it("uses swarm tasks to locate a container and reports a friendly error when stats cannot be retrieved", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" } } },
{ Status: { ContainerStatus: { ContainerID: "remote1" } } },
]);
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockRejectedValue(new Error("nope")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ error: "Unable to retrieve stats" });
});
it("returns stats for a swarm task container when present locally", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([{ Status: { ContainerStatus: { ContainerID: "local1" } } }]);
const containerStats = { cpu_stats: { cpu_usage: { total_usage: 2 } } };
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockResolvedValue(containerStats),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: containerStats });
});
it("returns 404 when no container or swarm task is found", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([]);
const req = { query: { service: ["missing", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ error: "not found" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -1,211 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, logger } = vi.hoisted(() => {
const state = {
docker: null,
dockerCtorArgs: [],
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor(conn) {
state.dockerCtorArgs.push(conn);
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/status/[...service]";
describe("pages/api/docker/status/[...service]", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerCtorArgs.length = 0;
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.docker = {
listContainers: vi.fn(),
getContainer: vi.fn(),
getService: vi.fn(),
listTasks: vi.fn(),
};
});
it("returns 400 when container name/server params are missing", async () => {
const req = { query: { service: [] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ error: "docker query parameters are required" });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.docker.listContainers.mockResolvedValue(Buffer.from("bad"));
const req = { query: { service: ["c", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("inspects an existing container and returns status + health", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/myapp"], Id: "cid1" }]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ State: { Status: "running", Health: { Status: "healthy" } } }),
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(getDockerArguments).toHaveBeenCalledWith("local");
expect(state.dockerCtorArgs).toHaveLength(1);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running", health: "healthy" });
});
it("returns 404 when container does not exist and swarm is disabled", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
const req = { query: { service: ["missing", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ status: "not found" });
});
it("reports replicated swarm service status based on desired replicas", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Replicated: { Replicas: "2" } } } }),
});
state.docker.listTasks.mockResolvedValue([{ Status: {} }, { Status: {} }]);
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running 2/2" });
});
it("reports partial status for replicated services with fewer running tasks", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Replicated: { Replicas: "3" } } } }),
});
state.docker.listTasks.mockResolvedValue([{ Status: {} }]);
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "partial 1/3" });
});
it("handles global services by inspecting a local task container when possible", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Global: {} } } }),
});
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" }, State: "running" } },
]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ State: { Status: "running", Health: { Status: "unhealthy" } } }),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running", health: "unhealthy" });
});
it("falls back to task status when global service container inspect fails", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Global: {} } } }),
});
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" }, State: "pending" } },
]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockRejectedValue(new Error("nope")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "pending" });
});
it("returns 404 when swarm is enabled but the service does not exist", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockRejectedValue(new Error("not found")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ status: "not found" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -0,0 +1,265 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, containersFromConfig, hasHomepageLabels, getSettings, logger } =
vi.hoisted(() => {
const state = {
docker: null,
containers: [],
health: {},
containers: [],
health: {},
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor() {
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
containersFromConfig: vi.fn(async () => new Set()),
hasHomepageLabels: vi.fn(() => false),
getSettings: vi.fn(() => ({ instanceName: undefined })),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/config/service-helpers", () => ({
containersFromConfig,
hasHomepageLabels,
}));
vi.mock("utils/config/config", () => ({
getSettings,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/statuses";
describe("pages/api/docker/statuses", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.containers = [];
state.health = {};
state.docker = {
listContainers: vi.fn(async (options) => {
const health = options?.filters?.health?.[0];
return health ? (state.health[health] ?? []) : state.containers;
}),
listServices: vi.fn(),
listTasks: vi.fn(),
};
containersFromConfig.mockResolvedValue(new Set());
hasHomepageLabels.mockReturnValue(false);
getSettings.mockReturnValue({ instanceName: undefined });
});
it("returns configured container statuses with health from the docker health filters", async () => {
containersFromConfig.mockResolvedValue(new Set(["glance", "share"]));
state.containers = [
{ Names: ["/glance"], Id: "cid-glance", State: "running" },
{ Names: ["/share"], Id: "cid-share", State: "exited" },
];
state.health = { healthy: [{ Id: "cid-glance" }] };
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(getDockerArguments).toHaveBeenCalledWith("local");
expect(containersFromConfig).toHaveBeenCalledWith("local");
// one list plus one per health state, constant regardless of container count
expect(state.docker.listContainers).toHaveBeenCalledTimes(4);
expect(state.docker.listServices).not.toHaveBeenCalled();
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
statuses: {
glance: { status: "running", health: "healthy" },
share: { status: "exited" },
},
});
});
it("takes health from the filter results rather than the status string", async () => {
containersFromConfig.mockResolvedValue(new Set(["app"]));
state.containers = [{ Names: ["/app"], Id: "cid1", State: "running", Status: "Up 2 hours (healthy)" }];
state.health = { unhealthy: [{ Id: "cid1" }] };
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { app: { status: "running", health: "unhealthy" } } });
});
it("omits health when the docker daemon rejects the health filter", async () => {
containersFromConfig.mockResolvedValue(new Set(["app"]));
state.containers = [{ Names: ["/app"], Id: "cid1", State: "running", Status: "Up 2 hours (healthy)" }];
state.docker.listContainers.mockImplementation(async (options) => {
if (options?.filters?.health) throw new Error("filter unsupported");
return state.containers;
});
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { app: { status: "running" } } });
});
it("omits containers that are neither configured nor labelled for homepage", async () => {
containersFromConfig.mockResolvedValue(new Set(["glance"]));
state.containers = [
{ Names: ["/glance"], State: "running", Status: "Up" },
{ Names: ["/secret-db"], State: "running", Status: "Up" },
];
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { glance: { status: "running" } } });
expect(res.body.statuses["secret-db"]).toBeUndefined();
});
it("includes containers discovered through homepage labels", async () => {
hasHomepageLabels.mockImplementation((labels) => labels?.["homepage.name"] !== undefined);
state.containers = [
{ Names: ["/labelled"], State: "running", Status: "Up", Labels: { "homepage.name": "App" } },
{ Names: ["/unlabelled"], State: "running", Status: "Up", Labels: {} },
];
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { labelled: { status: "running" } } });
});
it("includes swarm services when the server is in swarm mode", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set(["web", "api"]));
state.containers = [{ Names: ["/web"], Id: "cid1", State: "running", Status: "Up" }];
state.docker.listServices.mockResolvedValue([
{ ID: "sid", Spec: { Name: "api", Mode: { Replicated: { Replicas: "2" } } } },
]);
state.docker.listTasks.mockResolvedValue([
{ ServiceID: "sid", Status: {} },
{ ServiceID: "sid", Status: {} },
]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
statuses: {
web: { status: "running" },
api: { status: "running 2/2" },
},
});
});
it("omits swarm services that are neither configured nor labelled", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set());
state.containers = [];
state.docker.listServices.mockResolvedValue([
{ ID: "sid", Spec: { Name: "internal", Mode: { Replicated: { Replicas: "1" } } } },
]);
state.docker.listTasks.mockResolvedValue([{ ServiceID: "sid", Status: {} }]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: {} });
});
it("includes swarm services discovered through homepage labels", async () => {
state.dockerArgs.swarm = true;
hasHomepageLabels.mockImplementation((labels) => labels?.["homepage.name"] !== undefined);
state.containers = [];
state.docker.listServices.mockResolvedValue([
{
ID: "sid",
Spec: { Name: "api", Labels: { "homepage.name": "Api" }, Mode: { Replicated: { Replicas: "1" } } },
},
]);
state.docker.listTasks.mockResolvedValue([{ ServiceID: "sid", Status: {} }]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { api: { status: "running 1/1" } } });
});
it("returns only listed containers when swarm queries fail", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set(["web"]));
state.containers = [{ Names: ["/web"], Id: "cid1", State: "running", Status: "Up" }];
state.docker.listServices.mockRejectedValue(new Error("no services"));
state.docker.listTasks.mockRejectedValue(new Error("no tasks"));
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ statuses: { web: { status: "running" } } });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.containers = Buffer.from("bad");
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -34,7 +34,7 @@ function mockResponse() {
async function loadHandler() {
vi.resetModules();
return (await import("./index")).default;
return (await import("pages/api/mcp/index")).default;
}
describe("pages/api/mcp", () => {
@@ -57,7 +57,7 @@ vi.mock("widgets/widgets", () => ({
},
}));
import handler from "./proxy";
import handler from "pages/api/services/proxy";
function createReq({ method = "GET", body, query = {} } = {}) {
return { method, body, query };
@@ -0,0 +1,118 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { getPrivateWidgetOptions, httpProxy, logger } = vi.hoisted(() => ({
getPrivateWidgetOptions: vi.fn(),
httpProxy: vi.fn(),
logger: { debug: vi.fn() },
}));
vi.mock("utils/config/widget-helpers", () => ({
getPrivateWidgetOptions,
}));
vi.mock("utils/proxy/http", () => ({
httpProxy,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/widgets/customapi";
const json = (body) => Buffer.from(JSON.stringify(body));
async function callRoute(options, index = "0") {
getPrivateWidgetOptions.mockResolvedValueOnce(options);
const res = createMockRes();
await handler({ query: { index } }, res);
return res;
}
describe("pages/api/widgets/customapi", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it.each([
["no widget at that index", undefined],
["a widget without a url", { method: "GET" }],
["an invalid url", { url: "api.local/data" }],
])("returns 400 without calling upstream for %s", async (_, options) => {
const res = await callRoute(options, "3");
expect(getPrivateWidgetOptions).toHaveBeenCalledWith("customapi", "3");
expect(res.statusCode).toBe(400);
expect(httpProxy).not.toHaveBeenCalled();
});
it("proxies an authenticated POST with custom headers and a JSON body", async () => {
httpProxy.mockResolvedValueOnce([200, "application/json", json({ json: { hello: "world" } })]);
const res = await callRoute({
url: "https://httpbin.org/post",
username: "admin",
password: "hunter2",
method: "POST",
headers: { "X-Secret-Token": "super-secret", "Content-Type": "application/json" },
requestBody: { hello: "world" },
});
expect(httpProxy).toHaveBeenCalledWith(new URL("https://httpbin.org/post"), {
method: "POST",
headers: {
"User-Agent": "homepage",
Accept: "application/json",
"X-Secret-Token": "super-secret",
"Content-Type": "application/json",
Authorization: `Basic ${Buffer.from("admin:hunter2").toString("base64")}`,
},
body: '{"hello":"world"}',
});
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ json: { hello: "world" } });
});
it("sends a string body as-is and lets config override default headers", async () => {
httpProxy.mockResolvedValueOnce([200, "application/json", json({ data: { viewer: { login: "shamoon" } } })]);
const query = '{"query":"{ viewer { login } }"}';
const res = await callRoute({
url: "https://api.github.com/graphql",
method: "POST",
headers: { Authorization: "Bearer ghp_token", "User-Agent": "my-dashboard" },
requestBody: query,
});
expect(httpProxy).toHaveBeenCalledWith(expect.any(URL), {
method: "POST",
headers: { "User-Agent": "my-dashboard", Accept: "application/json", Authorization: "Bearer ghp_token" },
body: query,
});
expect(res.body).toEqual({ data: { viewer: { login: "shamoon" } } });
});
it.each([
["an HTTP error", [403, "application/json", json({ message: "API rate limit exceeded for 1.2.3.4" })]],
[
"a network failure",
[500, "application/json", { error: { message: "connect ECONNREFUSED", rawError: { address: "10.0.0.5" } } }],
],
["a non-JSON response", [200, "text/html", Buffer.from("<html><body>Login</body></html>")]],
])("returns only a sanitized error for %s", async (_, upstream) => {
httpProxy.mockResolvedValueOnce(upstream);
const res = await callRoute({ url: "https://api.github.com/repos/gethomepage/homepage?token=abc" });
expect(httpProxy).toHaveBeenCalledWith(expect.any(URL), {
method: "GET",
headers: { "User-Agent": "homepage", Accept: "application/json" },
});
expect(res.statusCode).toBe(upstream[0] === 200 ? 500 : upstream[0]);
expect(res.body.error.url).toBe("api.github.com (see logs for details)");
expect(JSON.stringify(res.body)).not.toMatch(/rate limit|ECONNREFUSED|10\.0\.0\.5|<html>|token=abc/);
expect(res.setHeader).not.toHaveBeenCalled();
});
});
+92 -3
View File
@@ -1,11 +1,15 @@
// @vitest-environment jsdom
import { render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { StrictMode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { getSettingsMock, authOptionsMock } = vi.hoisted(() => ({
const { getSettingsMock, authOptionsMock, signInMock, replaceMock, routerQuery } = vi.hoisted(() => ({
getSettingsMock: vi.fn(),
authOptionsMock: vi.fn(),
signInMock: vi.fn(),
replaceMock: vi.fn(),
routerQuery: {},
}));
vi.mock("utils/config/config", () => ({
@@ -20,13 +24,25 @@ vi.mock("pages/api/auth/[...nextauth]", () => ({
vi.mock("next/router", () => ({
useRouter: () => ({
query: {},
query: routerQuery,
replace: replaceMock,
}),
}));
vi.mock("next-auth/react", () => ({ signIn: signInMock }));
import SignInPage, { getServerSideProps } from "pages/auth/signin";
const OIDC_PROVIDERS = { "homepage-oidc": { id: "homepage-oidc", name: "Homepage OIDC", type: "oauth" } };
const SETTINGS = { theme: "dark", color: "slate", title: "Homepage" };
describe("pages/auth/signin", () => {
beforeEach(() => {
vi.clearAllMocks();
Object.keys(routerQuery).forEach((key) => delete routerQuery[key]);
window.sessionStorage.clear();
});
it("renders an error state when no providers are configured", async () => {
render(
<SignInPage
@@ -66,6 +82,67 @@ describe("pages/auth/signin", () => {
expect(screen.getByRole("button", { name: /login via oidc/i })).toBeInTheDocument();
});
it("redirects to the provider when auto-login is enabled", () => {
routerQuery.callbackUrl = "/some/page";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).toHaveBeenCalledWith("homepage-oidc", { callbackUrl: "/some/page" });
expect(screen.getByText(/redirecting to homepage oidc/i)).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /login via/i })).not.toBeInTheDocument();
});
it("does not auto-login when the provider returned an error", () => {
routerQuery.error = "OAuthCallback";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).not.toHaveBeenCalled();
expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
});
it("does not auto-login when it is explicitly disabled in the url", () => {
routerQuery.autologin = "0";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).not.toHaveBeenCalled();
expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
});
it("redirects once under strict mode, rather than tripping its own loop guard", () => {
render(
<StrictMode>
<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />
</StrictMode>,
);
expect(signInMock).toHaveBeenCalledTimes(1);
expect(replaceMock).not.toHaveBeenCalled();
});
it("stops auto-login and hands back the page when the session never sticks", () => {
routerQuery.callbackUrl = "/some/page";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).toHaveBeenCalledTimes(1);
expect(replaceMock).toHaveBeenCalledWith("/auth/signin?autologin=0&callbackUrl=%2Fsome%2Fpage");
});
it("does not auto-login the password provider", () => {
render(
<SignInPage
providers={{ credentials: { id: "credentials", name: "Password", type: "credentials" } }}
settings={SETTINGS}
autoLogin
/>,
);
expect(signInMock).not.toHaveBeenCalled();
});
it("getServerSideProps returns providers and only public sign-in settings", async () => {
authOptionsMock.mockReturnValueOnce({ providers: [{ id: "foo", name: "Foo", type: "oauth" }] });
getSettingsMock.mockReturnValueOnce({
@@ -88,6 +165,7 @@ describe("pages/auth/signin", () => {
expect(getSettingsMock).toHaveBeenCalled();
expect(res).toEqual({
props: {
autoLogin: false,
providers: { foo: { id: "foo", name: "Foo", type: "oauth" } },
settings: {
theme: "dark",
@@ -102,6 +180,17 @@ describe("pages/auth/signin", () => {
expect(res.props.settings).not.toHaveProperty("layout");
});
it("getServerSideProps enables auto-login from the environment", async () => {
authOptionsMock.mockReturnValueOnce({ providers: [] });
getSettingsMock.mockReturnValueOnce({ theme: "dark" });
vi.stubEnv("HOMEPAGE_OIDC_AUTO_LOGIN", "true");
const res = await getServerSideProps({});
expect(res.props.autoLogin).toBe(true);
vi.unstubAllEnvs();
});
it("getServerSideProps falls back to no providers when auth options fail to load", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
authOptionsMock.mockImplementationOnce(() => {
@@ -0,0 +1,19 @@
import { readdirSync } from "fs";
import { expect, it } from "vitest";
// Next compiles everything under src/pages into routes, so a colocated test ships
// to users and drags its dev dependencies into the runtime image.
function filesIn(dir, found = []) {
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
const path = `${dir}/${entry.name}`;
if (entry.isDirectory()) filesIn(path, found);
else found.push(path);
});
return found;
}
it("keeps test files out of src/pages", () => {
const tests = filesIn("src/pages").filter((path) => /\.(test|spec)\.[jt]sx?$/.test(path));
expect(tests).toEqual([]);
});
+6 -1
View File
@@ -7,6 +7,10 @@ import { SettingsContext } from "utils/contexts/settings";
export default function Item({ bookmark, iconOnly = false }) {
const description = bookmark.description ?? new URL(bookmark.href).hostname;
const { settings } = useContext(SettingsContext);
const cardStyle =
settings.cardStyle === "hairline"
? "shadow-xs inset-ring inset-ring-black/5 dark:inset-ring-white/5 hover:inset-ring-black/10 dark:hover:inset-ring-white/10"
: "shadow-md shadow-theme-900/10 dark:shadow-theme-900/20";
return (
<li
@@ -22,7 +26,8 @@ export default function Item({ bookmark, iconOnly = false }) {
target={bookmark.target ?? settings.target ?? "_blank"}
className={classNames(
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
"text-left cursor-pointer transition-all rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10",
cardStyle,
"text-left cursor-pointer transition-all rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10",
iconOnly ? "h-[60px] w-[60px] grid" : "block w-full mb-3",
)}
>
+10
View File
@@ -21,6 +21,16 @@ describe("components/bookmarks/item", () => {
expect(screen.getByRole("link").getAttribute("target")).toBe("_self");
});
it("applies hairline card styles when cardStyle is set", () => {
renderWithProviders(<Item bookmark={{ name: "A", href: "http://example.com/x", abbr: "A" }} iconOnly={false} />, {
settings: { cardStyle: "hairline" },
});
const className = screen.getByRole("link").className;
expect(className).toContain("inset-ring");
expect(className).not.toContain("shadow-md");
});
it("renders icon-only layout with icon when provided", () => {
renderWithProviders(
<Item bookmark={{ name: "A", href: "http://example.com/x", abbr: "A", icon: "mdi-home" }} iconOnly />,
+4 -3
View File
@@ -124,8 +124,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
? MOBILE_BUTTON_POSITIONS[settings.quicklaunch.mobileButtonPosition]
: null;
function openCurrentItem(newWindow) {
const result = results[activeItemIndex];
function openCurrentItem(newWindow, index = activeItemIndex) {
const result = results[index];
window.open(
result.href,
newWindow ? "_blank" : (result.target ?? searchProvider?.target ?? settings.target ?? "_blank"),
@@ -180,7 +180,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
function handleItemClick(event) {
closeAndReset();
openCurrentItem(event.metaKey);
// in case hover doesnt fire, use the clicked item, not the highlighted one
openCurrentItem(event.metaKey, parseInt(event.currentTarget.dataset.index, 10));
}
function handleItemKeyDown(event) {
+31
View File
@@ -263,6 +263,37 @@ describe("components/quicklaunch", () => {
openSpy.mockRestore();
});
it("opens the clicked result even without a preceding hover event", async () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
renderWithProviders(
<Wrapper
servicesAndBookmarks={[
{ name: "Alpha", href: "https://alpha.example" },
{ name: "Alpine", href: "https://alpine.example" },
{ name: "Almond", href: "https://almond.example" },
]}
/>,
{ settings: { target: "_self", quicklaunch: { showSearchSuggestions: false } } },
);
const input = screen.getByPlaceholderText("Search");
await waitFor(() => expect(input).toHaveFocus());
fireEvent.change(input, { target: { value: "al" } });
await waitFor(() => expect(document.querySelector('button[data-index="2"]')).toBeTruthy());
// touch devices don't fire mouseEnter, so the highlighted item is still the first one
fireEvent.click(document.querySelector('button[data-index="2"]'));
await act(async () => {
await new Promise((r) => setTimeout(r, 350));
});
expect(openSpy).toHaveBeenCalledWith("https://almond.example", "_self", "noreferrer");
openSpy.mockRestore();
});
it("handles Escape on a result button (not just the input)", async () => {
renderWithProviders(<Wrapper servicesAndBookmarks={[{ name: "Alpha", href: "https://alpha.example" }]} />, {
settings: { quicklaunch: { showSearchSuggestions: false } },
+6 -1
View File
@@ -19,6 +19,10 @@ export default function Item({ service, groupName, useEqualHeights }) {
const { settings } = useContext(SettingsContext);
const showStats = service.showStats === false ? false : settings.showStats;
const statusStyle = service.statusStyle !== undefined ? service.statusStyle : settings.statusStyle;
const cardStyle =
settings.cardStyle === "hairline"
? "shadow-xs inset-ring inset-ring-black/5 dark:inset-ring-white/5 hover:inset-ring-black/10 dark:hover:inset-ring-white/10"
: "shadow-md shadow-theme-900/10 dark:shadow-theme-900/20";
const [statsOpen, setStatsOpen] = useState(service.showStats);
const [statsClosing, setStatsClosing] = useState(false);
@@ -39,7 +43,8 @@ export default function Item({ service, groupName, useEqualHeights }) {
className={classNames(
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
useEqualHeights && "h-[calc(100%-0.5rem)]",
"transition-all mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card",
cardStyle,
"transition-all mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card",
)}
>
<div className="flex select-none z-0 service-title">
+40 -24
View File
@@ -1,45 +1,61 @@
import { useTranslation } from "next-i18next/pages";
import useSWR from "swr";
const WARNING_CLASS = "text-orange-400/50 dark:text-orange-400/80";
// docker container states other than running
const STATE_LABELS = {
created: "docker.created",
dead: "docker.dead",
exited: "docker.exited",
"not found": "docker.not_found",
paused: "docker.paused",
removing: "docker.removing",
restarting: "docker.restarting",
};
// states that mean something is wrong rather than merely not started
const WARNING_STATES = new Set(["dead", "exited", "not found", "restarting"]);
export default function Status({ service, style }) {
const { t } = useTranslation();
const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
const { data: response, error } = useSWR(`/api/docker/statuses?server=${encodeURIComponent(service.server || "")}`);
const statusError = error ?? response?.error;
const { statuses } = response ?? {};
const data = statuses ? (statuses[service.container] ?? { status: "not found" }) : undefined;
let statusLabel = t("docker.unknown");
let backgroundClass = "px-1.5 py-0.5 bg-theme-500/10 dark:bg-theme-900/50";
let colorClass = "text-black/20 dark:text-white/40 ";
if (error) {
if (statusError) {
statusLabel = t("docker.error");
colorClass = "text-rose-500/80";
} else if (data) {
if (data.status?.includes("running")) {
colorClass = "text-emerald-500/80";
} else if (data?.status?.includes("running")) {
colorClass = "text-emerald-500/80";
if (!data.health) {
statusLabel = data.status.replace("running", t("docker.running"));
} else {
statusLabel = data.health === "healthy" ? t("docker.healthy") : data.health;
if (!data.health) {
statusLabel = data.status.replace("running", t("docker.running"));
} else {
statusLabel = data.health === "healthy" ? t("docker.healthy") : data.health;
if (data.health === "starting") {
statusLabel = t("docker.starting");
colorClass = "text-blue-500/80";
}
if (data.health === "starting") {
statusLabel = t("docker.starting");
colorClass = "text-blue-500/80";
}
if (data.health === "unhealthy") {
statusLabel = t("docker.unhealthy");
colorClass = "text-orange-400/50 dark:text-orange-400/80";
}
if (data.health === "unhealthy") {
statusLabel = t("docker.unhealthy");
colorClass = WARNING_CLASS;
}
}
if (data.status === "not found" || data.status === "exited" || data.status?.startsWith("partial")) {
if (data.status === "not found") statusLabel = t("docker.not_found");
else if (data.status === "exited") statusLabel = t("docker.exited");
else statusLabel = data.status.replace("partial", t("docker.partial"));
colorClass = "text-orange-400/50 dark:text-orange-400/80";
}
} else if (data?.status?.startsWith("partial")) {
statusLabel = data.status.replace("partial", t("docker.partial"));
colorClass = WARNING_CLASS;
} else if (data && STATE_LABELS[data.status]) {
statusLabel = t(STATE_LABELS[data.status]);
if (WARNING_STATES.has(data.status)) colorClass = WARNING_CLASS;
}
if (style === "dot") {
+60 -8
View File
@@ -21,8 +21,11 @@ describe("components/services/status", () => {
render(<Status service={{ container: "c", server: "s" }} />);
expect(useSWR).toHaveBeenCalledWith("/api/docker/status/c/s");
expect(useSWR).toHaveBeenCalledWith("/api/docker/statuses?server=s");
expect(screen.getByText("docker.unknown")).toBeInTheDocument();
render(<Status service={{ container: "c" }} />);
expect(useSWR).toHaveBeenCalledWith("/api/docker/statuses?server=");
});
it("renders error when SWR fails", () => {
@@ -33,38 +36,87 @@ describe("components/services/status", () => {
expect(screen.getByText("docker.error")).toBeInTheDocument();
});
it("renders error when the api returns an error payload with a 200-parsed body", () => {
useSWR.mockReturnValue({ data: { error: "query failed" }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.error")).toBeInTheDocument();
expect(screen.queryByText("docker.not_found")).not.toBeInTheDocument();
});
it("renders healthy/unhealthy and partial/exited/not found statuses", () => {
useSWR.mockReturnValue({ data: { status: "running", health: "healthy" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "healthy" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.healthy")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "running", health: "unhealthy" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "unhealthy" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.unhealthy")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "partial 1/2" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "partial 1/2" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.partial 1/2")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "exited" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "exited" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.exited")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "not found" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: {} }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.not_found")).toBeInTheDocument();
});
it("renders starting health when container is running and starting", () => {
useSWR.mockReturnValue({ data: { status: "running", health: "starting" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "starting" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.starting")).toBeInTheDocument();
});
it("renders the remaining container states instead of falling through to unknown", () => {
const states = {
created: "docker.created",
paused: "docker.paused",
restarting: "docker.restarting",
removing: "docker.removing",
dead: "docker.dead",
};
Object.entries(states).forEach(([status, label]) => {
useSWR.mockReturnValue({ data: { statuses: { c: { status } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText(label)).toBeInTheDocument();
});
expect(screen.queryByText("docker.unknown")).not.toBeInTheDocument();
});
it("colors problem states as warnings and merely stopped states neutrally", () => {
useSWR.mockReturnValue({ data: { statuses: { c: { status: "restarting" } } }, error: undefined });
const { container: restarting } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
expect(restarting.querySelector(".bg-orange-400")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { statuses: { c: { status: "paused" } } }, error: undefined });
const { container: paused } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
expect(paused.querySelector(".bg-orange-400")).not.toBeInTheDocument();
});
it("does not surface health for containers that are not running", () => {
useSWR.mockReturnValue({
data: { statuses: { c: { status: "paused", health: "unhealthy" } } },
error: undefined,
});
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.paused")).toBeInTheDocument();
expect(screen.queryByText("docker.unhealthy")).not.toBeInTheDocument();
});
it("renders a dot when style is dot", () => {
useSWR.mockReturnValue({ data: { status: "running" }, error: undefined });
useSWR.mockReturnValue({ data: { statuses: { c: { status: "running" } } }, error: undefined });
const { container } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
+16 -2
View File
@@ -4,11 +4,14 @@ import { useContext, useMemo } from "react";
import { BlockHighlightContext } from "./highlight-context";
import { SettingsContext } from "utils/contexts/settings";
import { evaluateHighlight, getHighlightClass } from "utils/highlights";
export default function Block({ value, highlightValue, label, field }) {
const { t } = useTranslation();
const highlightConfig = useContext(BlockHighlightContext);
const { settings } = useContext(SettingsContext);
const refined = settings?.blockStyle === "refined";
const highlight = useMemo(() => {
if (!highlightConfig) return null;
@@ -45,9 +48,20 @@ export default function Block({ value, highlightValue, label, field }) {
data-highlight-level={highlight?.level}
data-highlight-source={highlight?.source}
>
<div className="font-thin text-sm">{value === undefined || value === null ? "-" : value}</div>
<div
className={classNames("font-bold text-xs uppercase", applyToValueOnly && "text-theme-700 dark:text-theme-200")}
className={classNames(
refined ? "text-[13px] font-medium tabular-nums" : "font-thin text-sm",
"service-block-value",
)}
>
{value === undefined || value === null ? "-" : value}
</div>
<div
className={classNames(
refined ? "text-[10px] font-semibold uppercase tracking-wider opacity-60" : "font-bold text-xs uppercase",
applyToValueOnly && "text-theme-700 dark:text-theme-200",
"service-block-label",
)}
>
{t(label)}
</div>
@@ -39,6 +39,24 @@ describe("components/services/widget/block", () => {
expect(el.className).toContain("danger-class");
});
it("uses the standard block styles by default", () => {
const { container } = renderWithProviders(<Block label="some.label" value="1" />, { settings: {} });
expect(container.querySelector(".service-block-value").className).toContain("font-thin");
expect(container.querySelector(".service-block-label").className).toContain("font-bold");
});
it("applies refined block styles when blockStyle is set", () => {
const { container } = renderWithProviders(<Block label="some.label" value="1" />, {
settings: { blockStyle: "refined" },
});
const value = container.querySelector(".service-block-value");
expect(value.className).toContain("tabular-nums");
expect(value.className).not.toContain("font-thin");
expect(container.querySelector(".service-block-label").className).toContain("tracking-wider");
});
it("prefers highlightValue over the rendered value for numeric highlighting", () => {
const highlightConfig = {
levels: { warn: "warn-class" },
+5 -1
View File
@@ -12,7 +12,11 @@ export default function SignOut() {
return (
<div id="signout" className="rounded-full flex align-middle self-center mr-3">
<button type="button" onClick={() => signOut({ callbackUrl: "/" })} className="outline-hidden">
<button
type="button"
onClick={() => signOut({ callbackUrl: "/auth/signin?autologin=0" })}
className="outline-hidden"
>
<MdLogout className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" aria-hidden="true" />
<span className="sr-only">{t("auth.signout")}</span>
</button>
+2 -1
View File
@@ -32,6 +32,7 @@ describe("components/toggles/signout", () => {
const { getByRole } = render(<SignOut />);
fireEvent.click(getByRole("button"));
expect(signOut).toHaveBeenCalledWith({ callbackUrl: "/" });
// Not "/", which would bounce straight back into the provider when auto-login is on
expect(signOut).toHaveBeenCalledWith({ callbackUrl: "/auth/signin?autologin=0" });
});
});
@@ -0,0 +1,53 @@
import classNames from "classnames";
import { useTranslation } from "next-i18next/pages";
import { TbApi } from "react-icons/tb";
import useSWR from "swr";
import Container from "../widget/container";
import Error from "../widget/error";
import Raw from "../widget/raw";
import WidgetLabel from "../widget/widget_label";
import ResolvedIcon from "components/resolvedicon";
import { formatValue, getValue } from "widgets/customapi/utils";
export default function CustomApi({ options }) {
const { t } = useTranslation();
const { index, icon, label, mappings = [], refreshInterval = 10000 } = options;
const { data, error } = useSWR(`/api/widgets/customapi?${new URLSearchParams({ index }).toString()}`, {
refreshInterval: Math.max(1000, refreshInterval),
});
if (error || (data?.error && !mappings.some((mapping) => mapping.field === "error"))) {
return <Error options={options} />;
}
return (
<Container options={options} additionalClassNames="information-widget-customapi">
<Raw>
<div className="flex flex-row items-center">
<div className="flex-none mr-2">
{icon ? (
<ResolvedIcon icon={icon} width={24} height={24} />
) : (
<TbApi className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className={classNames("flex flex-wrap items-center gap-0.5", !data && "animate-pulse")}>
{mappings.map((mapping) => (
<div key={mapping.label} className="flex flex-col items-center justify-center px-1 text-xs">
<span className="text-theme-800 dark:text-theme-200">{mapping.label}</span>
<span className="text-theme-800/70 dark:text-theme-200/50">
{data ? formatValue(t, mapping, getValue(mapping.field, data)) : "-"}
</span>
</div>
))}
</div>
</div>
{label && <WidgetLabel label={label} />}
</Raw>
</Container>
);
}
@@ -0,0 +1,117 @@
// @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";
const { useSWR } = vi.hoisted(() => ({ useSWR: vi.fn() }));
vi.mock("swr", () => ({ default: useSWR }));
vi.mock("components/resolvedicon", () => ({
default: ({ icon }) => <div data-testid="resolved-icon" data-icon={icon} />,
}));
import CustomApi from "./customapi";
const settings = { settings: { target: "_self" } };
describe("components/widgets/customapi", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("shows GitHub repo stats once loaded, with the fallback icon and a label", () => {
const options = {
index: 6,
label: "Homepage",
refreshInterval: 300000,
mappings: [
{ field: "stargazers_count", label: "Stars", format: "number" },
{ field: "open_issues_count", label: "Open", format: "number" },
{ field: "forks_count", label: "Forks", format: "number" },
],
};
useSWR.mockReturnValue({ data: undefined, error: undefined });
const { container, rerender } = renderWithProviders(<CustomApi options={options} />, settings);
expect(useSWR).toHaveBeenCalledWith("/api/widgets/customapi?index=6", { refreshInterval: 300000 });
expect(screen.getAllByText("-")).toHaveLength(3);
expect(screen.queryByTestId("resolved-icon")).toBeNull();
expect(container.querySelector("svg")).not.toBeNull();
useSWR.mockReturnValue({
data: { stargazers_count: 32809, open_issues_count: 1, forks_count: 2127, owner: { login: "gethomepage" } },
error: undefined,
});
rerender(<CustomApi options={options} />);
expect(screen.queryByText("-")).toBeNull();
expect(screen.getByText("Stars").nextSibling).toHaveTextContent("32809");
expect(screen.getByText("Open").nextSibling).toHaveTextContent("1");
expect(screen.getByText("Forks").nextSibling).toHaveTextContent("2127");
expect(screen.getByText("Homepage")).toHaveClass("information-widget-label");
});
it("shows a nested weather value with a configured icon and suffix", () => {
useSWR.mockReturnValue({
data: { current: { time: "2026-09-22T20:15", temperature_2m: 7.8, wind_speed_10m: 12.4 } },
error: undefined,
});
renderWithProviders(
<CustomApi
options={{
index: 7,
icon: "mdi-thermometer",
mappings: [{ field: "current.temperature_2m", label: "Temp", format: "float", suffix: "°C" }],
}}
/>,
settings,
);
expect(useSWR).toHaveBeenCalledWith("/api/widgets/customapi?index=7", { refreshInterval: 10000 });
expect(screen.getByTestId("resolved-icon")).toHaveAttribute("data-icon", "mdi-thermometer");
expect(screen.getByText("7.8 °C")).toBeInTheDocument();
});
it("shows a status API's error field as data when it is mapped", () => {
useSWR.mockReturnValue({ data: { error: "E_NONE", pending: 3 }, error: undefined });
renderWithProviders(
<CustomApi
options={{
index: 0,
mappings: [
{ field: "error", label: "Status", remap: [{ value: "E_NONE", to: "OK" }] },
{ field: "pending", label: "Queue", format: "number" },
],
}}
/>,
settings,
);
expect(screen.queryByText("widget.api_error")).toBeNull();
expect(screen.getByText("OK")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
});
it("shows the error state when the upstream API fails, clamping a too-fast refresh", () => {
useSWR.mockReturnValue({
data: { error: { message: "HTTP Error", url: "api.github.com (see logs for details)" } },
error: undefined,
});
renderWithProviders(
<CustomApi
options={{ index: 6, refreshInterval: 500, mappings: [{ field: "stargazers_count", label: "Stars" }] }}
/>,
settings,
);
expect(useSWR).toHaveBeenCalledWith("/api/widgets/customapi?index=6", { refreshInterval: 1000 });
expect(screen.getByText("widget.api_error")).toBeInTheDocument();
expect(screen.queryByText("Stars")).toBeNull();
});
});
+2
View File
@@ -139,6 +139,8 @@ export default function Search({ options }) {
}, [selectedProvider, options, query, searchSuggestions]);
function doSearch(value) {
if (!value) return;
const q = encodeURIComponent(value);
const { url } = selectedProvider;
if (url) {
+28 -1
View File
@@ -24,7 +24,19 @@ vi.mock("@headlessui/react", async () => {
<div>{children}</div>
</ComboboxContext.Provider>
),
ComboboxInput: (props) => <input {...props} />,
ComboboxInput: (props) => {
const ctx = useContext(ComboboxContext);
// real headlessui fires onChange(null) when the input is cleared
return (
<input
{...props}
onChange={(event) => {
props.onChange?.(event);
if (event.target.value === "") ctx?.onChange?.(null);
}}
/>
);
},
ComboboxOption: ({ as: As = "div", value, children, ...props }) => {
const ctx = useContext(ComboboxContext);
const content = typeof children === "function" ? children({ active: false }) : children;
@@ -83,6 +95,21 @@ describe("components/widgets/search", () => {
openSpy.mockRestore();
});
it("does not search when the input is cleared", () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
renderWithProviders(<Search options={{ provider: ["google"], showSearchSuggestions: false, target: "_self" }} />, {
settings: {},
});
const input = screen.getByPlaceholderText("search.placeholder");
fireEvent.change(input, { target: { value: "a" } });
fireEvent.change(input, { target: { value: "" } });
expect(openSpy).not.toHaveBeenCalled();
openSpy.mockRestore();
});
it("accepts provider configured as a string", () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
+1
View File
@@ -16,6 +16,7 @@ const widgetMappings = {
longhorn: dynamic(() => import("components/widgets/longhorn/longhorn")),
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
stocks: dynamic(() => import("components/widgets/stocks/stocks")),
customapi: dynamic(() => import("components/widgets/customapi/customapi")),
};
export default function Widget({ widget, style }) {

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