Compare commits

...
54 Commits
Author SHA1 Message Date
shamoon 51cc1684f9 2.6.0
Docs / Test Build Docs (push) Waiting to run
Docs / Build & Deploy Docs (push) Waiting to run
Docker CI / Docker Build & Push (push) Waiting to run
Lint / Linting Checks (push) Waiting to run
Release Drafter / Update Release Draft (push) Waiting to run
Release Drafter / Auto Label PR (push) Waiting to run
Tests / vitest (1) (push) Waiting to run
Tests / vitest (2) (push) Waiting to run
Tests / vitest (3) (push) Waiting to run
Tests / vitest (4) (push) Waiting to run
2026-10-16 22:26:12 -07:00
github-actions[bot]andCrowdin Bot 8a97e38393 New Crowdin translations by GitHub Action (#7241)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-10-16 22:25:18 -07:00
shamoon 9628256887 Refactor: auth architecture (#7256) 2026-10-16 22:09:09 -07:00
dependabot[bot] 89b0d20655 Chore(deps): Bump release-drafter/release-drafter/autolabeler from 7.8.0 to 7.10.0 (#7248)
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-15 18:39:43 +00:00
dependabot[bot] d20a5c5322 Chore(deps): Bump release-drafter/release-drafter from 7.8.0 to 7.10.0 (#7250)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-15 18:32:18 +00:00
dependabot[bot] 84721f2769 Chore(deps): Bump actions/setup-node from 7.0.0 to 7.1.0 (#7249)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-15 11:25:12 -07:00
Mustafa 11ca29104a Chore: send Gitea/Forgejo API key as Authorization header (#7245)
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-14 16:29:58 -07: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
195 changed files with 5393 additions and 1296 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@71fdb8814261dd703be4ca7c6450d21b1868da4b # v3.0.1
uses: crowdin/github-action@9c23991700c0ec5256fd41089b9d9d7d540e424e # v3.3.0
with:
upload_translations: false
download_translations: true
+5 -5
View File
@@ -62,13 +62,13 @@ 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
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: 24
cache: 'pnpm'
@@ -99,14 +99,14 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup QEMU
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.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' }}
+2 -2
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,7 +45,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: Build Docs
run: uv run --frozen zensical build --clean
+2 -2
View File
@@ -26,13 +26,13 @@ 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
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: 24
cache: 'pnpm'
+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@978bb0b30c5ee8cdd332ced253758157150d43f0 # 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@978bb0b30c5ee8cdd332ced253758157150d43f0 # 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@978bb0b30c5ee8cdd332ced253758157150d43f0
with:
config-name: release-drafter.yml
env:
+3 -3
View File
@@ -18,11 +18,11 @@ 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
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: 24
cache: pnpm
@@ -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
+2 -2
View File
@@ -6,7 +6,7 @@
</p>
<p align="center">
A modern, <em>fully static, fast</em>, secure <em>fully proxied</em>, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages. Easily configured via YAML files or through docker label discovery.
A modern, <em>fast</em>, secure <em>fully proxied</em>, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages. Easily configured via YAML files or through docker label discovery.
</p>
<p align="center">
@@ -31,7 +31,7 @@
With features like quick search, bookmarks, weather support, a wide range of integrations and widgets, an elegant and modern design, and a focus on performance, Homepage is your ideal start to the day and a handy companion throughout it.
- **Fast** - The site is statically generated at build time for instant load times.
- **Fast** - Cached dashboard data and optimized assets keep page loads quick.
- **Secure** - All API requests to backend services are proxied, keeping your API keys hidden. Constantly reviewed for security by the community.
- **For Everyone** - Images built for AMD64, ARM64.
- **Full i18n** - Support for over 40 languages.
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

+62 -2
View File
@@ -3,7 +3,7 @@ title: Settings
description: Service Configuration
---
The `settings.yaml` file allows you to define application level options. For changes made to this file to take effect, you will need to regenerate the static HTML, this can be done by clicking the refresh icon in the bottom right of the page.
The `settings.yaml` file allows you to define application level options. After making changes, click the refresh icon in the bottom right of the page to clear the cached dashboard data and reload the page.
## Title
@@ -33,7 +33,7 @@ startUrl: https://custom.url
!!! warning "Heads Up!"
You will need to restart the container any time you add new images, this is a limitation of the Next.js static site server.
You will need to restart the container any time you add new local images so the server can discover them.
!!! warning "Heads Up!"
@@ -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`:
+2 -2
View File
@@ -1,6 +1,6 @@
---
title: Home
description: A modern, fully static, fast, secure, fully proxied, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages.
description: A modern, fast, secure, fully proxied, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages.
icon: material/home
hide:
- navigation
@@ -18,7 +18,7 @@ hide:
<img src="assets/homepage_demo_clip.webp" alt="homepage" style="max-width: 100%; margin: 0 auto; display: block;" />
<p style="margin: 0 0 30px;">A modern, <em>fully static, fast</em>, secure <em>fully proxied</em>, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages. Easily configured via YAML files or through docker label discovery.</p>
<p style="margin: 0 0 30px;">A modern, <em>fast</em>, secure <em>fully proxied</em>, highly customizable application dashboard with integrations for over 100 services and translations into multiple languages. Easily configured via YAML files or through docker label discovery.</p>
</div>
+1 -1
View File
@@ -30,6 +30,6 @@ Finally, run the server:
HOMEPAGE_ALLOWED_HOSTS=gethomepage.dev:1234 pnpm start
```
When updating homepage versions you will need to re-build the static files i.e. repeat the process above.
When updating Homepage versions, rebuild the production bundle by repeating the steps above.
See [HOMEPAGE_ALLOWED_HOSTS](index.md#homepage_allowed_hosts) for more information on this environment variable.
+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)
+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.
+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.4.0",
"version": "2.6.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.2",
"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
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Tou",
"processing": "Verwerking",
"processed": "Verwerk",
"time": "Tyd"
"time": "Tyd",
"failed": "Failed"
},
"firefly": {
"networth": "Netto Waarde",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Gesoek",
"queued": "In ry",
"leagues": "Ligas"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "استخدام الذاكرة العشوائية",
@@ -780,7 +790,8 @@
"queue": "الطابور",
"processing": "قيد المعالجة",
"processed": "مُعَالَج",
"time": "الوقت"
"time": "الوقت",
"failed": "Failed"
},
"firefly": {
"networth": "صافي القيمة",
@@ -859,6 +870,14 @@
"reading": "أقرأُه",
"finished": "منتهي"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "الطابور",
"downloadBytesRemaining": "متبقِ",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "Упортеба памет",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Време"
"time": "Време",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Cua",
"processing": "Processant",
"processed": "Processat",
"time": "Temps"
"time": "Temps",
"failed": "Error"
},
"firefly": {
"networth": "Valor Net",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Pendents",
"queued": "En cua",
"leagues": "Lligues"
},
"feed": {
"noItems": "Sense elements"
}
}
+30 -8
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",
@@ -100,11 +100,11 @@
"not_found": "Nenalezen",
"exited": "Ukončen",
"partial": "Částečný",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
"created": "Vytvořeno",
"paused": "Pozastaveno",
"restarting": "Restartuji",
"removing": "Odebírání",
"dead": "Mrtvý"
},
"ping": {
"error": "Chyba",
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Fronta",
"processing": "Zpracovává se",
"processed": "Zpracováno",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Čisté jmění",
@@ -859,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á",
@@ -1258,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
+34 -12
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Warteschlange",
"processing": "Wird verarbeitet",
"processed": "Wird verarbeitet",
"time": "Zeit"
"time": "Zeit",
"failed": "Failed"
},
"firefly": {
"networth": "Reinvermögen",
@@ -859,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",
@@ -980,11 +999,11 @@
"message": "Nachricht",
"tags": "Schlagwörter",
"none": "Keine",
"min": "Min",
"low": "Low",
"default": "Default",
"high": "High",
"urgent": "Urgent"
"min": "Minimal",
"low": "Niedrig",
"default": "Standard",
"high": "Hoch",
"urgent": "Dringend"
},
"plantit": {
"events": "Ereignisse",
@@ -1098,8 +1117,8 @@
"beszel": {
"name": "Name",
"systems": "Systeme",
"up": "Up",
"down": "Down",
"up": "Online",
"down": "Offline",
"paused": "Pausiert",
"pending": "Wartend",
"status": "Status",
@@ -1249,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"
}
}
+23 -1
View File
@@ -256,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": "Χρήση μνήμης",
@@ -780,7 +790,8 @@
"queue": "Αναμονή",
"processing": "Σε επεξεργασία",
"processed": "Επεξεργασμένα",
"time": "Ώρα"
"time": "Ώρα",
"failed": "Failed"
},
"firefly": {
"networth": "Καθαρή αξία",
@@ -859,6 +870,14 @@
"reading": "Ανάγνωση",
"finished": "Ολοκληρώθηκε"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Αναμονή",
"downloadBytesRemaining": "Απομένουν",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Cola",
"processing": "Procesando",
"processed": "Procesado",
"time": "Tiempo"
"time": "Tiempo",
"failed": "Fallido"
},
"firefly": {
"networth": "Patrimonio neto",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Buscado",
"queued": "En cola",
"leagues": "Ligas"
},
"feed": {
"noItems": "No hay elementos"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "File d'attente",
"processing": "En traitement",
"processed": "Traité",
"time": "Temps"
"time": "Temps",
"failed": "Failed"
},
"firefly": {
"networth": "Valeur Nette",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Recherché",
"queued": "En file d'attente",
"leagues": "Ligues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "שימוש בזיכרון",
@@ -780,7 +790,8 @@
"queue": "תור",
"processing": "בעיבוד",
"processed": "עובדו",
"time": "זמן"
"time": "זמן",
"failed": "Failed"
},
"firefly": {
"networth": "שווי נקי",
@@ -859,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "תור",
"downloadBytesRemaining": "נותר",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Red čekanja",
"processing": "Obrada",
"processed": "Obrađeno",
"time": "Vrijeme"
"time": "Vrijeme",
"failed": "Failed"
},
"firefly": {
"networth": "Neto vrijednost",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Željeno",
"queued": "U redu čekanja",
"leagues": "Lige"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Sorban",
"processing": "Feldolgozás alatt",
"processed": "Feldolgozott",
"time": "Idő"
"time": "Idő",
"failed": "Failed"
},
"firefly": {
"networth": "Nettó érték",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Waktu"
"time": "Waktu",
"failed": "Failed"
},
"firefly": {
"networth": "Kekayaan Bersih",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Tempo"
"time": "Tempo",
"failed": "Failed"
},
"firefly": {
"networth": "Valore Netto",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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使用量",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "募集中",
"queued": "処理待ち",
"leagues": "リーグ"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "메모리 사용량",
@@ -780,7 +790,8 @@
"queue": "대기열",
"processing": "처리 중",
"processed": "처리됨",
"time": "시간"
"time": "시간",
"failed": "Failed"
},
"firefly": {
"networth": "순자산",
@@ -859,6 +870,14 @@
"reading": "읽는 중",
"finished": "완료"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "대기열",
"downloadBytesRemaining": "남음",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Wachtrij",
"processing": "Verwerken",
"processed": "Verwerkt",
"time": "Tijd"
"time": "Tijd",
"failed": "Failed"
},
"firefly": {
"networth": "Totaal eigen vermogen",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Gewenst",
"queued": "In de wachtrij",
"leagues": "Competities"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+28 -6
View File
@@ -100,11 +100,11 @@
"not_found": "Nie znaleziono",
"exited": "Zakończony",
"partial": "Częściowy",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
"created": "Utworzony",
"paused": "Wstrzymany",
"restarting": "Ponowne uruchamianie",
"removing": "Usuwanie",
"dead": "Martwy"
},
"ping": {
"error": "Błąd",
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "W kolejce",
"processing": "Przetwarzane",
"processed": "Przetworzone",
"time": "Czas"
"time": "Czas",
"failed": "Failed"
},
"firefly": {
"networth": "Wartość netto",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Poszukiwane",
"queued": "W kolejce",
"leagues": "Ligi"
},
"feed": {
"noItems": "Brak elementów"
}
}
+31 -9
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",
@@ -100,11 +100,11 @@
"not_found": "Não Encontrado",
"exited": "Saiu",
"partial": "Parcial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
"created": "Criado",
"paused": "Pausado",
"restarting": "A reiniciar",
"removing": "A remover",
"dead": "Morto"
},
"ping": {
"error": "Erro",
@@ -256,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",
@@ -479,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",
@@ -780,7 +790,8 @@
"queue": "Fila de espera",
"processing": "A Processar",
"processed": "Processado",
"time": "Hora"
"time": "Hora",
"failed": "Failed"
},
"firefly": {
"networth": "Valor Líquido",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Procurado",
"queued": "Em fila de espera",
"leagues": "Ligas"
},
"feed": {
"noItems": "Sem itens"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Fila de espera",
"processing": "Processando",
"processed": "Processado",
"time": "Hora"
"time": "Hora",
"failed": "Failed"
},
"firefly": {
"networth": "Valor Líquido",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Desejado",
"queued": "Na fila",
"leagues": "Ligas"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Timp"
"time": "Timp",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,6 +256,16 @@
"leech": "Лич",
"seed": "Сид"
},
"qui": {
"leech": "Лич",
"download": "Скачивание",
"seed": "Сид",
"upload": "Загрузка",
"total": "Всего",
"errored": "Ошибок",
"ratio": "Соотношение",
"freeSpace": "Свободно"
},
"qnap": {
"cpuUsage": "Использование ЦП",
"memUsage": "Использование ОЗУ",
@@ -780,7 +790,8 @@
"queue": "Очередь",
"processing": "Обрабатывается",
"processed": "Обработано",
"time": "Время"
"time": "Время",
"failed": "Неудачно"
},
"firefly": {
"networth": "Общая средства",
@@ -859,6 +870,14 @@
"reading": "Читаю",
"finished": "Завершено"
},
"bookorbit": {
"books": "Книги",
"authors": "Авторы",
"series": "Серии",
"storage": "Память",
"reading": "Чтение",
"finished": "Завершено"
},
"jdownloader": {
"downloadCount": "Очередь",
"downloadBytesRemaining": "Осталось",
@@ -1258,5 +1277,8 @@
"wanted": "Требуется",
"queued": "В очереди",
"leagues": "Лиги"
},
"feed": {
"noItems": "Ничего нет"
}
}
+24 -2
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"
},
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Poradie",
"processing": "Processing",
"processed": "Spracované",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "V poradí",
"leagues": "Leagues"
},
"feed": {
"noItems": "Žiadne položky"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Čas"
"time": "Čas",
"failed": "Failed"
},
"firefly": {
"networth": "Neto vrednost",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "Заузеће меморије",
@@ -780,7 +790,8 @@
"queue": "Ред",
"processing": "Обрада",
"processed": "Обрада",
"time": "Време"
"time": "Време",
"failed": "Failed"
},
"firefly": {
"networth": "Нето вредност",
@@ -859,6 +870,14 @@
"reading": "Читање",
"finished": "Завршено"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Ред",
"downloadBytesRemaining": "Преостало",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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ı",
@@ -780,7 +790,8 @@
"queue": "Kuyruk",
"processing": "İşleniyor",
"processed": "İşlendi",
"time": "Zaman"
"time": "Zaman",
"failed": "Failed"
},
"firefly": {
"networth": "Net değer",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+28 -6
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",
@@ -140,7 +140,7 @@
"series": "Серіали",
"episodes": "Серії",
"songs": "Пiснi",
"albums": "Albums"
"albums": "Альбоми"
},
"esphome": {
"offline": "Офлайн",
@@ -256,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": "Використання пам'яті",
@@ -780,7 +790,8 @@
"queue": "Черга",
"processing": "Обробка",
"processed": "Оброблено",
"time": "Час"
"time": "Час",
"failed": "Failed"
},
"firefly": {
"networth": "Чисті Активи",
@@ -859,6 +870,14 @@
"reading": "Читаю",
"finished": "Завершено"
},
"bookorbit": {
"books": "Книжки",
"authors": "Автори",
"series": "Серії",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "Черга",
"downloadBytesRemaining": "Залишилось",
@@ -1257,6 +1276,9 @@
"sportarr": {
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
"leagues": "Ліги"
},
"feed": {
"noItems": "Немає елементів"
}
}
+23 -1
View File
@@ -256,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ớ",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"time": "Time",
"failed": "Failed"
},
"firefly": {
"networth": "Net Worth",
@@ -859,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",
@@ -1258,5 +1277,8 @@
"wanted": "Cần tải",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "記憶體使用率",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "淨值",
@@ -859,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "佇列",
"downloadBytesRemaining": "剩餘大小",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "内存使用",
@@ -780,7 +790,8 @@
"queue": "队列",
"processing": "处理中",
"processed": "已处理",
"time": "时间"
"time": "时间",
"failed": "Failed"
},
"firefly": {
"networth": "净值",
@@ -859,6 +870,14 @@
"reading": "阅读中",
"finished": "已读完"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "队列",
"downloadBytesRemaining": "剩余",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
+23 -1
View File
@@ -256,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": "記憶體使用率",
@@ -780,7 +790,8 @@
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "時間"
"time": "時間",
"failed": "Failed"
},
"firefly": {
"networth": "淨值",
@@ -859,6 +870,14 @@
"reading": "Reading",
"finished": "Finished"
},
"bookorbit": {
"books": "Books",
"authors": "Authors",
"series": "Series",
"storage": "Storage",
"reading": "Reading",
"finished": "Finished"
},
"jdownloader": {
"downloadCount": "佇列",
"downloadBytesRemaining": "剩餘大小",
@@ -1258,5 +1277,8 @@
"wanted": "Wanted",
"queued": "Queued",
"leagues": "Leagues"
},
"feed": {
"noItems": "No items"
}
}
@@ -0,0 +1,57 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
vi.mock("next-auth/jwt", () => ({ getToken: vi.fn(async () => null) }));
vi.mock("utils/logger", () => ({ default: () => ({ error: vi.fn(), warn: vi.fn(), debug: vi.fn(), info: vi.fn() }) }));
const allRoutes = Object.entries(import.meta.glob("/src/pages/api/**/*.js"));
const routes = allRoutes.filter(([file]) => !/\/(auth|mcp)\//.test(file) && !file.endsWith("/healthcheck.js"));
describe("API authentication without middleware", () => {
beforeEach(() => vi.stubEnv("HOMEPAGE_AUTH_ENABLED", "true"));
afterEach(() => vi.unstubAllEnvs());
it.each(routes)("rejects anonymous access to %s at its exported handler", async (_file, load) => {
const { default: handler } = await load();
const req = { query: {}, headers: { "x-prerender-revalidate": "public-image-key" }, cookies: {} };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(401);
expect(res.body).toEqual({ error: "Unauthorized" });
expect(res.headers["Cache-Control"]).toBe("private, no-store");
});
it("keeps sign-in CSS public while protecting custom JavaScript", async () => {
const { default: handler } = await import("pages/api/config/[path]");
const css = createMockRes();
await handler({ query: { path: "custom.css" } }, css);
expect(css.statusCode).toBe(200);
const js = createMockRes();
await handler({ query: { path: "custom.js" }, headers: {}, cookies: {} }, js);
expect(js.statusCode).toBe(401);
});
});
describe("API host validation without middleware", () => {
beforeEach(() => {
vi.stubEnv("HOMEPAGE_ALLOWED_HOSTS", "");
vi.stubEnv("PORT", "3000");
});
afterEach(() => vi.unstubAllEnvs());
it.each(allRoutes)("rejects disallowed hosts at %s, including public routes", async (_file, load) => {
vi.spyOn(console, "error").mockImplementation(() => {});
const { default: handler } = await load();
const req = {
query: { path: "custom.css", nextauth: ["session"] },
method: "POST",
headers: { host: "evil.example", "x-prerender-revalidate": "public-image-key" },
cookies: {},
};
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ error: "Host validation failed. See logs for more details." });
});
});
@@ -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", () => {
+6 -13
View File
@@ -3,27 +3,20 @@ import { describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
import handler from "pages/api/revalidate";
import { invalidateDashboardData } from "utils/config/dashboard";
vi.mock("utils/config/dashboard", () => ({ invalidateDashboardData: vi.fn() }));
describe("pages/api/revalidate", () => {
it("revalidates and returns {revalidated:true}", async () => {
it("invalidates the dashboard snapshot without regenerating a static page", async () => {
const req = {};
const res = createMockRes();
res.revalidate = vi.fn().mockResolvedValueOnce(undefined);
await handler(req, res);
expect(res.revalidate).toHaveBeenCalledWith("/");
expect(invalidateDashboardData).toHaveBeenCalledOnce();
expect(res.revalidate).not.toHaveBeenCalled();
expect(res.body).toEqual({ revalidated: true });
});
it("returns 500 when revalidate throws", async () => {
const req = {};
const res = createMockRes();
res.revalidate = vi.fn().mockRejectedValueOnce(new Error("nope"));
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toBe("Error revalidating");
});
});
@@ -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 };
+4 -4
View File
@@ -19,22 +19,22 @@ describe("pages/api/theme", () => {
vi.clearAllMocks();
});
it("returns defaults when settings are missing", () => {
it("returns defaults when settings are missing", async () => {
getSettings.mockReturnValueOnce({});
const res = createMockRes();
handler({ res });
await handler({}, res);
expect(checkAndCopyConfig).toHaveBeenCalledWith("settings.yaml");
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ color: "slate", theme: "dark" });
});
it("returns configured color + theme when present", () => {
it("returns configured color + theme when present", async () => {
getSettings.mockReturnValueOnce({ color: "red", theme: "light" });
const res = createMockRes();
handler({ res });
await handler({}, res);
expect(res.body).toEqual({ color: "red", theme: "light" });
});
@@ -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();
});
});
+9 -7
View File
@@ -3,6 +3,7 @@
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { invalidateDashboardData } from "utils/config/dashboard";
import { ColorContext } from "utils/contexts/color";
import { SettingsContext } from "utils/contexts/settings";
import { TabContext } from "utils/contexts/tab";
@@ -170,8 +171,9 @@ vi.mock("components/toggles/revalidate", () => ({
default: () => null,
}));
describe("pages/index getStaticProps", () => {
describe("pages/index getServerSideProps", () => {
beforeEach(() => {
invalidateDashboardData();
vi.clearAllMocks();
state.throwIn = null;
state.validateData = [];
@@ -190,8 +192,8 @@ describe("pages/index getStaticProps", () => {
it("returns initial settings and api fallbacks for swr", async () => {
getSettings.mockReturnValueOnce({ providers: { x: 1 }, language: "en", title: "Homepage" });
const { getStaticProps } = await import("pages/index.jsx");
const result = await getStaticProps();
const { getServerSideProps } = await import("pages/index.jsx");
const result = await getServerSideProps({});
expect(result.props.initialSettings).toEqual({ language: "en", title: "Homepage" });
expect(result.props.fallback["/api/services"]).toEqual([{ name: "svc" }]);
@@ -204,8 +206,8 @@ describe("pages/index getStaticProps", () => {
it("normalizes legacy language codes before requesting translations", async () => {
getSettings.mockReturnValueOnce({ providers: {}, language: "zh-CN" });
const { getStaticProps } = await import("pages/index.jsx");
await getStaticProps();
const { getServerSideProps } = await import("pages/index.jsx");
await getServerSideProps({});
expect(serverSideTranslations).toHaveBeenCalledWith("zh-Hans");
});
@@ -214,8 +216,8 @@ describe("pages/index getStaticProps", () => {
getSettings.mockReturnValueOnce({ providers: {}, language: "de" });
state.throwIn = "services";
const { getStaticProps } = await import("pages/index.jsx");
const result = await getStaticProps();
const { getServerSideProps } = await import("pages/index.jsx");
const result = await getServerSideProps({});
expect(result.props.initialSettings).toEqual({});
expect(result.props.fallback["/api/services"]).toEqual([]);
@@ -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 />,
+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">
+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" },
@@ -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();
});
});
+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 }) {
+2 -2
View File
@@ -25,12 +25,12 @@ export default function Resource({
}`}
>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between gap-2">
<div className="pl-0.5">{value}</div>
<div className="pl-0.5 resource-value">{value}</div>
<div className="pr-1">{label}</div>
</div>
{expanded && (
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between gap-2">
<div className="pl-0.5">{expandedValue}</div>
<div className="pl-0.5 resource-value">{expandedValue}</div>
<div className="pr-1">{expandedLabel}</div>
</div>
)}
+3 -13
View File
@@ -1,6 +1,7 @@
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
import { isAllowedHost, secureCookie } from "utils/auth";
import { isAuthEnabled } from "utils/env";
const authEnabled = isAuthEnabled();
@@ -17,18 +18,7 @@ function withPrivateCache(res) {
}
export async function middleware(req) {
// Check the Host header, if HOMEPAGE_ALLOWED_HOSTS is set
const host = req.headers.get("host");
const port = process.env.PORT || 3000;
let allowedHosts = [`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`];
const allowAll = process.env.HOMEPAGE_ALLOWED_HOSTS === "*";
if (process.env.HOMEPAGE_ALLOWED_HOSTS) {
allowedHosts = allowedHosts.concat(process.env.HOMEPAGE_ALLOWED_HOSTS.split(","));
}
if (!allowAll && (!host || !allowedHosts.includes(host))) {
console.error(
`Host validation failed for: ${host}. Hint: Set the HOMEPAGE_ALLOWED_HOSTS environment variable to allow requests from this host / port.`,
);
if (!isAllowedHost(req.headers.get("host"))) {
return NextResponse.json({ error: "Host validation failed. See logs for more details." }, { status: 400 });
}
@@ -40,7 +30,7 @@ export async function middleware(req) {
return withPrivateCache(NextResponse.next());
}
const token = await getToken({ req, secret: authSecret });
const token = await getToken({ req, secret: authSecret, secureCookie: secureCookie() });
if (!token) {
const signInUrl = new URL("/auth/signin", req.url);
// Same-origin by construction, so this cannot be used as an open redirect
+25
View File
@@ -37,6 +37,7 @@ describe("middleware", () => {
beforeEach(() => {
vi.clearAllMocks();
process.env = { ...originalEnv };
delete process.env.HOMEPAGE_ALLOWED_HOSTS;
console.error = originalConsoleError;
});
@@ -135,6 +136,29 @@ describe("middleware", () => {
expect(res.type).toBe("next");
});
it.each([
["NEXTAUTH_URL", "https://homepage.example", true],
["NEXTAUTH_URL", "HTTPS://homepage.example", true],
["NEXTAUTH_URL", " https://homepage.example ", true],
["NEXTAUTH_URL", "HTTP://homepage.example", false],
["HOMEPAGE_EXTERNAL_URL", "HTTPS://homepage.example", true],
["HOMEPAGE_EXTERNAL_URL", " https://homepage.example ", true],
["HOMEPAGE_EXTERNAL_URL", "homepage.example", false],
])("reads the session cookie NextAuth sets when %s is %j", async (variable, url, secure) => {
process.env.HOMEPAGE_AUTH_ENABLED = "true";
process.env.HOMEPAGE_AUTH_SECRET = "secret";
delete process.env.NEXTAUTH_URL;
process.env[variable] = url;
getToken.mockResolvedValueOnce({ sub: "user" });
const middleware = await loadMiddleware();
const res = await middleware(createReq("localhost:3000", "http://localhost:3000/"));
expect(getToken).toHaveBeenCalledWith(expect.objectContaining({ secureCookie: secure }));
expect(res.type).toBe("next");
});
it("redirects to signin when auth is enabled and no token is present", async () => {
process.env.HOMEPAGE_AUTH_ENABLED = "true";
process.env.HOMEPAGE_AUTH_SECRET = "secret";
@@ -147,6 +171,7 @@ describe("middleware", () => {
expect(getToken).toHaveBeenCalledWith({
req: expect.objectContaining({ url: "http://localhost:3000/some" }),
secret: "secret",
secureCookie: false,
});
expect(NextResponse.redirect).toHaveBeenCalled();
expect(res.type).toBe("redirect");
+1
View File
@@ -3,6 +3,7 @@ import { appWithTranslation } from "next-i18next/pages";
import Head from "next/head";
import "styles/globals.css";
import "styles/manrope.css";
import "styles/plex-mono.css";
import "styles/theme.css";
import { SWRConfig } from "swr";
+4 -1
View File
@@ -3,6 +3,7 @@ import { createHash, timingSafeEqual } from "node:crypto";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { withAllowedHost } from "utils/auth";
import { applyNextAuthEnv, isAuthEnabled } from "utils/env";
import createLogger from "utils/logger";
@@ -163,7 +164,7 @@ export const authOptions = {
const nextAuthHandler = NextAuth(authOptions);
export default async function handler(req, res) {
async function handler(req, res) {
// Just pass empty session if auth not enabled
if (!authEnabled) {
return res.status(200).json({});
@@ -171,3 +172,5 @@ export default async function handler(req, res) {
return nextAuthHandler(req, res);
}
export default withAllowedHost(handler);
+4 -1
View File
@@ -1,5 +1,8 @@
import { withAuth } from "utils/auth";
import { bookmarksResponse } from "utils/config/api-response";
export default async function handler(req, res) {
async function handler(req, res) {
res.send(await bookmarksResponse());
}
export default withAuth(handler);
+4 -1
View File
@@ -1,6 +1,7 @@
import fs from "fs";
import path from "path";
import { withAuth } from "utils/auth";
import { CONF_DIR } from "utils/config/config";
import createLogger from "utils/logger";
@@ -10,7 +11,7 @@ const logger = createLogger("configFileService");
* @param {import("next").NextApiRequest} req
* @param {import("next").NextApiResponse} res
*/
export default async function handler(req, res) {
async function handler(req, res) {
const { path: relativePath } = req.query;
// only two supported files, for now
@@ -32,3 +33,5 @@ export default async function handler(req, res) {
return res.status(500).end("Internal Server Error");
}
}
export default withAuth(handler, { public: (req) => req.query?.path === "custom.css" });
+4 -1
View File
@@ -1,9 +1,10 @@
import { withAuth } from "utils/auth";
import { getDockerStats } from "utils/docker/stats";
import createLogger from "utils/logger";
const logger = createLogger("dockerStats");
export default async function handler(req, res) {
async function handler(req, res) {
try {
const result = await getDockerStats(req.query.server);
@@ -19,3 +20,5 @@ export default async function handler(req, res) {
});
}
}
export default withAuth(handler);
+4 -1
View File
@@ -1,9 +1,10 @@
import { withAuth } from "utils/auth";
import { getDockerStatuses } from "utils/docker/status";
import createLogger from "utils/logger";
const logger = createLogger("dockerStatuses");
export default async function handler(req, res) {
async function handler(req, res) {
try {
const result = await getDockerStatuses(req.query.server);
@@ -19,3 +20,5 @@ export default async function handler(req, res) {
});
}
}
export default withAuth(handler);
+4 -1
View File
@@ -2,6 +2,7 @@ import { createHash } from "crypto";
import { readFileSync } from "fs";
import { join } from "path";
import { withAuth } from "utils/auth";
import checkAndCopyConfig, { CONF_DIR } from "utils/config/config";
const configs = [
@@ -20,7 +21,7 @@ function hash(buffer) {
return hashSum.digest("hex");
}
export default async function handler(req, res) {
async function handler(req, res) {
const hashes = configs.map((config) => {
checkAndCopyConfig(config);
const configYaml = join(CONF_DIR, config);
@@ -36,3 +37,5 @@ export default async function handler(req, res) {
hash: combinedHash,
});
}
export default withAuth(handler);

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