Compare commits

...

71 Commits

Author SHA1 Message Date
Crowdin Bot 80eab6a221 New Crowdin translations by GitHub Action
Lint / Linting Checks (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-09-15 12:28:38 +00:00
shamoon c764d05a0c Fix: better handle PeaNUT missing data (#7139)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-12 21:49:05 -07:00
Ben Belov fcd9a715d9 Enhancement: Add bearer authentication to whatsupdocker widget (#7134)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-11 23:24:08 -07:00
dependabot[bot] eacc205249 Chore(deps): Bump crowdin/github-action from 3.0.0 to 3.0.1 (#7128)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 16:57:51 +00:00
dependabot[bot] 4249a3fb95 Chore(deps): Bump actions/deploy-pages from 5.0.0 to 5.0.1 (#7127)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 16:50:37 +00:00
dependabot[bot] 0b98428ab5 Chore(deps): Bump docker/setup-qemu-action from 4.2.0 to 4.3.0 (#7129)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 09:44:14 -07:00
dependabot[bot] bbe52519bb Chore(deps): Bump js-yaml from 5.4.0 to 5.4.1 (#7119)
Docker CI / Docker Build & Push (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-09 15:12:08 -07:00
shamoon 541b2fe9f1 2.3.0
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-09 14:45:28 -07:00
github-actions[bot] 574ba2c7cb New Crowdin translations by GitHub Action (#7090)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-09 14:43:53 -07:00
shamoon f78cab7557 Documentation: update jellyfin versioning 2026-09-09 14:43:02 -07:00
shamoon 2cd77eae1f Fixhancement: use ghostfolio baseCurrency for net worth (#7116)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-08 09:25:58 -07:00
shamoon 6ab83b0b5f Tweak: handle QL selection without hover (#7105)
Release Drafter / Auto Label PR (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-06 18:35:01 -07:00
shamoon 9aa5a79c41 Fix: dont fire search on empty field (#7101)
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-05 16:07:12 -07:00
shamoon 1c2cb76702 Enhancement: render more docker states (#7099)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-05 10:24:59 -07:00
shamoon 7de1faa769 Performance: use bulk request for docker stats (#7098) 2026-09-05 10:08:20 -07:00
shamoon 1024a382ee Enhancement: allow OIDC auto-login (#7096) 2026-09-05 08:25:00 -07:00
DaddyBoard 438788240b Performance: fetch all docker container statuses in one request (#7092)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-09-05 15:17:52 +00:00
dependabot[bot] df34927198 Chore(deps): Bump crowdin/github-action from 2.17.1 to 3.0.0 (#7091)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-05 02:21:31 -07:00
shamoon ddc5adc91c Fix: handle literal null from crowdsec api alerts (#7088)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-02 12:40:40 -07:00
shamoon 22f1317ad3 2.2.0
Docs / Test Build Docs (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-09-01 20:34:02 -07:00
shamoon 7c36d09217 Merge branch 'dev' 2026-09-01 20:33:49 -07:00
github-actions[bot] 378f2d5d6d New Crowdin translations by GitHub Action (#7043)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-09-01 20:33:38 -07:00
shamoon d907ca1d0e Merge changes from fork 2026-09-01 20:25:13 -07:00
dependabot[bot] 81accd8dbe Chore(deps-dev): Bump eslint-config-next from 15.5.22 to 16.3.3 (#7082)
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 10:52:46 -07:00
dependabot[bot] 070e1b9cca Chore(deps): Bump next from 16.3.0 to 16.3.3 (#7084)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 17:02:22 +00:00
dependabot[bot] 6324c5ee63 Chore(deps): Bump react-i18next from 17.0.11 to 17.0.12 (#7085)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 16:55:53 +00:00
dependabot[bot] 9b475f48d8 Chore(deps): Bump js-yaml from 5.3.0 to 5.4.0 (#7083)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 16:48:35 +00:00
dependabot[bot] 35b72b65de Chore(deps-dev): Bump typescript from 5.9.3 to 6.0.3 (#7081)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 09:42:44 -07:00
shamoon f6dca0c5ad Fixhancement: crowdsec widget improvements (#7075)
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
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-08-29 23:43:19 -07:00
shamoon e6ad43a76c Tweak: improve nextauth error logging for easier debugging (#7070)
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-08-28 12:23:55 -07:00
dependabot[bot] c46b3e6cff Chore(deps): Bump docker/setup-buildx-action from 4.2.0 to 4.3.0 (#7065)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-27 17:43:25 +00:00
dependabot[bot] e913243149 Chore(deps): Bump crowdin/github-action from 2.17.0 to 2.17.1 (#7066)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-27 17:37:38 +00:00
dependabot[bot] 99e16c6da0 Chore(deps): Bump astral-sh/setup-uv from 10.0.0 to 10.0.1 (#7067)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-27 10:31:08 -07:00
Vaibhav Srivastava c0b6eb3501 Documentation: fix typo ommitted -> omitted (#7064)
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-08-27 00:20:59 -07:00
shamoon a17763ca9f Accessibility: improve sr labeling of search ui (#7052)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
2026-08-23 21:49:31 -07:00
shamoon bd39fb48b9 Chore: narrow/specify some workflow permissions (#7051)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
2026-08-23 16:51:28 -07:00
shamoon f2dc80e39e Tweak: move favicon population earlier (#7041)
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-08-21 14:06:21 -07:00
shamoon 7d48b7ea78 Fix QL Copilot thing 2026-08-21 14:00:24 -07:00
shamoon 4c361422ca Chore: full react hooks eslint compliance (#7040) 2026-08-21 14:00:02 -07:00
shamoon 7c26cf6d97 Add 'minor' label to release drafter configuration
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-08-21 12:15:44 -07:00
shamoon 5873f8e7d5 2.1.2
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-08-21 09:58:19 -07:00
shamoon 6099837373 Merge branch 'dev' 2026-08-21 09:52:58 -07:00
shamoon f0bd255adb Fix yaml parsing in 2.1.1 (#7037) 2026-08-21 09:48:25 -07:00
shamoon 21bf7ed8e2 2.1.1 2026-08-21 08:47:00 -07:00
github-actions[bot] 728c6fbe18 New Crowdin translations by GitHub Action (#7027)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-08-21 15:46:27 +00:00
dependabot[bot] 6a96289717 Chore(deps-dev): Bump eslint-plugin-react-hooks from 5.2.0 to 7.1.1 (#7033)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-21 08:22:55 -07:00
shamoon fa47a2d86c Chore: again, improve k8s error logging 2026-08-21 08:11:48 -07:00
shamoon cc22d04429 Fixhancement: Handle k8s ipv6 node tls bug (#7035) 2026-08-21 07:21:38 -07:00
dependabot[bot] a414f376b9 Chore(deps): Bump js-yaml from 4.3.1 to 5.3.0 (#7032)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-08-21 07:18:27 -07:00
dependabot[bot] bdd77342d8 Chore(deps-dev): Bump @testing-library/jest-dom from 7.0.0 to 7.0.1 in the testing-library group (#7029)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 13:57:53 +00:00
dependabot[bot] 63d2172ef7 Chore(deps): Bump swr from 2.5.0 to 2.5.1 (#7031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 13:51:09 +00:00
dependabot[bot] 85e4e439bc Chore(deps): Bump @kubernetes/client-node from 1.4.0 to 2.0.0 (#7030)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 06:44:33 -07:00
shamoon 36aa241689 Merge branch 'main' into dev 2026-08-21 06:37:32 -07:00
shamoon 0933ba015a Docs: clarify Claude use of mcp-remote 2026-08-21 06:34:09 -07:00
shamoon c57117daae Fix: move mcp method ordering 2026-08-21 06:29:55 -07:00
shamoon 93763b7c10 2.1.0
Docker CI / Docker Build & Push (push) Has been cancelled
Docs / Test Build Docs (push) Has been cancelled
Docs / Build & Deploy Docs (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-08-20 19:14:17 -07:00
github-actions[bot] 6c551ace34 New Crowdin translations by GitHub Action (#6983)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-08-20 19:13:24 -07:00
shamoon 7886742d56 Just test to keep this from changing 2026-08-20 16:46:59 -07:00
shamoon a253861061 Tweak: read auth providers in-process on the sign-in page, set env earlier (#7023) 2026-08-20 16:44:15 -07:00
dependabot[bot] edfb28ab5d Chore(deps): Bump astral-sh/setup-uv from 9.0.0 to 10.0.0 (#7021)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 10:00:42 -07:00
shamoon 3ab6b040b2 Fixhancement: handle linkwarden API change (#7018)
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-08-19 10:25:24 -07:00
shamoon 9cabb46bb7 Fix: fix calendar hover with white theme (#7017) 2026-08-19 10:03:52 -07:00
shamoon d7cc2d3f19 Tweakhancement: use routeNamespace as fallback for k8s gateway integration (#7009)
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-08-18 10:55:33 -07:00
shamoon b5084ba8b3 Fix: fix k8s gateway error logging (#7008) 2026-08-18 10:51:29 -07:00
shamoon dca0c9ab81 Fix: fix duplicati widget with no jobs (#7005) 2026-08-18 08:44:44 -07:00
shamoon d4fd14b724 Merge branch 'main' into dev
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-08-18 00:54:00 -07:00
Zhelyan Radoev 600c9eee78 Enhancement: add authorizationsLast24H field to Authentik widget, opt… (#6989)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-08-16 07:21:51 -07:00
shamoon 198c86fb49 Fix: ensure authentik widget v1 fallback (#6991)
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-08-15 11:05:46 -07:00
shamoon f711d290a7 Fix: re-enable global lint, fix un-caught warnings / errors (#6990) 2026-08-15 11:01:29 -07:00
shamoon 342afa2215 Enhancement: widget support for Pulse v6 API changes (#6987)
Lint / Linting Checks (push) Has been cancelled
Docker CI / Docker Build & Push (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled
2026-08-15 00:11:08 -07:00
shamoon 27704f962e Tweak: whitelist custom.css from auth (#6986) 2026-08-14 23:52:46 -07:00
391 changed files with 6544 additions and 4236 deletions
+1
View File
@@ -10,6 +10,7 @@ version-resolver:
- 'breaking-change' - 'breaking-change'
minor: minor:
labels: labels:
- 'minor'
- 'enhancement' - 'enhancement'
- 'feature' - 'feature'
patch: patch:
+5 -1
View File
@@ -10,6 +10,10 @@ on:
] ]
branches: [ dev ] branches: [ dev ]
permissions:
contents: write
pull-requests: write
jobs: jobs:
synchronize-with-crowdin: synchronize-with-crowdin:
name: Crowdin Sync name: Crowdin Sync
@@ -19,7 +23,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: crowdin action - name: crowdin action
uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2 uses: crowdin/github-action@71fdb8814261dd703be4ca7c6450d21b1868da4b # v3.0.1
with: with:
upload_translations: false upload_translations: false
download_translations: true download_translations: true
+4 -3
View File
@@ -17,6 +17,8 @@ on:
env: env:
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
permissions: {}
jobs: jobs:
@@ -27,7 +29,6 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -98,10 +99,10 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
- name: Setup Docker buildx - name: Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- name: Build and push Docker image - name: Build and push Docker image
id: build-and-push id: build-and-push
+7 -5
View File
@@ -10,8 +10,6 @@ on:
permissions: permissions:
contents: read contents: read
pages: write
id-token: write
jobs: jobs:
@@ -25,7 +23,7 @@ jobs:
with: with:
python-version-file: ".python-version" python-version-file: ".python-version"
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- run: sudo apt-get install pngquant - run: sudo apt-get install pngquant
- name: Test Docs Build - name: Test Docs Build
run: uv run --frozen zensical build --clean run: uv run --frozen zensical build --clean
@@ -33,6 +31,10 @@ jobs:
name: Build & Deploy Docs name: Build & Deploy Docs
if: github.repository == 'gethomepage/homepage' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' if: github.repository == 'gethomepage/homepage' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment: environment:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
@@ -43,12 +45,12 @@ jobs:
with: with:
python-version-file: ".python-version" python-version-file: ".python-version"
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- run: sudo apt-get install pngquant - run: sudo apt-get install pngquant
- name: Build Docs - name: Build Docs
run: uv run --frozen zensical build --clean run: uv run --frozen zensical build --clean
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with: with:
path: site path: site
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
id: deployment id: deployment
+5 -3
View File
@@ -6,15 +6,15 @@ on:
discussion: discussion:
types: [labeled] types: [labeled]
permissions: permissions: {}
discussions: write
issues: write
jobs: jobs:
close-needs-discussion: close-needs-discussion:
name: Issues Need Discussion name: Issues Need Discussion
if: github.event_name == 'issues' && github.event.label.name == 'needs-discussion' if: github.event_name == 'issues' && github.event.label.name == 'needs-discussion'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
issues: write
steps: steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
@@ -49,6 +49,8 @@ jobs:
name: Discussions Need Information name: Discussions Need Information
if: github.event_name == 'discussion' && github.event.label.name == 'needs-information' if: github.event_name == 'discussion' && github.event.label.name == 'needs-information'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
steps: steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
+3
View File
@@ -6,6 +6,9 @@ on:
workflow_dispatch: workflow_dispatch:
merge_group: merge_group:
permissions:
contents: read
jobs: jobs:
lint: lint:
name: Linting Checks name: Linting Checks
-1
View File
@@ -2,7 +2,6 @@ name: PR Quality
permissions: permissions:
contents: read contents: read
issues: read
pull-requests: write pull-requests: write
on: on:
-1
View File
@@ -5,7 +5,6 @@ on:
types: [labeled] types: [labeled]
permissions: permissions:
issues: write
pull-requests: write pull-requests: write
jobs: jobs:
+14 -4
View File
@@ -5,10 +5,7 @@ on:
- cron: '0 3 * * *' - cron: '0 3 * * *'
workflow_dispatch: workflow_dispatch:
permissions: permissions: {}
issues: write
pull-requests: write
discussions: write
concurrency: concurrency:
group: lock group: lock
@@ -17,6 +14,9 @@ jobs:
stale: stale:
name: 'Stale' name: 'Stale'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps: steps:
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10 - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10
with: with:
@@ -31,6 +31,10 @@ jobs:
lock-threads: lock-threads:
name: 'Lock Old Threads' name: 'Lock Old Threads'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
issues: write
pull-requests: write
steps: steps:
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2 - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
with: with:
@@ -56,6 +60,8 @@ jobs:
close-answered-discussions: close-answered-discussions:
name: 'Close Answered Discussions' name: 'Close Answered Discussions'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
steps: steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
@@ -112,6 +118,8 @@ jobs:
close-outdated-discussions: close-outdated-discussions:
name: 'Close Outdated Discussions' name: 'Close Outdated Discussions'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
steps: steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
@@ -203,6 +211,8 @@ jobs:
close-unsupported-feature-requests: close-unsupported-feature-requests:
name: 'Close Unsupported Feature Requests' name: 'Close Unsupported Feature Requests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
steps: steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
+3
View File
@@ -5,6 +5,9 @@ on:
push: push:
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
vitest: vitest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+4
View File
@@ -7,6 +7,10 @@ As of version v0.6.30 homepage supports adding your own custom css & javascript.
To add custom css simply edit the `custom.css` file under your config directory, similarly for javascript you would edit `custom.js`. You can then target elements in homepage with various classes / ids to customize things to your liking. To add custom css simply edit the `custom.css` file under your config directory, similarly for javascript you would edit `custom.js`. You can then target elements in homepage with various classes / ids to customize things to your liking.
!!! warning
When Homepage authentication is enabled, `custom.css` remains publicly accessible so it can style the sign-in page. Do not include secrets or sensitive internal URLs in this file. `custom.js` remains protected and is only loaded after authentication.
You can also set a specific `id` for a service or bookmark to target with your custom css or javascript, e.g. You can also set a specific `id` for a service or bookmark to target with your custom css or javascript, e.g.
```yaml ```yaml
+17
View File
@@ -49,6 +49,23 @@ environment:
HOMEPAGE_MCP_TOKEN: "generate-with-openssl-rand-base64-32" HOMEPAGE_MCP_TOKEN: "generate-with-openssl-rand-base64-32"
``` ```
## Connecting Claude Desktop
Claude Desktop can use [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) to communicate with the homepage MCP endpoint in `claude_desktop_config.json`:
```json
{
"mcpServers": {
"homepage": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp", "--header", "X-Homepage-MCP-Token: your-token"]
}
}
}
```
The **Add custom connector** option will not work: remote connectors authenticate with OAuth and cannot supply `HOMEPAGE_MCP_TOKEN`.
## Read-only by default ## Read-only by default
The MCP endpoint exposes tools for reading and validating supported Homepage config files. File writes are disabled unless you opt in with: The MCP endpoint exposes tools for reading and validating supported Homepage config files. File writes are disabled unless you opt in with:
+6 -1
View File
@@ -69,11 +69,16 @@ For OIDC login (overrides password login):
- `HOMEPAGE_OIDC_CLIENT_ID` - `HOMEPAGE_OIDC_CLIENT_ID`
- `HOMEPAGE_OIDC_CLIENT_SECRET` - `HOMEPAGE_OIDC_CLIENT_SECRET`
- Optional: `HOMEPAGE_OIDC_NAME` (display name), `HOMEPAGE_OIDC_SCOPE` (defaults to `openid email profile`) - Optional: `HOMEPAGE_OIDC_NAME` (display name), `HOMEPAGE_OIDC_SCOPE` (defaults to `openid email profile`)
- Optional: `HOMEPAGE_OIDC_AUTO_LOGIN=true` to skip the login page and send unauthenticated visitors straight to the provider
!!! tip
With auto-login enabled, visit `/auth/signin?autologin=0` to reach the login page without being redirected, e.g. in case something goes wrong!
!!! warning !!! warning
Homepage grants access to any identity that the configured OIDC provider authorizes for this client. Configure client assignments, groups, or access policies at the identity provider. Homepage does not apply additional claim-based authorization. Homepage grants access to any identity that the configured OIDC provider authorizes for this client. Configure client assignments, groups, or access policies at the identity provider. Homepage does not apply additional claim-based authorization.
All app pages and `/api` routes except `/api/healthcheck` will require a signed-in session. Static assets remain public. All app pages and `/api` routes except `/api/healthcheck` and `/api/config/custom.css` will require a signed-in session. Static assets remain public.
Configure your OIDC provider with the a callback URI like `https://homepage.example.com/api/auth/callback/homepage-oidc`. Configure your OIDC provider with the a callback URI like `https://homepage.example.com/api/auth/callback/homepage-oidc`.
@@ -85,6 +85,7 @@ To ensure cohesiveness of various widgets, the following should be used as a gui
- Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control. - Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control.
- Widgets should be only one row of blocks - Widgets should be only one row of blocks
- Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets - Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets
- Use `withWidgetFields(service, defaultFields)` from `utils/widget-fields` to apply default fields and cap configured fields without mutating the service. The helper limits widgets to 4 fields.
- Minimize the number of API calls - Minimize the number of API calls
- Avoid the use of custom proxy unless absolutely necessary - Avoid the use of custom proxy unless absolutely necessary
- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself. - Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself.
+2 -2
View File
@@ -5,7 +5,7 @@ description: Authentik Widget Configuration
Learn more about [Authentik](https://github.com/goauthentik/authentik). Learn more about [Authentik](https://github.com/goauthentik/authentik).
This widget reads the number of active users in the system, as well as logins for the last 24 hours. This widget reads the number of active users in the system, as well as logins for the last 24 hours, and authorizations when using widget version 2.
You will need to generate an API token for an existing user under `Admin Portal` > `Directory` > `Tokens & App passwords`. You will need to generate an API token for an existing user under `Admin Portal` > `Directory` > `Tokens & App passwords`.
Make sure to set Intent to "API Token". Make sure to set Intent to "API Token".
@@ -15,7 +15,7 @@ The account you made the API token for also needs the following **Assigned globa
- authentik Core -> Can view User (Model: User) - authentik Core -> Can view User (Model: User)
- authentik Events -> Can view Event (Model: Event) - authentik Events -> Can view Event (Model: Event)
Allowed fields: `["users", "loginsLast24H", "failedLoginsLast24H"]`. Allowed fields: `["users", "loginsLast24H", "failedLoginsLast24H", "authorizationsLast24H"]` (`authorizationsLast24H` works with v2 only).
| Authentik Version | Homepage Widget Version | | Authentik Version | Homepage Widget Version |
| ----------------- | ----------------------- | | ----------------- | ----------------------- |
+3 -2
View File
@@ -9,7 +9,9 @@ See the [crowdsec docs](https://docs.crowdsec.net/docs/local_api/intro/#machines
in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`). in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`).
!!! note !!! note
Without the `limit24h` option, the widget will fetch all alerts which is limited to 100 by the API to avoid performance issues.
`alerts` counts alerts raised by your own engine in the last 24 hours. Alerts originating from the
CrowdSec community blocklist are excluded.
Allowed fields: `["alerts", "bans"]`. Allowed fields: `["alerts", "bans"]`.
@@ -19,5 +21,4 @@ widget:
url: http://crowdsechostorip:port url: http://crowdsechostorip:port
username: localhost # machine_id in crowdsec username: localhost # machine_id in crowdsec
password: password password: password
limit24h: true # optional, limits alerts to last 24h. Default: false
``` ```
+2 -2
View File
@@ -13,8 +13,8 @@ Allowed fields: `["movies", "series", "episodes", "songs", "albums"]`.
| Jellyfin Version | Homepage Widget Version | | Jellyfin Version | Homepage Widget Version |
| ---------------- | ----------------------- | | ---------------- | ----------------------- |
| < 10.12 | 1 (default) | | < 12 (10.12) | 1 (default) |
| >= 10.12 | 2 | | >= 12 (10.12) | 2 |
```yaml ```yaml
widget: widget:
+1 -1
View File
@@ -15,5 +15,5 @@ widget:
url: https://proxmoxbackupserver.host:port url: https://proxmoxbackupserver.host:port
username: api_token_id username: api_token_id
password: api_token_secret password: api_token_secret
datastore: datastore_name #optional; if ommitted, will display a combination of all datastores used / total datastore: datastore_name #optional; if omitted, will display a combination of all datastores used / total
``` ```
+1
View File
@@ -12,5 +12,6 @@ widget:
type: pulse type: pulse
url: http://pulse.host.or.ip:7655 url: http://pulse.host.or.ip:7655
key: your-api-token # `monitoring:read` scope is required key: your-api-token # `monitoring:read` scope is required
version: 2 # required for Pulse v6, defaults to 1
fields: ["nodes", "vms", "lxcs"] # optional fields: ["nodes", "vms", "lxcs"] # optional
``` ```
+1
View File
@@ -13,4 +13,5 @@ widget:
url: http://whatsupdocker:port url: http://whatsupdocker:port
username: username # optional username: username # optional
password: password # optional password: password # optional
key: bearer-token # optional, takes precedence over username/password
``` ```
+5 -20
View File
@@ -1,27 +1,12 @@
import path from "node:path"; import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import { fileURLToPath } from "node:url"; import prettierConfig from "eslint-config-prettier/flat";
import { fixupConfigRules } from "@eslint/compat";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
import prettier from "eslint-plugin-prettier";
import { defineConfig, globalIgnores } from "eslint/config"; import { defineConfig, globalIgnores } from "eslint/config";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([ export default defineConfig([
...nextCoreWebVitals,
prettierConfig,
{ {
extends: fixupConfigRules(compat.extends("next/core-web-vitals", "prettier", "plugin:react-hooks/recommended")), files: ["**/*.{js,mjs,cjs,jsx}"],
plugins: {
prettier,
},
languageOptions: { languageOptions: {
ecmaVersion: 6, ecmaVersion: 6,
+9 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "2.0.0", "version": "2.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -15,7 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.10", "@headlessui/react": "^2.2.10",
"@kubernetes/client-node": "^1.4.0", "@kubernetes/client-node": "^2.0.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"dockerode": "^5.0.0", "dockerode": "^5.0.0",
@@ -23,12 +23,12 @@
"gamedig": "^5.3.3", "gamedig": "^5.3.3",
"i18next": "^26.3.6", "i18next": "^26.3.6",
"ical.js": "^2.2.1", "ical.js": "^2.2.1",
"js-yaml": "^4.3.1", "js-yaml": "^5.4.1",
"json-rpc-2.0": "^1.7.1", "json-rpc-2.0": "^1.7.1",
"luxon": "^3.7.2", "luxon": "^3.7.2",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"minecraftstatuspinger": "^1.2.2", "minecraftstatuspinger": "^1.2.2",
"next": "^16.2.12", "next": "^16.3.3",
"next-auth": "^4.24.15", "next-auth": "^4.24.15",
"next-i18next": "^16.0.8", "next-i18next": "^16.0.8",
"ping": "^1.0.0", "ping": "^1.0.0",
@@ -36,10 +36,10 @@
"raw-body": "^4.0.0", "raw-body": "^4.0.0",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"react-i18next": "^17.0.11", "react-i18next": "^17.0.12",
"react-icons": "^5.6.0", "react-icons": "^5.6.0",
"recharts": "^3.1.2", "recharts": "^3.1.2",
"swr": "^2.4.2", "swr": "^2.5.1",
"systeminformation": "^5.33.1", "systeminformation": "^5.33.1",
"tough-cookie": "^6.0.2", "tough-cookie": "^6.0.2",
"urbackup-server-api": "^0.92.2", "urbackup-server-api": "^0.92.2",
@@ -48,29 +48,21 @@
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.1.0",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.4",
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@tailwindcss/postcss": "^4.3.3", "@tailwindcss/postcss": "^4.3.3",
"@testing-library/jest-dom": "^7.0.0", "@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@vitest/coverage-v8": "^4.1.10", "@vitest/coverage-v8": "^4.1.10",
"eslint": "^9.25.1", "eslint": "^9.25.1",
"eslint-config-next": "^15.5.11", "eslint-config-next": "^16.3.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^30.0.1", "jsdom": "^30.0.1",
"postcss": "^8.5.26", "postcss": "^8.5.26",
"prettier": "^3.8.4", "prettier": "^3.8.4",
"prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-organize-imports": "^4.3.0",
"tailwind-scrollbar": "^4.0.2", "tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"typescript": "^5.7.3", "typescript": "^6.0.3",
"vitest": "^4.1.0" "vitest": "^4.1.0"
}, },
"optionalDependencies": { "optionalDependencies": {
+593 -463
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4"/></svg>

After

Width:  |  Height:  |  Size: 96 B

+12 -4
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Teken uit"
}, },
"widget": { "widget": {
"missing_type": "Ontbrekende legstuk-tipe: {{type}}", "missing_type": "Ontbrekende legstuk-tipe: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Wag asseblief" "wait": "Wag asseblief"
}, },
"search": { "search": {
"placeholder": "Soek…" "placeholder": "Soek…",
"provider": "Soekenjin: {{name}}",
"select_provider": "Kies soekenjin"
}, },
"resources": { "resources": {
"cpu": "SVE", "cpu": "SVE",
@@ -97,7 +99,12 @@
"unhealthy": "Ongesond", "unhealthy": "Ongesond",
"not_found": "Nie Gevind Nie", "not_found": "Nie Gevind Nie",
"exited": "Verlaat", "exited": "Verlaat",
"partial": "Gedeeltelik" "partial": "Gedeeltelik",
"created": "Geskep",
"paused": "Onderbreek",
"restarting": "Herlaai",
"removing": "Verwydering",
"dead": "Dood"
}, },
"ping": { "ping": {
"error": "Fout", "error": "Fout",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Gebruikers", "users": "Gebruikers",
"loginsLast24H": "Aantekenings (24h)", "loginsLast24H": "Aantekenings (24h)",
"failedLoginsLast24H": "Mislukte Aantekenings (24h)" "failedLoginsLast24H": "Mislukte Aantekenings (24h)",
"authorizationsLast24H": "Magtigings (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "GEH", "mem": "GEH",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "الرجاء الإنتظار" "wait": "الرجاء الإنتظار"
}, },
"search": { "search": {
"placeholder": "بحث …" "placeholder": "بحث …",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "المعالج", "cpu": "المعالج",
@@ -97,7 +99,12 @@
"unhealthy": "غير سليم", "unhealthy": "غير سليم",
"not_found": "غير موجود", "not_found": "غير موجود",
"exited": "خرجت", "exited": "خرجت",
"partial": "جزئي" "partial": "جزئي",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "خطأ", "error": "خطأ",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "المستخدمون", "users": "المستخدمون",
"loginsLast24H": "تسجيلات الدخول (٢٤س)", "loginsLast24H": "تسجيلات الدخول (٢٤س)",
"failedLoginsLast24H": "فشل تسجيلات الدخول (٢٤س)" "failedLoginsLast24H": "فشل تسجيلات الدخول (٢٤س)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "الذاكرة", "mem": "الذاكرة",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Моля изчакайте" "wait": "Моля изчакайте"
}, },
"search": { "search": {
"placeholder": "Търсене…" "placeholder": "Търсене…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Процесор", "cpu": "Процесор",
@@ -97,7 +99,12 @@
"unhealthy": "Нездравословен", "unhealthy": "Нездравословен",
"not_found": "Не е открит", "not_found": "Не е открит",
"exited": "Напуснал", "exited": "Напуснал",
"partial": "Частично" "partial": "Частично",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Грешка", "error": "Грешка",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Потребители", "users": "Потребители",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+267 -259
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Tanca la sessió"
}, },
"widget": { "widget": {
"missing_type": "Falta el tipus de widget: {{type}}", "missing_type": "Falta el tipus de widget: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Si us plau, espereu" "wait": "Si us plau, espereu"
}, },
"search": { "search": {
"placeholder": "Cerca…" "placeholder": "Cerca…",
"provider": "Motor de cerca: {{name}}",
"select_provider": "Escull motor de cerca"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -70,17 +72,17 @@
"empty_data": "Estat del subsistema desconegut" "empty_data": "Estat del subsistema desconegut"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Saludable",
"degraded": "Degraded", "degraded": "Degradat",
"no_data": "No storage data available" "no_data": "Sense dades d'emmagatzematge disponible"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Tasques",
"stored": "Stored", "stored": "Desat",
"lastBackup": "Last Run", "lastBackup": "Darrera execució",
"nextRun": "Next Run", "nextRun": "Propera execució",
"running": "Running", "running": "En execució",
"warnings": "Warnings", "warnings": "Avisos",
"errors": "Errors" "errors": "Errors"
}, },
"docker": { "docker": {
@@ -97,7 +99,12 @@
"unhealthy": "No saludable", "unhealthy": "No saludable",
"not_found": "No trobat", "not_found": "No trobat",
"exited": "Tancat", "exited": "Tancat",
"partial": "Parcial" "partial": "Parcial",
"created": "Creat",
"paused": "Pausat",
"restarting": "Reiniciant",
"removing": "Eliminant",
"dead": "Mort"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -133,7 +140,7 @@
"series": "Sèries", "series": "Sèries",
"episodes": "Episodis", "episodes": "Episodis",
"songs": "Cançons", "songs": "Cançons",
"albums": "Albums" "albums": "Àlbums"
}, },
"esphome": { "esphome": {
"offline": "Desconnectat", "offline": "Desconnectat",
@@ -244,10 +251,10 @@
"seed": "Sembrat" "seed": "Sembrat"
}, },
"qbittorrent": { "qbittorrent": {
"download": "Download", "download": "Baixada",
"upload": "Upload", "upload": "Pujada",
"leech": "Leech", "leech": "Sangonera",
"seed": "Seed" "seed": "Sembrat"
}, },
"qnap": { "qnap": {
"cpuUsage": "Ús de CPU", "cpuUsage": "Ús de CPU",
@@ -258,44 +265,44 @@
"invalid": "No vàlid" "invalid": "No vàlid"
}, },
"deluge": { "deluge": {
"download": "Download", "download": "Baixada",
"upload": "Upload", "upload": "Pujada",
"leech": "Leech", "leech": "Sangonera",
"seed": "Seed" "seed": "Sembrat"
}, },
"develancacheui": { "develancacheui": {
"cachehitbytes": "Bytes trobats a la memòria cau", "cachehitbytes": "Bytes trobats a la memòria cau",
"cachemissbytes": "Bytes no trobats a la memòria cau" "cachemissbytes": "Bytes no trobats a la memòria cau"
}, },
"downloadstation": { "downloadstation": {
"download": "Download", "download": "Baixada",
"upload": "Upload", "upload": "Pujada",
"leech": "Leech", "leech": "Sangonera",
"seed": "Seed" "seed": "Sembrat"
}, },
"sonarr": { "sonarr": {
"wanted": "Volgut", "wanted": "Pendents",
"queued": "En cua", "queued": "En cua",
"series": "Series", "series": "Sèries",
"queue": "Queue", "queue": "Cua",
"unknown": "Unknown" "unknown": "Desconegut"
}, },
"radarr": { "radarr": {
"wanted": "Wanted", "wanted": "Pendents",
"missing": "Falten", "missing": "Falten",
"queued": "Queued", "queued": "En cua",
"movies": "Movies", "movies": "Pel·lícules",
"queue": "Queue", "queue": "Cua",
"unknown": "Unknown" "unknown": "Desconegut"
}, },
"lidarr": { "lidarr": {
"wanted": "Wanted", "wanted": "Pendents",
"queued": "Queued", "queued": "En cua",
"artists": "Artistes" "artists": "Artistes"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "Pendents",
"queued": "Queued", "queued": "En cua",
"books": "Llibres" "books": "Llibres"
}, },
"bazarr": { "bazarr": {
@@ -308,16 +315,16 @@
"available": "Disponible" "available": "Disponible"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "Pendent",
"approved": "Approved", "approved": "Aprovat",
"available": "Available", "available": "Disponible",
"completed": "Completed", "completed": "Completat",
"processing": "Processing", "processing": "Processant",
"issues": "Open Issues" "issues": "Incidències"
}, },
"netalertx": { "netalertx": {
"total": "Total", "total": "Total",
"connected": "Connected", "connected": "Connectat",
"new_devices": "Nous dispositius", "new_devices": "Nous dispositius",
"down_alerts": "Alertes de caigudes" "down_alerts": "Alertes de caigudes"
}, },
@@ -328,18 +335,18 @@
"gravity": "Gravetat" "gravity": "Gravetat"
}, },
"adguard": { "adguard": {
"queries": "Queries", "queries": "Consultes",
"blocked": "Blocked", "blocked": "Bloquejat",
"filtered": "Filtrat", "filtered": "Filtrat",
"latency": "Latència" "latency": "Latència"
}, },
"speedtest": { "speedtest": {
"upload": "Upload", "upload": "Pujada",
"download": "Download", "download": "Baixada",
"ping": "Ping" "ping": "Latència"
}, },
"portainer": { "portainer": {
"running": "Running", "running": "En execució",
"stopped": "Aturat", "stopped": "Aturat",
"total": "Total" "total": "Total"
}, },
@@ -357,16 +364,16 @@
"address": "Adreça", "address": "Adreça",
"expires": "Caduca", "expires": "Caduca",
"never": "Mai", "never": "Mai",
"user": "User", "user": "Usuari",
"hostname": "Hostname", "hostname": "Nom d'hoste",
"name": "Name", "name": "Nom",
"client_version": "Client Version", "client_version": "Versió de client",
"os": "OS", "os": "OS",
"created": "Created", "created": "Creat",
"authorized": "Authorized", "authorized": "Autoritzat",
"is_external": "Is External", "is_external": "És extern",
"update_available": "Update Available", "update_available": "Actualització disponible",
"tags": "Tags", "tags": "Etiquetes",
"last_seen": "Vist per darrer cop", "last_seen": "Vist per darrer cop",
"now": "Ara", "now": "Ara",
"years": "{{number}}a", "years": "{{number}}a",
@@ -376,7 +383,7 @@
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Fa {{value}}", "ago": "Fa {{value}}",
"true": "Yes", "true": "",
"false": "No" "false": "No"
}, },
"technitium": { "technitium": {
@@ -453,8 +460,8 @@
"domain_count": "Dominis" "domain_count": "Dominis"
}, },
"medusa": { "medusa": {
"wanted": "Volguts", "wanted": "Pendents",
"queued": "Encuat", "queued": "En cua",
"series": "Sèries" "series": "Sèries"
}, },
"minecraft": { "minecraft": {
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Usuaris", "users": "Usuaris",
"loginsLast24H": "Inicis de sessió (24h)", "loginsLast24H": "Inicis de sessió (24h)",
"failedLoginsLast24H": "Errors d'inici de sessió (24h)" "failedLoginsLast24H": "Errors d'inici de sessió (24h)",
"authorizationsLast24H": "Autentificacions (últ. 24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
@@ -539,37 +547,37 @@
"61-day": "Pluja lleugera", "61-day": "Pluja lleugera",
"61-night": "Pluja lleugera", "61-night": "Pluja lleugera",
"63-day": "Pluja", "63-day": "Pluja",
"63-night": "Rain", "63-night": "Pluja",
"65-day": "Pluja intensa", "65-day": "Pluja intensa",
"65-night": "Heavy Rain", "65-night": "Pluja forta",
"66-day": "Pluja gelada", "66-day": "Pluja gelada",
"66-night": "Freezing Rain", "66-night": "Pluja gelada",
"67-day": "Freezing Rain", "67-day": "Pluja gelada",
"67-night": "Freezing Rain", "67-night": "Pluja gelada",
"71-day": "Neu lleugera", "71-day": "Neu lleugera",
"71-night": "Light Snow", "71-night": "Nevada lleugera",
"73-day": "Neu", "73-day": "Neu",
"73-night": "Snow", "73-night": "Nevada",
"75-day": "Neu intensa", "75-day": "Neu intensa",
"75-night": "Heavy Snow", "75-night": "Nevada forta",
"77-day": "Neu lleugera", "77-day": "Neu lleugera",
"77-night": "Snow Grains", "77-night": "Flocs de neu",
"80-day": "Plovisqueig", "80-day": "Plovisqueig",
"80-night": "Light Showers", "80-night": "Plugims suaus",
"81-day": "Xàfecs", "81-day": "Xàfecs",
"81-night": "Showers", "81-night": "Plugims",
"82-day": "Xàfecs intensos", "82-day": "Xàfecs intensos",
"82-night": "Heavy Showers", "82-night": "Plugims forts",
"85-day": "Xàfecs de neu", "85-day": "Xàfecs de neu",
"85-night": "Snow Showers", "85-night": "Plugims amb neu",
"86-day": "Snow Showers", "86-day": "Plugims amb neu",
"86-night": "Snow Showers", "86-night": "Plugims amb neu",
"95-day": "Tempesta", "95-day": "Tempesta",
"95-night": "Thunderstorm", "95-night": "Tormenta",
"96-day": "Tempesta amb calamarsa", "96-day": "Tempesta amb calamarsa",
"96-night": "Thunderstorm With Hail", "96-night": "Tormenta amb calamarsa",
"99-day": "Thunderstorm With Hail", "99-day": "Tormenta amb calamarsa",
"99-night": "Thunderstorm With Hail" "99-night": "Tormenta amb calamarsa"
}, },
"homebridge": { "homebridge": {
"available_update": "Sistema", "available_update": "Sistema",
@@ -578,18 +586,18 @@
"up_to_date": "Actualitzat", "up_to_date": "Actualitzat",
"child_bridges": "Ponts fills", "child_bridges": "Ponts fills",
"child_bridges_status": "{{ok}}/{{total}}", "child_bridges_status": "{{ok}}/{{total}}",
"up": "Up", "up": "En línia",
"pending": "Pending", "pending": "Pendent",
"down": "Down", "down": "Inactiu",
"ok": "Ok" "ok": "Ok"
}, },
"healthchecks": { "healthchecks": {
"new": "Nou", "new": "Nou",
"up": "Up", "up": "Actiu",
"grace": "En Període de gràcia", "grace": "En Període de gràcia",
"down": "Down", "down": "Inactiu",
"paused": "En pausa", "paused": "En pausa",
"status": "Status", "status": "Estat",
"last_ping": "Últim ping", "last_ping": "Últim ping",
"never": "Sense pings" "never": "Sense pings"
}, },
@@ -599,74 +607,74 @@
"containers_failed": "Error" "containers_failed": "Error"
}, },
"autobrr": { "autobrr": {
"approvedPushes": "Approved", "approvedPushes": "Aprovat",
"rejectedPushes": "Rebutjat", "rejectedPushes": "Rebutjat",
"filters": "Filtres", "filters": "Filtres",
"indexers": "Indexers" "indexers": "Indexadors"
}, },
"tubearchivist": { "tubearchivist": {
"downloads": "Queue", "downloads": "Cua",
"videos": "Vídeos", "videos": "Vídeos",
"channels": "Canals", "channels": "Canals",
"playlists": "Llistes de reproducció" "playlists": "Llistes de reproducció"
}, },
"truenas": { "truenas": {
"load": "Càrrega del sistema", "load": "Càrrega del sistema",
"uptime": "Uptime", "uptime": "Temps actiu",
"alerts": "Alerts" "alerts": "Alertes"
}, },
"pyload": { "pyload": {
"speed": "Velocitat", "speed": "Velocitat",
"active": "Active", "active": "Actiu",
"queue": "Queue", "queue": "Cua",
"total": "Total" "total": "Total"
}, },
"gluetun": { "gluetun": {
"public_ip": "IP Pública", "public_ip": "IP Pública",
"region": "Regió", "region": "Regió",
"country": "País", "country": "País",
"port_forwarded": "Port Forwarded" "port_forwarded": "Ports redirigits"
}, },
"hdhomerun": { "hdhomerun": {
"channels": "Channels", "channels": "Canals",
"hd": "HD", "hd": "HD",
"tunerCount": "Sintonitzadors", "tunerCount": "Sintonitzadors",
"channelNumber": "Canal", "channelNumber": "Canal",
"channelNetwork": "Xarxa", "channelNetwork": "Xarxa",
"signalStrength": "Intensitat", "signalStrength": "Intensitat",
"signalQuality": "Qualitat", "signalQuality": "Qualitat",
"symbolQuality": "Quality", "symbolQuality": "Qualitat",
"networkRate": "Bitrate", "networkRate": "Taxa de bits",
"clientIP": "Client" "clientIP": "Client"
}, },
"scrutiny": { "scrutiny": {
"passed": "Aprovat", "passed": "Aprovat",
"failed": "Failed", "failed": "Error",
"unknown": "Unknown" "unknown": "Desconegut"
}, },
"paperlessngx": { "paperlessngx": {
"inbox": "Safata d'entrada", "inbox": "Safata d'entrada",
"total": "Total" "total": "Total"
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Organitzacions",
"sites": "Sites", "sites": "Llocs",
"resources": "Resources", "resources": "Recursos",
"targets": "Targets", "targets": "Objectius",
"traffic": "Traffic", "traffic": "Tràfic",
"in": "In", "in": "Dins",
"out": "Out" "out": "Fora"
}, },
"peanut": { "peanut": {
"battery_charge": "Càrrega de la bateria", "battery_charge": "Càrrega de la bateria",
"ups_load": "Càrrega del SAI", "ups_load": "Càrrega del SAI",
"ups_status": "Estat del SAI", "ups_status": "Estat del SAI",
"online": "Online", "online": "En línia",
"on_battery": "En Bateria", "on_battery": "En Bateria",
"low_battery": "Bateria Baixa" "low_battery": "Bateria Baixa"
}, },
"nextdns": { "nextdns": {
"wait": "Please Wait", "wait": "Espereu si us plau",
"no_devices": "No s'han rebut dades del Dispositiu" "no_devices": "No s'han rebut dades del Dispositiu"
}, },
"mikrotik": { "mikrotik": {
@@ -759,8 +767,8 @@
}, },
"mylar": { "mylar": {
"series": "Sèries", "series": "Sèries",
"issues": "Problemes", "issues": "Números",
"wanted": "Volguts" "wanted": "Pendents"
}, },
"photoprism": { "photoprism": {
"albums": "Àlbums", "albums": "Àlbums",
@@ -854,37 +862,37 @@
"jdownloader": { "jdownloader": {
"downloadCount": "Cua", "downloadCount": "Cua",
"downloadBytesRemaining": "Restant", "downloadBytesRemaining": "Restant",
"downloadTotalBytes": "Size", "downloadTotalBytes": "Mida",
"downloadSpeed": "Speed" "downloadSpeed": "Velocitat"
}, },
"kavita": { "kavita": {
"seriesCount": "Series", "seriesCount": "Sèries",
"totalFiles": "Files" "totalFiles": "Fitxers"
}, },
"azuredevops": { "azuredevops": {
"result": "Resultat", "result": "Resultat",
"status": "Status", "status": "Estat",
"buildId": "Id de compilació", "buildId": "Id de compilació",
"succeeded": "Amb èxit", "succeeded": "Amb èxit",
"notStarted": "No Iniciat", "notStarted": "No Iniciat",
"failed": "Failed", "failed": "Error",
"canceled": "Cancel·lat", "canceled": "Cancel·lat",
"inProgress": "En curs", "inProgress": "En curs",
"totalPrs": "PRs Totals", "totalPrs": "PRs Totals",
"myPrs": "Les meves PRs", "myPrs": "Les meves PRs",
"approved": "Approved" "approved": "Aprovat"
}, },
"gamedig": { "gamedig": {
"status": "Status", "status": "Estat",
"online": "Online", "online": "En línia",
"offline": "Offline", "offline": "Desconnectat",
"name": "Nom", "name": "Nom",
"map": "Mapa", "map": "Mapa",
"currentPlayers": "Jugadors actuals", "currentPlayers": "Jugadors actuals",
"players": "Players", "players": "Jugadors",
"maxPlayers": "Màxim de jugadors", "maxPlayers": "Màxim de jugadors",
"bots": "Bots", "bots": "Bots",
"ping": "Ping" "ping": "Latència"
}, },
"urbackup": { "urbackup": {
"ok": "Ok", "ok": "Ok",
@@ -894,39 +902,39 @@
}, },
"mealie": { "mealie": {
"recipes": "Receptes", "recipes": "Receptes",
"users": "Users", "users": "Usuaris",
"categories": "Categories", "categories": "Categories",
"tags": "Etiquetes" "tags": "Etiquetes"
}, },
"openmediavault": { "openmediavault": {
"downloading": "Descarregant", "downloading": "Descarregant",
"total": "Total", "total": "Total",
"running": "Running", "running": "En execució",
"stopped": "Stopped", "stopped": "Aturat",
"passed": "Passed", "passed": "Aprovat",
"failed": "Failed" "failed": "Error"
}, },
"openwrt": { "openwrt": {
"uptime": "Uptime", "uptime": "Temps actiu",
"cpuLoad": "Càrrega mitjana de CPU (5min)", "cpuLoad": "Càrrega mitjana de CPU (5min)",
"up": "Up", "up": "En línia",
"down": "Down", "down": "Desconnectat",
"bytesTx": "Enviat", "bytesTx": "Enviat",
"bytesRx": "Received" "bytesRx": "Rebut"
}, },
"uptimerobot": { "uptimerobot": {
"status": "Status", "status": "Estat",
"uptime": "Uptime", "uptime": "Temps actiu",
"lastDown": "Darrera Inactivitat", "lastDown": "Darrera Inactivitat",
"downDuration": "Duració d'Inactivitat", "downDuration": "Duració d'Inactivitat",
"sitesUp": "Sites Up", "sitesUp": "Pàgines actives",
"sitesDown": "Sites Down", "sitesDown": "Pàgines inactives",
"paused": "Paused", "paused": "Pausat",
"notyetchecked": "Sense verificar", "notyetchecked": "Sense verificar",
"up": "Up", "up": "En línia",
"seemsdown": "Sembla caigut", "seemsdown": "Sembla caigut",
"down": "Down", "down": "Desconnectat",
"unknown": "Unknown" "unknown": "Desconegut"
}, },
"calendar": { "calendar": {
"inCinemas": "En cines", "inCinemas": "En cines",
@@ -934,7 +942,7 @@
"digitalRelease": "Estrena digital", "digitalRelease": "Estrena digital",
"noEventsToday": "Cap esdeveniment per avui!", "noEventsToday": "Cap esdeveniment per avui!",
"noEventsFound": "No s'han trobat esdeveniments", "noEventsFound": "No s'han trobat esdeveniments",
"errorWhenLoadingData": "Error when loading calendar data" "errorWhenLoadingData": "Error carregant dades del calendari"
}, },
"romm": { "romm": {
"platforms": "Plataformes", "platforms": "Plataformes",
@@ -945,50 +953,50 @@
"totalfilesize": "Tamany total" "totalfilesize": "Tamany total"
}, },
"mailcow": { "mailcow": {
"domains": "Domains", "domains": "Dominis",
"mailboxes": "Bústies", "mailboxes": "Bústies",
"mails": "Correus", "mails": "Correus",
"storage": "Storage" "storage": "Emmagatzematge"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Items gestionats",
"episodesHandled": "Episodes Handled", "episodesHandled": "Episodis gestionats",
"moviesHandled": "Movies Handled", "moviesHandled": "Pel·lícules gestionades",
"showsHandled": "Shows Handled", "showsHandled": "Sèries gestionades",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Temporades gestionades",
"reclaimable": "Reclaimable", "reclaimable": "Reclamable",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Pel·lícula reclamable",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Sèrie reclamable",
"totalCapacity": "Total Capacity" "totalCapacity": "Capacitat total"
}, },
"netdata": { "netdata": {
"warnings": "Avisos", "warnings": "Avisos",
"criticals": "Crítics" "criticals": "Crítics"
}, },
"ntfy": { "ntfy": {
"title": "Title", "title": "Títol",
"priority": "Priority", "priority": "Prioritat",
"lastReceived": "Last Received", "lastReceived": "Rebut per últim cop",
"message": "Message", "message": "Missatge",
"tags": "Tags", "tags": "Etiquetes",
"none": "None", "none": "Cap",
"min": "Min", "min": "Min",
"low": "Low", "low": "Baix",
"default": "Default", "default": "Per defecte",
"high": "High", "high": "Alt",
"urgent": "Urgent" "urgent": "Urgent"
}, },
"plantit": { "plantit": {
"events": "Esdeveniments", "events": "Esdeveniments",
"plants": "Plantes", "plants": "Plantes",
"photos": "Photos", "photos": "Fotos",
"species": "Espècies" "species": "Espècies"
}, },
"gitea": { "gitea": {
"notifications": "Notificacions", "notifications": "Notificacions",
"issues": "Issues", "issues": "Incidències",
"pulls": "Sol·licitud de Canvis", "pulls": "Sol·licitud de Canvis",
"repositories": "Repositories" "repositories": "Repositoris"
}, },
"stash": { "stash": {
"scenes": "Escenes", "scenes": "Escenes",
@@ -1002,13 +1010,13 @@
"galleries": "Biblioteques", "galleries": "Biblioteques",
"performers": "Intèrprets", "performers": "Intèrprets",
"studios": "Estudis", "studios": "Estudis",
"movies": "Movies", "movies": "Pel·lícules",
"tags": "Tags", "tags": "Etiquetes",
"oCount": "O Count" "oCount": "O Count"
}, },
"tandoor": { "tandoor": {
"users": "Users", "users": "Usuaris",
"recipes": "Recipes", "recipes": "Receptes",
"keywords": "Paraules claus" "keywords": "Paraules claus"
}, },
"homebox": { "homebox": {
@@ -1016,17 +1024,17 @@
"totalWithWarranty": "Amb Garantia", "totalWithWarranty": "Amb Garantia",
"locations": "Ubicacions", "locations": "Ubicacions",
"labels": "Etiquetes", "labels": "Etiquetes",
"users": "Users", "users": "Usuaris",
"totalValue": "Valor total" "totalValue": "Valor total"
}, },
"crowdsec": { "crowdsec": {
"alerts": "Alerts", "alerts": "Alertes",
"bans": "Prohibicions" "bans": "Prohibicions"
}, },
"wgeasy": { "wgeasy": {
"connected": "Connected", "connected": "Connectat",
"enabled": "Enabled", "enabled": "Habilitat",
"disabled": "Disabled", "disabled": "Deshabilitat",
"total": "Total" "total": "Total"
}, },
"swagdashboard": { "swagdashboard": {
@@ -1036,9 +1044,9 @@
"banned": "Bloquejat" "banned": "Bloquejat"
}, },
"myspeed": { "myspeed": {
"ping": "Ping", "ping": "Latència",
"download": "Download", "download": "Baixada",
"upload": "Upload" "upload": "Pujada"
}, },
"stocks": { "stocks": {
"stocks": "Accions", "stocks": "Accions",
@@ -1116,7 +1124,7 @@
}, },
"gitlab": { "gitlab": {
"groups": "Grups", "groups": "Grups",
"issues": "Problemes", "issues": "Incidències",
"merges": "Merge Requests", "merges": "Merge Requests",
"projects": "Projectes" "projects": "Projectes"
}, },
@@ -1135,120 +1143,120 @@
"tags": "Etiquetes" "tags": "Etiquetes"
}, },
"slskd": { "slskd": {
"slskStatus": "Network", "slskStatus": "Xarxa",
"connected": "Connected", "connected": "Connectat",
"disconnected": "Disconnected", "disconnected": "Desconnectat",
"updateStatus": "Update", "updateStatus": "Actualitza",
"update_yes": "Available", "update_yes": "Disponible",
"update_no": "Up to Date", "update_no": "Actualitzat",
"downloads": "Downloads", "downloads": "Descàrregues",
"uploads": "Uploads", "uploads": "Pujades",
"sharedFiles": "Files" "sharedFiles": "Fitxers"
}, },
"jellystat": { "jellystat": {
"songs": "Songs", "songs": "Cançons",
"movies": "Movies", "movies": "Pel·lícules",
"episodes": "Episodes", "episodes": "Episodis",
"other": "Other" "other": "Altres"
}, },
"checkmk": { "checkmk": {
"serviceErrors": "Service issues", "serviceErrors": "Errors de servei",
"hostErrors": "Host issues" "hostErrors": "Errors de l'hoste"
}, },
"komodo": { "komodo": {
"total": "Total", "total": "Total",
"running": "Running", "running": "En execució",
"stopped": "Stopped", "stopped": "Aturat",
"down": "Down", "down": "Fora de servei",
"unhealthy": "Unhealthy", "unhealthy": "No saludable",
"unknown": "Unknown", "unknown": "Desconegut",
"servers": "Servers", "servers": "Servidors",
"stacks": "Stacks", "stacks": "Stacks",
"containers": "Containers" "containers": "Contenidors"
}, },
"filebrowser": { "filebrowser": {
"available": "Available", "available": "Disponible",
"used": "Used", "used": "Utilitzat",
"total": "Total" "total": "Total"
}, },
"wallos": { "wallos": {
"activeSubscriptions": "Subscriptions", "activeSubscriptions": "Subscripcions",
"thisMonthlyCost": "This Month", "thisMonthlyCost": "Aquest mes",
"nextMonthlyCost": "Next Month", "nextMonthlyCost": "Mes que ve",
"previousMonthlyCost": "Prev. Month", "previousMonthlyCost": "Mes passat",
"nextRenewingSubscription": "Next Payment" "nextRenewingSubscription": "Proper pagament"
}, },
"unraid": { "unraid": {
"STARTED": "Started", "STARTED": "Iniciat",
"STOPPED": "Stopped", "STOPPED": "Aturat",
"NEW_ARRAY": "New Array", "NEW_ARRAY": "Nou array",
"RECON_DISK": "Reconstructing Disk", "RECON_DISK": "Reconstruint disc",
"DISABLE_DISK": "Disk Disabled", "DISABLE_DISK": "Disc desactivat",
"SWAP_DSBL": "Swap Disable", "SWAP_DSBL": "Swap desactivat",
"INVALID_EXPANSION": "Invalid Expansion", "INVALID_EXPANSION": "Expansió invàlida",
"PARITY_NOT_BIGGEST": "Parity Not Biggest", "PARITY_NOT_BIGGEST": "Paritat no del més gran",
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks", "TOO_MANY_MISSING_DISKS": "Falten masses discs",
"NEW_DISK_TOO_SMALL": "New Disk Too Small", "NEW_DISK_TOO_SMALL": "Nou disc massa petit",
"NO_DATA_DISKS": "No Data Disks", "NO_DATA_DISKS": "Sense discs de dades",
"notifications": "Notifications", "notifications": "Notificacions",
"status": "Status", "status": "Estat",
"cpu": "CPU", "cpu": "CPU",
"memoryUsed": "Memory Used", "memoryUsed": "Memòria emprada",
"memoryAvailable": "Memory Available", "memoryAvailable": "Memòria disponible",
"arrayUsed": "Array Used", "arrayUsed": "Array en ús",
"arrayFree": "Array Free", "arrayFree": "Array lliure",
"poolUsed": "{{pool}} Used", "poolUsed": "{{pool}} en ús",
"poolFree": "{{pool}} Free" "poolFree": "{{pool}} lliure"
}, },
"backrest": { "backrest": {
"num_plans": "Plans", "num_plans": "Plans",
"num_success_30": "Successes", "num_success_30": "Èxits",
"num_failure_30": "Failures", "num_failure_30": "Fallades",
"num_success_latest": "Succeeding", "num_success_latest": "Triomfant",
"num_failure_latest": "Failing", "num_failure_latest": "Fallant",
"bytes_added_30": "Bytes Added" "bytes_added_30": "Bytes afegits"
}, },
"yourspotify": { "yourspotify": {
"songs": "Songs", "songs": "Cançons",
"time": "Time", "time": "Temps",
"artists": "Artists" "artists": "Artistes"
}, },
"arcane": { "arcane": {
"containers": "Containers", "containers": "Contenidors",
"images": "Images", "images": "Imatges",
"image_updates": "Image Updates", "image_updates": "Actualitzacions d'imatges",
"images_unused": "Unused", "images_unused": "Sense utilitzar",
"environment_required": "Environment ID Required" "environment_required": "ID d'entorn requerit"
}, },
"dockhand": { "dockhand": {
"running": "Running", "running": "En execució",
"stopped": "Stopped", "stopped": "Aturats",
"cpu": "CPU", "cpu": "CPU",
"memory": "Memory", "memory": "Memòria",
"images": "Images", "images": "Imatges",
"volumes": "Volumes", "volumes": "Volums",
"events_today": "Events Today", "events_today": "Esdeveniments avui",
"pending_updates": "Pending Updates", "pending_updates": "Actualitzacions pendents",
"stacks": "Stacks", "stacks": "Stacks",
"paused": "Paused", "paused": "Pausats",
"total": "Total", "total": "Total",
"environment_not_found": "Environment Not Found" "environment_not_found": "Entorn no trobat"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "Menjat",
"burned": "Burned", "burned": "Cremat",
"remaining": "Remaining", "remaining": "Restant",
"steps": "Steps" "steps": "Passes"
}, },
"syncthing": { "syncthing": {
"connected": "Connected", "connected": "Connectat",
"synced": "Synced", "synced": "Sincronitzat",
"errors": "Errors", "errors": "Errors",
"storage": "Storage" "storage": "Emmagatzematge"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Pendents",
"queued": "Queued", "queued": "En cua",
"leagues": "Leagues" "leagues": "Lligues"
} }
} }
+36 -28
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Odhlásit se"
}, },
"widget": { "widget": {
"missing_type": "Chybí typ widgetu: {{type}}", "missing_type": "Chybí typ widgetu: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Počkejte prosím" "wait": "Počkejte prosím"
}, },
"search": { "search": {
"placeholder": "Hledat…" "placeholder": "Hledat…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Využití procesoru", "cpu": "Využití procesoru",
@@ -75,13 +77,13 @@
"no_data": "Nejsou k dispozici žádná data úložiště" "no_data": "Nejsou k dispozici žádná data úložiště"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Úlohy",
"stored": "Stored", "stored": "Uloženo",
"lastBackup": "Last Run", "lastBackup": "Naposledy spuštěno",
"nextRun": "Next Run", "nextRun": "Další spuštění",
"running": "Running", "running": "Běží",
"warnings": "Warnings", "warnings": "Upozornění",
"errors": "Errors" "errors": "Chyby"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -97,7 +99,12 @@
"unhealthy": "Nezdravý", "unhealthy": "Nezdravý",
"not_found": "Nenalezen", "not_found": "Nenalezen",
"exited": "Ukončen", "exited": "Ukončen",
"partial": "Částečný" "partial": "Částečný",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Chyba", "error": "Chyba",
@@ -133,7 +140,7 @@
"series": "Seriály", "series": "Seriály",
"episodes": "Epizody", "episodes": "Epizody",
"songs": "Skladby", "songs": "Skladby",
"albums": "Albums" "albums": "Alba"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Uživatelé", "users": "Uživatelé",
"loginsLast24H": "Příhlášení (24h)", "loginsLast24H": "Příhlášení (24h)",
"failedLoginsLast24H": "Neúspěšná přihlášení (24h)" "failedLoginsLast24H": "Neúspěšná přihlášení (24h)",
"authorizationsLast24H": "Autorizace (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "Využití paměti", "mem": "Využití paměti",
@@ -951,15 +959,15 @@
"storage": "Úložiště" "storage": "Úložiště"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Zpracované položky",
"episodesHandled": "Episodes Handled", "episodesHandled": "Zpracované epizody",
"moviesHandled": "Movies Handled", "moviesHandled": "Zpracované filmy",
"showsHandled": "Shows Handled", "showsHandled": "Zpracované seriály",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Zpracované série",
"reclaimable": "Reclaimable", "reclaimable": "Uvolnitelné",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Uvolnitelné filmy",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Uvolnitelné seriály",
"totalCapacity": "Total Capacity" "totalCapacity": "Celková kapacita"
}, },
"netdata": { "netdata": {
"warnings": "Upozornění", "warnings": "Upozornění",
@@ -1241,14 +1249,14 @@
"steps": "Kroky" "steps": "Kroky"
}, },
"syncthing": { "syncthing": {
"connected": "Connected", "connected": "Připojeno",
"synced": "Synced", "synced": "Synchronizováno",
"errors": "Errors", "errors": "Chyby",
"storage": "Storage" "storage": "Úložiště"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Požadované",
"queued": "Queued", "queued": "Ve frontě",
"leagues": "Leagues" "leagues": "Ligy"
} }
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Vent venligst" "wait": "Vent venligst"
}, },
"search": { "search": {
"placeholder": "Søg…" "placeholder": "Søg…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Usund", "unhealthy": "Usund",
"not_found": "Ikke Fundet", "not_found": "Ikke Fundet",
"exited": "Forladt", "exited": "Forladt",
"partial": "Delvis" "partial": "Delvis",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Login (24 timer)", "loginsLast24H": "Login (24 timer)",
"failedLoginsLast24H": "Mislykkede logins (24 timer)" "failedLoginsLast24H": "Mislykkede logins (24 timer)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+48 -40
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Abmelden"
}, },
"widget": { "widget": {
"missing_type": "Fehlender Widget-Typ: {{type}}", "missing_type": "Fehlender Widget-Typ: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Bitte warten" "wait": "Bitte warten"
}, },
"search": { "search": {
"placeholder": "Suche…" "placeholder": "Suche…",
"provider": "Suchmaschine: {{name}}",
"select_provider": "Suchmaschine auswählen"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -75,13 +77,13 @@
"no_data": "Keine Speicherdaten verfügbar" "no_data": "Keine Speicherdaten verfügbar"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Aufgaben",
"stored": "Stored", "stored": "Gespeichert",
"lastBackup": "Last Run", "lastBackup": "Zuletzt ausgeführt",
"nextRun": "Next Run", "nextRun": "Nächste Ausführung",
"running": "Running", "running": "Wird ausgeführt",
"warnings": "Warnings", "warnings": "Warnungen",
"errors": "Errors" "errors": "Fehler"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -97,7 +99,12 @@
"unhealthy": "Fehlerhaft", "unhealthy": "Fehlerhaft",
"not_found": "Nicht gefunden", "not_found": "Nicht gefunden",
"exited": "Beendet", "exited": "Beendet",
"partial": "Teilweise" "partial": "Teilweise",
"created": "Erstellt am",
"paused": "Pausiert",
"restarting": "Wird neu gestartet",
"removing": "Wird entfernt",
"dead": "Tot"
}, },
"ping": { "ping": {
"error": "Fehler", "error": "Fehler",
@@ -133,7 +140,7 @@
"series": "Serien", "series": "Serien",
"episodes": "Episoden", "episodes": "Episoden",
"songs": "Titel", "songs": "Titel",
"albums": "Albums" "albums": "Alben"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -357,15 +364,15 @@
"address": "Adresse", "address": "Adresse",
"expires": "Läuft ab", "expires": "Läuft ab",
"never": "Nie", "never": "Nie",
"user": "User", "user": "Benutzer",
"hostname": "Hostname", "hostname": "Hostname",
"name": "Name", "name": "Name",
"client_version": "Client Version", "client_version": "Client-Version",
"os": "OS", "os": "Betriebssystem",
"created": "Created", "created": "Erstellt am",
"authorized": "Authorized", "authorized": "Autorisiert",
"is_external": "Is External", "is_external": "Ist extern",
"update_available": "Update Available", "update_available": "Aktualisierung verfügbar",
"tags": "Tags", "tags": "Tags",
"last_seen": "Zuletzt gesehen", "last_seen": "Zuletzt gesehen",
"now": "Jetzt", "now": "Jetzt",
@@ -376,8 +383,8 @@
"minutes": "{{number}}min", "minutes": "{{number}}min",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "Vor {{value}}", "ago": "Vor {{value}}",
"true": "Yes", "true": "Ja",
"false": "No" "false": "Nein"
}, },
"technitium": { "technitium": {
"totalQueries": "Anfragen", "totalQueries": "Anfragen",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Benutzer", "users": "Benutzer",
"loginsLast24H": "Anmeldungen (24 h)", "loginsLast24H": "Anmeldungen (24 h)",
"failedLoginsLast24H": "Fehlversuche (24 h)" "failedLoginsLast24H": "Fehlversuche (24 h)",
"authorizationsLast24H": "Autorisierungen (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "RAM", "mem": "RAM",
@@ -480,7 +488,7 @@
"vms": "VMs" "vms": "VMs"
}, },
"pulse": { "pulse": {
"nodes": "Nodes", "nodes": "Knoten",
"vms": "VMs", "vms": "VMs",
"lxcs": "LXCs" "lxcs": "LXCs"
}, },
@@ -649,13 +657,13 @@
"total": "Total" "total": "Total"
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Organisationen",
"sites": "Seiten", "sites": "Seiten",
"resources": "Ressourcen", "resources": "Ressourcen",
"targets": "Ziele", "targets": "Ziele",
"traffic": "Traffic", "traffic": "Traffic",
"in": "In", "in": "Eingehend",
"out": "Out" "out": "Ausgehend"
}, },
"peanut": { "peanut": {
"battery_charge": "Akkuladung", "battery_charge": "Akkuladung",
@@ -951,27 +959,27 @@
"storage": "Speicher" "storage": "Speicher"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Items bearbeitet",
"episodesHandled": "Episodes Handled", "episodesHandled": "Episoden behandelt",
"moviesHandled": "Movies Handled", "moviesHandled": "Filme behandelt",
"showsHandled": "Shows Handled", "showsHandled": "Serien behandelt",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Staffeln bearbeitet",
"reclaimable": "Reclaimable", "reclaimable": "Zurückforderbar",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Film zurückforderbar",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Serie zurückforderbar",
"totalCapacity": "Total Capacity" "totalCapacity": "Gesamte Kapazität"
}, },
"netdata": { "netdata": {
"warnings": "Warnungen", "warnings": "Warnungen",
"criticals": "Kritisch" "criticals": "Kritisch"
}, },
"ntfy": { "ntfy": {
"title": "Title", "title": "Titel",
"priority": "Priority", "priority": "Priorität",
"lastReceived": "Last Received", "lastReceived": "Zuletzt erhalten",
"message": "Message", "message": "Nachricht",
"tags": "Tags", "tags": "Schlagwörter",
"none": "None", "none": "Keine",
"min": "Min", "min": "Min",
"low": "Low", "low": "Low",
"default": "Default", "default": "Default",
+289 -281
View File
@@ -14,14 +14,14 @@
"date": "{{value, date}}", "date": "{{value, date}}",
"relativeDate": "{{value, relativeDate}}", "relativeDate": "{{value, relativeDate}}",
"duration": "{{value, duration}}", "duration": "{{value, duration}}",
"months": "mo", "months": "Μ",
"days": "d", "days": "μ",
"hours": "h", "hours": "ω",
"minutes": "m", "minutes": "λ",
"seconds": "s" "seconds": "δ"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Αποσύνδεση"
}, },
"widget": { "widget": {
"missing_type": "Λείπει ο τύπος widget: {{type}}", "missing_type": "Λείπει ο τύπος widget: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Παρακαλώ περιμένετε" "wait": "Παρακαλώ περιμένετε"
}, },
"search": { "search": {
"placeholder": "Αναζήτηση…" "placeholder": "Αναζήτηση…",
"provider": "Μηχανή αναζήτησης: {{name}}",
"select_provider": "Επιλέξτε τις μηχανές αναζήτησης"
}, },
"resources": { "resources": {
"cpu": "Επεξεργαστής", "cpu": "Επεξεργαστής",
@@ -64,24 +66,24 @@
"wlan_devices": "WLAN Συσκευές", "wlan_devices": "WLAN Συσκευές",
"lan_users": "LAN Χρήστες", "lan_users": "LAN Χρήστες",
"wlan_users": "WLAN Χρήστες", "wlan_users": "WLAN Χρήστες",
"up": "UP", "up": "ΕΠΑΝΩ",
"down": "ΚΑΤΩ", "down": "ΚΑΤΩ",
"wait": "Please wait", "wait": "Παρακαλώ περιμένετε",
"empty_data": "Άγνωστη κατάσταση υποσυστήματος" "empty_data": "Άγνωστη κατάσταση υποσυστήματος"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Υγειές",
"degraded": "Degraded", "degraded": "Degraded",
"no_data": "No storage data available" "no_data": "Δεν υπάρχουν διαθέσιμα δεδομένα χώρου αποθήκευσης"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Εργασίες",
"stored": "Stored", "stored": "Αποθηκευμένα",
"lastBackup": "Last Run", "lastBackup": "Τελευταία εκτέλεση",
"nextRun": "Next Run", "nextRun": "Επόμενη εκτέλεση",
"running": "Running", "running": "Εκτελείται",
"warnings": "Warnings", "warnings": "Προειδοποιήσεις",
"errors": "Errors" "errors": "Σφάλματα"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -97,10 +99,15 @@
"unhealthy": "Άρρωστο", "unhealthy": "Άρρωστο",
"not_found": "Δεν βρέθηκε", "not_found": "Δεν βρέθηκε",
"exited": "Έκλεισε", "exited": "Έκλεισε",
"partial": "Μερικό" "partial": "Μερικό",
"created": "Δημιουργήθηκε",
"paused": "Σε παύση",
"restarting": "Γίνεται επανεκκίνηση",
"removing": "Αφαιρείται",
"dead": "Νεκρό"
}, },
"ping": { "ping": {
"error": "Error", "error": "Σφάλμα",
"ping": "Ping", "ping": "Ping",
"down": "Ping down", "down": "Ping down",
"up": "Ping up", "up": "Ping up",
@@ -108,11 +115,11 @@
}, },
"siteMonitor": { "siteMonitor": {
"http_status": "Κατάσταση HTTP", "http_status": "Κατάσταση HTTP",
"error": "Error", "error": "Σφάλμα",
"response": "Απόκριση", "response": "Απόκριση",
"down": "Down", "down": "Κάτω",
"up": "Up", "up": "Πάνω",
"not_available": "Not Available" "not_available": "Μη Διαθέσιμο"
}, },
"emby": { "emby": {
"playing": "Παίζει", "playing": "Παίζει",
@@ -125,22 +132,22 @@
"songs": "Τραγούδια" "songs": "Τραγούδια"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "Γίνεται αναπαραγωγή",
"transcoding": "Transcoding", "transcoding": "Επανακωδικοποίηση",
"bitrate": "Bitrate", "bitrate": "Ρυθμός μετάδοσης",
"no_active": "No Active Streams", "no_active": "Δεν Υπάρχουν Ενεργές Ροές",
"movies": "Movies", "movies": "Ταινίες",
"series": "Series", "series": "Σειρές",
"episodes": "Episodes", "episodes": "Επεισόδια",
"songs": "Songs", "songs": "Τραγούδια",
"albums": "Albums" "albums": "Άλμπουμ"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Εκτός σύνδεσης",
"offline_alt": "Offline", "offline_alt": "Χωρίς Σύνδεση",
"online": "Συνδεδεμένοι", "online": "Συνδεδεμένοι",
"total": "Total", "total": "Σύνολο",
"unknown": "Unknown" "unknown": "Άγνωστο"
}, },
"evcc": { "evcc": {
"pv_power": "Παραγωγή", "pv_power": "Παραγωγή",
@@ -161,7 +168,7 @@
"unread": "Μη Διαβασμένο" "unread": "Μη Διαβασμένο"
}, },
"fritzbox": { "fritzbox": {
"connectionStatus": "Status", "connectionStatus": "Κατάσταση",
"connectionStatusUnconfigured": "Μη Ρυθμισμένο", "connectionStatusUnconfigured": "Μη Ρυθμισμένο",
"connectionStatusConnecting": "Κατάσταση Σύνδεσης", "connectionStatusConnecting": "Κατάσταση Σύνδεσης",
"connectionStatusAuthenticating": "Ταυτοποίηση", "connectionStatusAuthenticating": "Ταυτοποίηση",
@@ -169,16 +176,16 @@
"connectionStatusDisconnecting": "Αποσύνδεση", "connectionStatusDisconnecting": "Αποσύνδεση",
"connectionStatusDisconnected": "Αποσυνδέθηκε", "connectionStatusDisconnected": "Αποσυνδέθηκε",
"connectionStatusConnected": "Συνδέθηκε", "connectionStatusConnected": "Συνδέθηκε",
"uptime": "Uptime", "uptime": "Συνεχής λειτουργία",
"maxDown": "Μέγιστο Download", "maxDown": "Μέγιστο Download",
"maxUp": "Μέγιστο Upload", "maxUp": "Μέγιστο Upload",
"down": "Down", "down": "Κάτω",
"up": "Up", "up": "Πάνω",
"received": "Ληφθέντα", "received": "Ληφθέντα",
"sent": "Απεσταλμένα", "sent": "Απεσταλμένα",
"externalIPAddress": "Εξωτερική IP", "externalIPAddress": "Εξωτερική IP",
"externalIPv6Address": "Ext. IPv6", "externalIPv6Address": "Εξωτ. IPv6",
"externalIPv6Prefix": "Ext. IPv6-Prefix" "externalIPv6Prefix": "Εξωτ. IPv6-Prefix"
}, },
"caddy": { "caddy": {
"upstreams": "Upstreams", "upstreams": "Upstreams",
@@ -196,10 +203,10 @@
"passes": "Περάσματα" "passes": "Περάσματα"
}, },
"tautulli": { "tautulli": {
"playing": "Playing", "playing": "Γίνεται αναπαραγωγή",
"transcoding": "Transcoding", "transcoding": "Γίνεται επανακωδικοποίηση",
"bitrate": "Bitrate", "bitrate": "Ρυθμός μετάδοσης",
"no_active": "No Active Streams", "no_active": "Δεν Υπάρχουν Ενεργές Ροές",
"plex_connection_error": "Έλεγχος Σύνδεσης με Plex" "plex_connection_error": "Έλεγχος Σύνδεσης με Plex"
}, },
"tracearr": { "tracearr": {
@@ -286,16 +293,16 @@
"queued": "Queued", "queued": "Queued",
"movies": "Movies", "movies": "Movies",
"queue": "Queue", "queue": "Queue",
"unknown": "Unknown" "unknown": "Άγνωστο"
}, },
"lidarr": { "lidarr": {
"wanted": "Wanted", "wanted": "Επιθυμητό",
"queued": "Queued", "queued": "Σε αναμονή",
"artists": "Καλλιτέχνες" "artists": "Καλλιτέχνες"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "Επιθυμητό",
"queued": "Queued", "queued": "Σε αναμονή",
"books": "Βιβλία" "books": "Βιβλία"
}, },
"bazarr": { "bazarr": {
@@ -308,16 +315,16 @@
"available": "Διαθέσιμο" "available": "Διαθέσιμο"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "Σε εκκρεμότητα",
"approved": "Approved", "approved": "Εγκρίθηκε",
"available": "Available", "available": "Διαθέσιμα",
"completed": "Completed", "completed": "Ολοκληρώθηκε",
"processing": "Processing", "processing": "Σε επεξεργασία",
"issues": "Open Issues" "issues": "Ανοιχτά θέματα"
}, },
"netalertx": { "netalertx": {
"total": "Total", "total": "Σύνολο",
"connected": "Connected", "connected": "Σε σύνδεση",
"new_devices": "Νέες συσκευές", "new_devices": "Νέες συσκευές",
"down_alerts": "Ειδοποιήσεις offline" "down_alerts": "Ειδοποιήσεις offline"
}, },
@@ -328,45 +335,45 @@
"gravity": "Gravity" "gravity": "Gravity"
}, },
"adguard": { "adguard": {
"queries": "Queries", "queries": "Ερωτήματα",
"blocked": "Blocked", "blocked": "Αποκλεισμένα",
"filtered": "Φιλτραρισμένα", "filtered": "Φιλτραρισμένα",
"latency": "Καθυστέρηση" "latency": "Καθυστέρηση"
}, },
"speedtest": { "speedtest": {
"upload": "Upload", "upload": "Αποστολή",
"download": "Download", "download": "Λήψη",
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Running", "running": "Εκτελείται",
"stopped": "Σταματημένο", "stopped": "Σταματημένο",
"total": "Total" "total": "Σύνολο"
}, },
"suwayomi": { "suwayomi": {
"download": "Downloaded", "download": "Ελήφθησαν",
"nondownload": "Non-Downloaded", "nondownload": "Δεν Ελήφθησαν",
"read": "Read", "read": "Διαβάστηκε",
"unread": "Unread", "unread": "Μη αναγνωσμένα",
"downloadedread": "Downloaded & Read", "downloadedread": "Ληφθέν & Αναγνωσμένο",
"downloadedunread": "Downloaded & Unread", "downloadedunread": "Ληφθέν & Μη Αναγνωσμένο",
"nondownloadedread": "Non-Downloaded & Read", "nondownloadedread": "Μη Ληφθέν & Αναγνωσμένο",
"nondownloadedunread": "Non-Downloaded & Unread" "nondownloadedunread": "Μη Ληφθέν & Μη Αναγνωσμένο"
}, },
"tailscale": { "tailscale": {
"address": "Διεύθυνση", "address": "Διεύθυνση",
"expires": "Λήγει", "expires": "Λήγει",
"never": "Ποτέ", "never": "Ποτέ",
"user": "User", "user": "Χρήστης",
"hostname": "Hostname", "hostname": "Όνομα υπολογιστή",
"name": "Name", "name": "Όνομα",
"client_version": "Client Version", "client_version": "Έκδοση Πελάτη",
"os": "OS", "os": "Λειτουργικό",
"created": "Created", "created": "Δημιουργήθηκε",
"authorized": "Authorized", "authorized": "Εξουσιοδοτημένο",
"is_external": "Is External", "is_external": "Είναι Εξωτερική",
"update_available": "Update Available", "update_available": "Διαθέσιμη ενημέρωση",
"tags": "Tags", "tags": "Ετικέτες",
"last_seen": "Τελευταία Σύνδεση", "last_seen": "Τελευταία Σύνδεση",
"now": "Τώρα", "now": "Τώρα",
"years": "{{number}}χρόνια", "years": "{{number}}χρόνια",
@@ -376,20 +383,20 @@
"minutes": "{{number}}λεπτά", "minutes": "{{number}}λεπτά",
"seconds": "{{number}}δευτερόλεπτα", "seconds": "{{number}}δευτερόλεπτα",
"ago": "{{value}} πρίν", "ago": "{{value}} πρίν",
"true": "Yes", "true": "Ναι",
"false": "No" "false": "Όχι"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Ερωτήματα",
"totalNoError": "Success", "totalNoError": "Επιτυχία",
"totalServerFailure": "Failures", "totalServerFailure": "Σφάλματα",
"totalNxDomain": "NX Domains", "totalNxDomain": "Τομείς NX",
"totalRefused": "Refused", "totalRefused": "Απορρίφθηκαν",
"totalAuthoritative": "Authoritative", "totalAuthoritative": "Εξουσιοδοτημένο",
"totalRecursive": "Recursive", "totalRecursive": "Recursive",
"totalCached": "Cached", "totalCached": "Cached",
"totalBlocked": "Blocked", "totalBlocked": "Αποκλεισμένα",
"totalDropped": "Dropped", "totalDropped": "Απορρίφθηκαν",
"totalClients": "Πελάτες" "totalClients": "Πελάτες"
}, },
"tdarr": { "tdarr": {
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Συνδέσεις (24h)", "loginsLast24H": "Συνδέσεις (24h)",
"failedLoginsLast24H": "Αποτυχημένες Συνδέσεις (24h)" "failedLoginsLast24H": "Αποτυχημένες Συνδέσεις (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
@@ -521,75 +529,75 @@
"2-day": "Αραιές Νεφώσεις", "2-day": "Αραιές Νεφώσεις",
"2-night": "Partly Cloudy", "2-night": "Partly Cloudy",
"3-day": "Συννεφιές", "3-day": "Συννεφιές",
"3-night": "Cloudy", "3-night": "Συννεφιά",
"45-day": "Ομίχλη", "45-day": "Ομίχλη",
"45-night": "Foggy", "45-night": "Ομίχλη",
"48-day": "Foggy", "48-day": "Ομίχλη",
"48-night": "Foggy", "48-night": "Ομίχλη",
"51-day": "Ψιλόβροχο", "51-day": "Ψιλόβροχο",
"51-night": "Light Drizzle", "51-night": "Ελαφρύ Ψιλόβροχο",
"53-day": "Ψιλόβροχο", "53-day": "Ψιλόβροχο",
"53-night": "Drizzle", "53-night": "Ψιλόβροχο",
"55-day": "Heavy Drizzle", "55-day": "Βαρύ Ψιλόβροχο",
"55-night": "Heavy Drizzle", "55-night": "Βαρύ Ψιλόβροχο",
"56-day": "Light Freezing Drizzle", "56-day": "Ελαφρύ Παγωμένο Ψιλόβροχο",
"56-night": "Light Freezing Drizzle", "56-night": "Ελαφρύ Παγωμένο Ψιλόβροχο",
"57-day": "Παγωμένο ψιχάλισμα", "57-day": "Παγωμένο ψιχάλισμα",
"57-night": "Freezing Drizzle", "57-night": "Παγωμένο ψιλόβροχο",
"61-day": "Ψιλόβροχο", "61-day": "Ψιλόβροχο",
"61-night": "Light Rain", "61-night": "Ελαφριά Βροχή",
"63-day": "Βροχή", "63-day": "Βροχή",
"63-night": "Rain", "63-night": "Βροχή",
"65-day": "Δυνατή βροχή", "65-day": "Δυνατή βροχή",
"65-night": "Heavy Rain", "65-night": "Δυνατή βροχή",
"66-day": "Παγωμένη βροχή", "66-day": "Παγωμένη βροχή",
"66-night": "Freezing Rain", "66-night": "Παγωμένη βροχή",
"67-day": "Freezing Rain", "67-day": "Παγωμένη βροχή",
"67-night": "Freezing Rain", "67-night": "Παγωμένη βροχή",
"71-day": "Ελαφριά Χιονόπτωση", "71-day": "Ελαφριά Χιονόπτωση",
"71-night": "Light Snow", "71-night": "Ελαφρά Χιονόπτωση",
"73-day": "Χιόνι", "73-day": "Χιόνι",
"73-night": "Snow", "73-night": "Χιόνι",
"75-day": "Ισχυρή χιονόπτωση", "75-day": "Ισχυρή χιονόπτωση",
"75-night": "Heavy Snow", "75-night": "Ισχυρή χιονόπτωση",
"77-day": "Κόκκοι Χιονιού", "77-day": "Κόκκοι Χιονιού",
"77-night": "Snow Grains", "77-night": "Χιονάκι",
"80-day": "Ασθενείς βροχές", "80-day": "Ασθενείς βροχές",
"80-night": "Light Showers", "80-night": "Ασθενείς βροχές",
"81-day": "Βροχοπτώσεις", "81-day": "Βροχοπτώσεις",
"81-night": "Showers", "81-night": "Βροχοπτώσεις",
"82-day": "Ισχυρές βροχοπτώσεις", "82-day": "Ισχυρές βροχοπτώσεις",
"82-night": "Heavy Showers", "82-night": "Ισχυρές βροχοπτώσεις",
"85-day": "Χιονοπτώσεις", "85-day": "Χιονοπτώσεις",
"85-night": "Snow Showers", "85-night": "Χιονοπτώσεις",
"86-day": "Snow Showers", "86-day": "Χιονοπτώσεις",
"86-night": "Snow Showers", "86-night": "Χιονοπτώσεις",
"95-day": "Καταιγίδα", "95-day": "Καταιγίδα",
"95-night": "Thunderstorm", "95-night": "Καταιγίδα",
"96-day": "Καταιγίδα Με Χαλάζι", "96-day": "Καταιγίδα Με Χαλάζι",
"96-night": "Thunderstorm With Hail", "96-night": "Καταιγίδα Με Χαλάζι",
"99-day": "Thunderstorm With Hail", "99-day": "Καταιγίδα Με Χαλάζι",
"99-night": "Thunderstorm With Hail" "99-night": "Καταιγίδα Με Χαλάζι"
}, },
"homebridge": { "homebridge": {
"available_update": "Σύστημα", "available_update": "Σύστημα",
"updates": "Ενημερώσεις", "updates": "Ενημερώσεις",
"update_available": "Διαθέσιμη ενημέρωση", "update_available": "Διαθέσιμη ενημέρωση",
"up_to_date": "Ενημερωμένο", "up_to_date": "Ενημερωμένο",
"child_bridges": "Child Bridges", "child_bridges": "Θυγατρικές Γέφυρες",
"child_bridges_status": "{{ok}}/{{total}}", "child_bridges_status": "{{ok}}/{{total}}",
"up": "Up", "up": "Πάνω",
"pending": "Pending", "pending": "Σε εκκρεμότητα",
"down": "Down", "down": "Κάτω",
"ok": "Ok" "ok": "Οκ"
}, },
"healthchecks": { "healthchecks": {
"new": "New", "new": "Νέο",
"up": "Up", "up": "Πάνω",
"grace": "In Grace Period", "grace": "Σε Περίοδο Χάριτος",
"down": "Down", "down": "Κάτω",
"paused": "Paused", "paused": "Σε παύση",
"status": "Status", "status": "Κατάσταση",
"last_ping": "Τελευταίο Ping", "last_ping": "Τελευταίο Ping",
"never": "Δεν υπάρχουν ping ακόμα" "never": "Δεν υπάρχουν ping ακόμα"
}, },
@@ -599,26 +607,26 @@
"containers_failed": "Απέτυχε" "containers_failed": "Απέτυχε"
}, },
"autobrr": { "autobrr": {
"approvedPushes": "Approved", "approvedPushes": "Εγκρίθηκε",
"rejectedPushes": "Απορρίφθηκε", "rejectedPushes": "Απορρίφθηκε",
"filters": "Φίλτρα", "filters": "Φίλτρα",
"indexers": "Indexers" "indexers": "Ευρετήρια"
}, },
"tubearchivist": { "tubearchivist": {
"downloads": "Queue", "downloads": "Αναμονή",
"videos": "Βίντεο", "videos": "Βίντεο",
"channels": "Κανάλια", "channels": "Κανάλια",
"playlists": "Λίστες αναπαραγωγής" "playlists": "Λίστες αναπαραγωγής"
}, },
"truenas": { "truenas": {
"load": "Φόρτος Συστήματος", "load": "Φόρτος Συστήματος",
"uptime": "Uptime", "uptime": "Συνεχής λειτουργία",
"alerts": "Alerts" "alerts": "Ειδοποιήσεις"
}, },
"pyload": { "pyload": {
"speed": "Ταχύτητα", "speed": "Ταχύτητα",
"active": "Active", "active": "Ενεργό",
"queue": "Queue", "queue": "Αναμονή",
"total": "Total" "total": "Total"
}, },
"gluetun": { "gluetun": {
@@ -727,56 +735,56 @@
"immich": { "immich": {
"users": "Users", "users": "Users",
"photos": "Φωτογραφίες", "photos": "Φωτογραφίες",
"videos": "Videos", "videos": "Βίντεο",
"storage": "Αποθηκευτικός χώρος" "storage": "Αποθηκευτικός χώρος"
}, },
"uptimekuma": { "uptimekuma": {
"up": "Online τοποθεσίες", "up": "Online τοποθεσίες",
"down": "Offline τοποθεσίες", "down": "Offline τοποθεσίες",
"uptime": "Uptime", "uptime": "Συνεχής λειτουργία",
"incident": "Περιστατικό", "incident": "Περιστατικό",
"m": "m" "m": "λ"
}, },
"atsumeru": { "atsumeru": {
"series": "Series", "series": "Σειρές",
"archives": "Archives", "archives": "Αρχεία",
"chapters": "Chapters", "chapters": "Κεφάλαια",
"categories": "Κατηγορίες" "categories": "Κατηγορίες"
}, },
"komga": { "komga": {
"libraries": "Libraries", "libraries": "Βιβλιοθήκες",
"series": "Series", "series": "Σειρές",
"books": "Books" "books": "Βιβλία"
}, },
"diskstation": { "diskstation": {
"days": "Days", "days": "Ημέρες",
"uptime": "Uptime", "uptime": "Συνεχής λειτουργία",
"volumeAvailable": "Available" "volumeAvailable": "Διαθέσιμα"
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Channels", "channels": "Κανάλια",
"streams": "Streams" "streams": "Ροές"
}, },
"mylar": { "mylar": {
"series": "Series", "series": "Σειρές",
"issues": "Issues", "issues": "Προβλήματα",
"wanted": "Wanted" "wanted": "Επιθυμητά"
}, },
"photoprism": { "photoprism": {
"albums": "Albums", "albums": "Άλμπουμ",
"photos": "Photos", "photos": "Φωτογραφίες",
"videos": "Videos", "videos": "Βίντεο",
"people": "Άνθρωποι" "people": "Άνθρωποι"
}, },
"fileflows": { "fileflows": {
"queue": "Queue", "queue": "Αναμονή",
"processing": "Processing", "processing": "Σε επεξεργασία",
"processed": "Processed", "processed": "Επεξεργασμένα",
"time": "Ώρα" "time": "Ώρα"
}, },
"firefly": { "firefly": {
"networth": "Net Worth", "networth": "Καθαρή αξία",
"budget": "Budget" "budget": "Προϋπολογισμός"
}, },
"grafana": { "grafana": {
"dashboards": "Πίνακας Ελέγχου", "dashboards": "Πίνακας Ελέγχου",
@@ -793,15 +801,15 @@
"numshares": "Κοινόχρηστα στοιχεία" "numshares": "Κοινόχρηστα στοιχεία"
}, },
"kopia": { "kopia": {
"status": "Status", "status": "Κατάσταση",
"size": "Μέγεθος", "size": "Μέγεθος",
"lastrun": "Τελευταία εκτέλεση", "lastrun": "Τελευταία εκτέλεση",
"nextrun": "Επόμενη εκτέλεση", "nextrun": "Επόμενη εκτέλεση",
"failed": "Failed" "failed": "Απέτυχε"
}, },
"unmanic": { "unmanic": {
"active_workers": "Ενεργοί χρήστες", "active_workers": "Ενεργοί χρήστες",
"total_workers": "Total Workers", "total_workers": "Σύνολο Εργατών",
"records_total": "Μήκος Ουράς" "records_total": "Μήκος Ουράς"
}, },
"pterodactyl": { "pterodactyl": {
@@ -814,21 +822,21 @@
"targets_total": "Συνολικοί Στόχοι" "targets_total": "Συνολικοί Στόχοι"
}, },
"gatus": { "gatus": {
"up": "Sites Up", "up": "Ιστοσελίδες Πάνω",
"down": "Sites Down", "down": "Ιστοσελίδες Κάτω",
"uptime": "Uptime" "uptime": "Συνεχής λειτουργία"
}, },
"ghostfolio": { "ghostfolio": {
"gross_percent_today": "Today", "gross_percent_today": "Σήμερα",
"gross_percent_1y": "Ένας χρόνος", "gross_percent_1y": "Ένας χρόνος",
"gross_percent_max": "Διαχρονικά", "gross_percent_max": "Διαχρονικά",
"net_worth": "Net Worth" "net_worth": "Καθαρή αξία"
}, },
"audiobookshelf": { "audiobookshelf": {
"podcasts": "Podcasts", "podcasts": "Podcasts",
"books": "Books", "books": "Βιβλία",
"podcastsDuration": "Διάρκεια", "podcastsDuration": "Διάρκεια",
"booksDuration": "Duration" "booksDuration": "Διάρκεια"
}, },
"homeassistant": { "homeassistant": {
"people_home": "Σύνολο ανθρώπων στο σπίτι", "people_home": "Σύνολο ανθρώπων στο σπίτι",
@@ -837,33 +845,33 @@
}, },
"whatsupdocker": { "whatsupdocker": {
"monitoring": "Παρακολούθηση", "monitoring": "Παρακολούθηση",
"updates": "Updates" "updates": "Ενημερώσεις"
}, },
"calibreweb": { "calibreweb": {
"books": "Books", "books": "Βιβλία",
"authors": "Συντάκτες", "authors": "Συντάκτες",
"categories": "Categories", "categories": "Κατηγορίες",
"series": "Series" "series": "Σειρές"
}, },
"booklore": { "booklore": {
"libraries": "Libraries", "libraries": "Βιβλιοθήκες",
"books": "Books", "books": "Βιβλία",
"reading": "Reading", "reading": "Ανάγνωση",
"finished": "Finished" "finished": "Ολοκληρώθηκε"
}, },
"jdownloader": { "jdownloader": {
"downloadCount": "Queue", "downloadCount": "Αναμονή",
"downloadBytesRemaining": "Remaining", "downloadBytesRemaining": "Απομένουν",
"downloadTotalBytes": "Size", "downloadTotalBytes": "Μέγεθος",
"downloadSpeed": "Speed" "downloadSpeed": "Ταχύτητα"
}, },
"kavita": { "kavita": {
"seriesCount": "Series", "seriesCount": "Σειρές",
"totalFiles": "Files" "totalFiles": "Αρχεία"
}, },
"azuredevops": { "azuredevops": {
"result": "Αποτέλεσμα", "result": "Αποτέλεσμα",
"status": "Status", "status": "Κατάσταση",
"buildId": "Build ID", "buildId": "Build ID",
"succeeded": "Πέτυχε", "succeeded": "Πέτυχε",
"notStarted": "Δεν ξεκίνησε", "notStarted": "Δεν ξεκίνησε",
@@ -948,7 +956,7 @@
"domains": "Domains", "domains": "Domains",
"mailboxes": "Mailboxes", "mailboxes": "Mailboxes",
"mails": "Mails", "mails": "Mails",
"storage": "Storage" "storage": "Αποθηκευτικός χώρος"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Items Handled",
@@ -959,61 +967,61 @@
"reclaimable": "Reclaimable", "reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity" "totalCapacity": "Συνολική χωρητικότητα"
}, },
"netdata": { "netdata": {
"warnings": "Warnings", "warnings": "Προειδοποιήσεις",
"criticals": "Criticals" "criticals": "Κρίσιμα"
}, },
"ntfy": { "ntfy": {
"title": "Title", "title": "Τίτλος",
"priority": "Priority", "priority": "Προτεραιότητα",
"lastReceived": "Last Received", "lastReceived": "Τελευταία Ληφθέντα",
"message": "Message", "message": "Μήνυμα",
"tags": "Tags", "tags": "Ετικέτες",
"none": "None", "none": "Κανένα",
"min": "Min", "min": "Ελάχιστο",
"low": "Low", "low": "Χαμηλό",
"default": "Default", "default": "Προεπιλογή",
"high": "High", "high": "Υψηλό",
"urgent": "Urgent" "urgent": "Επείγον"
}, },
"plantit": { "plantit": {
"events": "Events", "events": "Συμβάντα",
"plants": "Plants", "plants": "Φυτά",
"photos": "Photos", "photos": "Φωτογραφίες",
"species": "Species" "species": "Είδη"
}, },
"gitea": { "gitea": {
"notifications": "Notifications", "notifications": "Ειδοποιήσεις",
"issues": "Issues", "issues": "Προβλήματα",
"pulls": "Pull Requests", "pulls": "Pull Requests",
"repositories": "Repositories" "repositories": "Αποθετήρια"
}, },
"stash": { "stash": {
"scenes": "Scenes", "scenes": "Σκηνές",
"scenesPlayed": "Scenes Played", "scenesPlayed": "Σκηνές Που Παίχτηκαν",
"playCount": "Total Plays", "playCount": "Συνολικές Αναπαραγωγές",
"playDuration": "Time Watched", "playDuration": "Ώρα Παρακολούθησης",
"sceneSize": "Scenes Size", "sceneSize": "Μέγεθος Σκηνών",
"sceneDuration": "Scenes Duration", "sceneDuration": "Διάρκεια Σκηνών",
"images": "Images", "images": "Εικόνες",
"imageSize": "Images Size", "imageSize": "Μέγεθος Εικόνων",
"galleries": "Galleries", "galleries": "Συλλογές",
"performers": "Performers", "performers": "Καλλιτέχνες",
"studios": "Studios", "studios": "Στούντιο",
"movies": "Movies", "movies": "Ταινίες",
"tags": "Tags", "tags": "Ετικέτες",
"oCount": "O Count" "oCount": "Μετρητής O"
}, },
"tandoor": { "tandoor": {
"users": "Users", "users": "Χρήστες",
"recipes": "Recipes", "recipes": "Συνταγές",
"keywords": "Keywords" "keywords": "Λέξεις-κλειδιά"
}, },
"homebox": { "homebox": {
"items": "Items", "items": "Στοιχεία",
"totalWithWarranty": "With Warranty", "totalWithWarranty": "Με Εγγύηση",
"locations": "Locations", "locations": "Locations",
"labels": "Labels", "labels": "Labels",
"users": "Users", "users": "Users",
@@ -1076,72 +1084,72 @@
"vikunja": { "vikunja": {
"projects": "Active Projects", "projects": "Active Projects",
"tasks7d": "Tasks Due This Week", "tasks7d": "Tasks Due This Week",
"tasksOverdue": "Overdue Tasks", "tasksOverdue": "Εκπρόθεσμες Εργασίες",
"tasksInProgress": "Tasks In Progress" "tasksInProgress": "Εργασίες Σε Εξέλιξη"
}, },
"headscale": { "headscale": {
"name": "Name", "name": "Όνομα",
"address": "Address", "address": "Διεύθυνση",
"last_seen": "Last Seen", "last_seen": "Τελευταία Σύνδεση",
"status": "Status", "status": "Κατάσταση",
"online": "Online", "online": "Σε Σύνδεση",
"offline": "Offline" "offline": "Χωρίς Σύνδεση"
}, },
"beszel": { "beszel": {
"name": "Name", "name": "Όνομα",
"systems": "Systems", "systems": "Συστήματα",
"up": "Up", "up": "Πάνω",
"down": "Down", "down": "Κάτω",
"paused": "Paused", "paused": "Σε παύση",
"pending": "Pending", "pending": "Σε εκκρεμότητα",
"status": "Status", "status": "Κατάσταση",
"updated": "Updated", "updated": "Ενημερώθηκε",
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"disk": "Disk", "disk": "Δίσκος",
"network": "NET" "network": "Δίκτυο"
}, },
"argocd": { "argocd": {
"apps": "Apps", "apps": "Εφαρμογές",
"synced": "Synced", "synced": "Συγχρονισμένα",
"outOfSync": "Out Of Sync", "outOfSync": "Εκτός Συγχρονισμού",
"healthy": "Healthy", "healthy": "Υγιή",
"degraded": "Degraded", "degraded": "Degraded",
"progressing": "Progressing", "progressing": "Σε εξέλιξη",
"missing": "Missing", "missing": "Λείπουν",
"suspended": "Suspended" "suspended": "Σε αναστολή"
}, },
"spoolman": { "spoolman": {
"loading": "Loading" "loading": "Γίνεται φόρτωση"
}, },
"gitlab": { "gitlab": {
"groups": "Groups", "groups": "Ομάδες",
"issues": "Issues", "issues": "Προβλήματα",
"merges": "Merge Requests", "merges": "Αιτήματα Συγχώνευσης",
"projects": "Projects" "projects": "Projects"
}, },
"apcups": { "apcups": {
"status": "Status", "status": "Κατάσταση",
"load": "Load", "load": "Φόρτωση",
"bcharge": "Battery Charge", "bcharge": "Φόρτιση Μπαταρίας",
"timeleft": "Time Left" "timeleft": "Χρόνος που απομένει"
}, },
"karakeep": { "karakeep": {
"bookmarks": "Bookmarks", "bookmarks": "Σελιδοδείκτες",
"favorites": "Favorites", "favorites": "Αγαπημένα",
"archived": "Archived", "archived": "Αρχειοθετημένα",
"highlights": "Highlights", "highlights": "Επισημάνσεις",
"lists": "Lists", "lists": "Λίστες",
"tags": "Tags" "tags": "Ετικέτες"
}, },
"slskd": { "slskd": {
"slskStatus": "Network", "slskStatus": "Δίκτυο",
"connected": "Connected", "connected": "Σε σύνδεση",
"disconnected": "Disconnected", "disconnected": "Αποσυνδέθηκε",
"updateStatus": "Update", "updateStatus": "Ενημέρωση",
"update_yes": "Available", "update_yes": "Διαθέσιμα",
"update_no": "Up to Date", "update_no": "Ενημερωμένο",
"downloads": "Downloads", "downloads": "Λήψεις",
"uploads": "Uploads", "uploads": "Uploads",
"sharedFiles": "Files" "sharedFiles": "Files"
}, },
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Please wait" "wait": "Please wait"
}, },
"search": { "search": {
"placeholder": "Search…" "placeholder": "Search…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Bonvolu atendi" "wait": "Bonvolu atendi"
}, },
"search": { "search": {
"placeholder": "Serĉi…" "placeholder": "Serĉi…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Ĉefprocesoro", "cpu": "Ĉefprocesoro",
@@ -97,7 +99,12 @@
"unhealthy": "Malsana", "unhealthy": "Malsana",
"not_found": "Ne trovita", "not_found": "Ne trovita",
"exited": "Eliris", "exited": "Eliris",
"partial": "Parta" "partial": "Parta",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+12 -4
View File
@@ -39,7 +39,9 @@
"wait": "Espera, por favor" "wait": "Espera, por favor"
}, },
"search": { "search": {
"placeholder": "Buscar…" "placeholder": "Buscar…",
"provider": "Motor de búsqueda: {{name}}",
"select_provider": "Seleccionar motores de búsqueda"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "No saludable", "unhealthy": "No saludable",
"not_found": "No encontrado", "not_found": "No encontrado",
"exited": "Terminado", "exited": "Terminado",
"partial": "Parcial" "partial": "Parcial",
"created": "Creado",
"paused": "Pausado",
"restarting": "Reiniciando",
"removing": "Eliminando",
"dead": "Muerto"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -133,7 +140,7 @@
"series": "Series", "series": "Series",
"episodes": "Episodios", "episodes": "Episodios",
"songs": "Canciones", "songs": "Canciones",
"albums": "Albums" "albums": "Álbumes"
}, },
"esphome": { "esphome": {
"offline": "Fuera de línea", "offline": "Fuera de línea",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Usuarios", "users": "Usuarios",
"loginsLast24H": "Inicios de sesión (24h)", "loginsLast24H": "Inicios de sesión (24h)",
"failedLoginsLast24H": "Inicios de sesión fallidos (24h)" "failedLoginsLast24H": "Inicios de sesión fallidos (24h)",
"authorizationsLast24H": "Autenticaciones (24 h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Itxaron mesedez" "wait": "Itxaron mesedez"
}, },
"search": { "search": {
"placeholder": "Bilatu…" "placeholder": "Bilatu…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Odota, ole hyvä" "wait": "Odota, ole hyvä"
}, },
"search": { "search": {
"placeholder": "Hae…" "placeholder": "Hae…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Kirjautumisia (24h)", "loginsLast24H": "Kirjautumisia (24h)",
"failedLoginsLast24H": "Epäonnistuneita kirjautumisia (24h)" "failedLoginsLast24H": "Epäonnistuneita kirjautumisia (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+12 -4
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Se déconnecter"
}, },
"widget": { "widget": {
"missing_type": "Type de widget manquant : {{type}}", "missing_type": "Type de widget manquant : {{type}}",
@@ -39,7 +39,9 @@
"wait": "Veuillez patienter" "wait": "Veuillez patienter"
}, },
"search": { "search": {
"placeholder": "Recherche…" "placeholder": "Recherche…",
"provider": "Moteur de recherche : {{name}}",
"select_provider": "Sélectionnez un moteur de recherche"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Mauvaise santé", "unhealthy": "Mauvaise santé",
"not_found": "Inconnu", "not_found": "Inconnu",
"exited": "Arrêté", "exited": "Arrêté",
"partial": "Partiel" "partial": "Partiel",
"created": "Créé",
"paused": "En pause",
"restarting": "Redémarrage",
"removing": "Suppression",
"dead": "Mort"
}, },
"ping": { "ping": {
"error": "Erreur", "error": "Erreur",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Utilisateurs", "users": "Utilisateurs",
"loginsLast24H": "Connexions (24 h)", "loginsLast24H": "Connexions (24 h)",
"failedLoginsLast24H": "Connexions échouées (24 h)" "failedLoginsLast24H": "Connexions échouées (24 h)",
"authorizationsLast24H": "Authentifications (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "RAM", "mem": "RAM",
+67 -59
View File
@@ -21,7 +21,7 @@
"seconds": "שניות" "seconds": "שניות"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "התנתק"
}, },
"widget": { "widget": {
"missing_type": "סוג ווידג'ט חסר: {{type}}", "missing_type": "סוג ווידג'ט חסר: {{type}}",
@@ -39,7 +39,9 @@
"wait": "המתן בבקשה" "wait": "המתן בבקשה"
}, },
"search": { "search": {
"placeholder": "חיפוש…" "placeholder": "חיפוש…",
"provider": "מנוע חיפוש: {{name}}",
"select_provider": "בחר מנוע חיפוש"
}, },
"resources": { "resources": {
"cpu": "מעבד", "cpu": "מעבד",
@@ -70,18 +72,18 @@
"empty_data": "מצב תת-מערכת לא ידוע" "empty_data": "מצב תת-מערכת לא ידוע"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "בריא",
"degraded": "Degraded", "degraded": "ירוד",
"no_data": "No storage data available" "no_data": "אין אחסון זמין"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "משימות",
"stored": "Stored", "stored": "שמור",
"lastBackup": "Last Run", "lastBackup": "Last Run",
"nextRun": "Next Run", "nextRun": "Next Run",
"running": "Running", "running": "Running",
"warnings": "Warnings", "warnings": "הזהרות",
"errors": "Errors" "errors": "שגיאות"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -97,7 +99,12 @@
"unhealthy": "לא בריא", "unhealthy": "לא בריא",
"not_found": "לא נמצא", "not_found": "לא נמצא",
"exited": "יצא", "exited": "יצא",
"partial": "חלקי" "partial": "חלקי",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "שגיאה", "error": "שגיאה",
@@ -125,15 +132,15 @@
"songs": "שירים" "songs": "שירים"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "מנגן",
"transcoding": "Transcoding", "transcoding": "Transcoding",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams", "no_active": "אין מדיה פעילה",
"movies": "Movies", "movies": "סרטים",
"series": "Series", "series": "סדרות",
"episodes": "Episodes", "episodes": "פרקים",
"songs": "Songs", "songs": "שירים",
"albums": "Albums" "albums": "אלבומים"
}, },
"esphome": { "esphome": {
"offline": "מכובה", "offline": "מכובה",
@@ -308,11 +315,11 @@
"available": "זמין" "available": "זמין"
}, },
"seerr": { "seerr": {
"pending": "Pending", "pending": "ממתין",
"approved": "Approved", "approved": "מאושר",
"available": "Available", "available": "זמין",
"completed": "Completed", "completed": "הסתיים",
"processing": "Processing", "processing": "מעבד",
"issues": "Open Issues" "issues": "Open Issues"
}, },
"netalertx": { "netalertx": {
@@ -357,16 +364,16 @@
"address": "כתובת", "address": "כתובת",
"expires": "תאריך תפוגה", "expires": "תאריך תפוגה",
"never": "אף פעם", "never": "אף פעם",
"user": "User", "user": "משתמש",
"hostname": "Hostname", "hostname": "Hostname",
"name": "Name", "name": "שם",
"client_version": "Client Version", "client_version": "Client Version",
"os": "OS", "os": "מערכת הפעלה",
"created": "Created", "created": "יוצר",
"authorized": "Authorized", "authorized": "מורשה",
"is_external": "Is External", "is_external": "האם חיצוני",
"update_available": "Update Available", "update_available": "עדכון זמין",
"tags": "Tags", "tags": "תגיות",
"last_seen": "נראה לאחרונה", "last_seen": "נראה לאחרונה",
"now": "כעת", "now": "כעת",
"years": "{{number}}y", "years": "{{number}}y",
@@ -376,8 +383,8 @@
"minutes": "{{number}}m", "minutes": "{{number}}m",
"seconds": "{{number}}s", "seconds": "{{number}}s",
"ago": "{{value}} Ago", "ago": "{{value}} Ago",
"true": "Yes", "true": "כן",
"false": "No" "false": "לא"
}, },
"technitium": { "technitium": {
"totalQueries": "שאילתות", "totalQueries": "שאילתות",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "משתמשים", "users": "משתמשים",
"loginsLast24H": "כניסות (24h)", "loginsLast24H": "כניסות (24h)",
"failedLoginsLast24H": "כניסות שנכשלו (24h)" "failedLoginsLast24H": "כניסות שנכשלו (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "זיכרון", "mem": "זיכרון",
@@ -481,7 +489,7 @@
}, },
"pulse": { "pulse": {
"nodes": "Nodes", "nodes": "Nodes",
"vms": "VMs", "vms": "מכונות וירטואליות",
"lxcs": "LXCs" "lxcs": "LXCs"
}, },
"glances": { "glances": {
@@ -581,7 +589,7 @@
"up": "למעלה", "up": "למעלה",
"pending": "ממתין", "pending": "ממתין",
"down": "למטה", "down": "למטה",
"ok": "Ok" "ok": "אוקיי"
}, },
"healthchecks": { "healthchecks": {
"new": "חדש", "new": "חדש",
@@ -650,9 +658,9 @@
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Orgs",
"sites": "Sites", "sites": "אתרים",
"resources": "Resources", "resources": "משאבים",
"targets": "Targets", "targets": "מטרות",
"traffic": "Traffic", "traffic": "Traffic",
"in": "In", "in": "In",
"out": "Out" "out": "Out"
@@ -754,7 +762,7 @@
"volumeAvailable": "זמין" "volumeAvailable": "זמין"
}, },
"dispatcharr": { "dispatcharr": {
"channels": "Channels", "channels": "ערוצים",
"streams": "Streams" "streams": "Streams"
}, },
"mylar": { "mylar": {
@@ -1179,9 +1187,9 @@
"nextRenewingSubscription": "תשלום הבא" "nextRenewingSubscription": "תשלום הבא"
}, },
"unraid": { "unraid": {
"STARTED": "Started", "STARTED": "התחיל",
"STOPPED": "Stopped", "STOPPED": "נעצר",
"NEW_ARRAY": "New Array", "NEW_ARRAY": "מערך חדש",
"RECON_DISK": "Reconstructing Disk", "RECON_DISK": "Reconstructing Disk",
"DISABLE_DISK": "Disk Disabled", "DISABLE_DISK": "Disk Disabled",
"SWAP_DSBL": "Swap Disable", "SWAP_DSBL": "Swap Disable",
@@ -1193,31 +1201,31 @@
"notifications": "Notifications", "notifications": "Notifications",
"status": "Status", "status": "Status",
"cpu": "CPU", "cpu": "CPU",
"memoryUsed": "Memory Used", "memoryUsed": "זיכרון בשימוש",
"memoryAvailable": "Memory Available", "memoryAvailable": "זיכרון זמין",
"arrayUsed": "Array Used", "arrayUsed": "Array Used",
"arrayFree": "Array Free", "arrayFree": "Array Free",
"poolUsed": "{{pool}} Used", "poolUsed": "{{pool}} Used",
"poolFree": "{{pool}} Free" "poolFree": "{{pool}} Free"
}, },
"backrest": { "backrest": {
"num_plans": "Plans", "num_plans": "תוכניות",
"num_success_30": "Successes", "num_success_30": "הצלחות",
"num_failure_30": "Failures", "num_failure_30": "כשלונות",
"num_success_latest": "Succeeding", "num_success_latest": "Succeeding",
"num_failure_latest": "Failing", "num_failure_latest": "Failing",
"bytes_added_30": "Bytes Added" "bytes_added_30": "Bytes Added"
}, },
"yourspotify": { "yourspotify": {
"songs": "Songs", "songs": "שירים",
"time": "Time", "time": "זמן",
"artists": "Artists" "artists": "אומנים"
}, },
"arcane": { "arcane": {
"containers": "Containers", "containers": "Containers",
"images": "Images", "images": "Images",
"image_updates": "Image Updates", "image_updates": "Image Updates",
"images_unused": "Unused", "images_unused": "לא בשימוש",
"environment_required": "Environment ID Required" "environment_required": "Environment ID Required"
}, },
"dockhand": { "dockhand": {
@@ -1227,7 +1235,7 @@
"memory": "Memory", "memory": "Memory",
"images": "Images", "images": "Images",
"volumes": "Volumes", "volumes": "Volumes",
"events_today": "Events Today", "events_today": "אירועים היום",
"pending_updates": "Pending Updates", "pending_updates": "Pending Updates",
"stacks": "Stacks", "stacks": "Stacks",
"paused": "Paused", "paused": "Paused",
@@ -1235,16 +1243,16 @@
"environment_not_found": "Environment Not Found" "environment_not_found": "Environment Not Found"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "נאכלו",
"burned": "Burned", "burned": "נשרפו",
"remaining": "Remaining", "remaining": "נותרו",
"steps": "Steps" "steps": "צעדים"
}, },
"syncthing": { "syncthing": {
"connected": "Connected", "connected": "Connected",
"synced": "Synced", "synced": "מסונכרן",
"errors": "Errors", "errors": "שגיאות",
"storage": "Storage" "storage": "אחסון"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Wanted",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Please wait" "wait": "Please wait"
}, },
"search": { "search": {
"placeholder": "Search…" "placeholder": "Search…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+22 -14
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Odjavi se"
}, },
"widget": { "widget": {
"missing_type": "Nedostajuća vrsta widgeta: {{type}}", "missing_type": "Nedostajuća vrsta widgeta: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Pričekaj" "wait": "Pričekaj"
}, },
"search": { "search": {
"placeholder": "Traži …" "placeholder": "Traži …",
"provider": "Tražilica: {{name}}",
"select_provider": "Odaberi tražilicu"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Nefunkcionalno", "unhealthy": "Nefunkcionalno",
"not_found": "Nepronađeno", "not_found": "Nepronađeno",
"exited": "Zatoreno", "exited": "Zatoreno",
"partial": "Djelomično" "partial": "Djelomično",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Greška", "error": "Greška",
@@ -133,7 +140,7 @@
"series": "Serije", "series": "Serije",
"episodes": "Epizode", "episodes": "Epizode",
"songs": "Pjesme", "songs": "Pjesme",
"albums": "Albums" "albums": "Albumi"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Korisnici", "users": "Korisnici",
"loginsLast24H": "Prijave (24 h)", "loginsLast24H": "Prijave (24 h)",
"failedLoginsLast24H": "Neuspjele prijave (24 h)" "failedLoginsLast24H": "Neuspjele prijave (24 h)",
"authorizationsLast24H": "Autorizacije (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
@@ -951,15 +959,15 @@
"storage": "Spremište" "storage": "Spremište"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Obrađene stavke",
"episodesHandled": "Episodes Handled", "episodesHandled": "Obrađene epizode",
"moviesHandled": "Movies Handled", "moviesHandled": "Obrađeni filmovi",
"showsHandled": "Shows Handled", "showsHandled": "Obrađene emisije",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Obrađene sezone",
"reclaimable": "Reclaimable", "reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity" "totalCapacity": "Ukupni kapacitet"
}, },
"netdata": { "netdata": {
"warnings": "Upozorenja", "warnings": "Upozorenja",
@@ -1247,8 +1255,8 @@
"storage": "Spremište" "storage": "Spremište"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Željeno",
"queued": "Queued", "queued": "U redu čekanja",
"leagues": "Leagues" "leagues": "Lige"
} }
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Kérjük várjon" "wait": "Kérjük várjon"
}, },
"search": { "search": {
"placeholder": "Keresés…" "placeholder": "Keresés…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Processzor", "cpu": "Processzor",
@@ -97,7 +99,12 @@
"unhealthy": "Egészségtelen", "unhealthy": "Egészségtelen",
"not_found": "Nem található", "not_found": "Nem található",
"exited": "Kilépett", "exited": "Kilépett",
"partial": "Részleges" "partial": "Részleges",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Hiba", "error": "Hiba",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Felhasználók", "users": "Felhasználók",
"loginsLast24H": "Bejelentkezések (24 óra)", "loginsLast24H": "Bejelentkezések (24 óra)",
"failedLoginsLast24H": "Sikertelen bejelentkezések (24h)" "failedLoginsLast24H": "Sikertelen bejelentkezések (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Harap tunggu" "wait": "Harap tunggu"
}, },
"search": { "search": {
"placeholder": "Telusuri…" "placeholder": "Telusuri…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Tidak Lancar", "unhealthy": "Tidak Lancar",
"not_found": "Tidak Ditemukan", "not_found": "Tidak Ditemukan",
"exited": "Terkeluar", "exited": "Terkeluar",
"partial": "Sebagian" "partial": "Sebagian",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Login (24j)", "loginsLast24H": "Login (24j)",
"failedLoginsLast24H": "Login Gagal (24j)" "failedLoginsLast24H": "Login Gagal (24j)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Attendi per favore" "wait": "Attendi per favore"
}, },
"search": { "search": {
"placeholder": "Cerca…" "placeholder": "Cerca…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Non sano", "unhealthy": "Non sano",
"not_found": "Non trovato", "not_found": "Non trovato",
"exited": "Uscito", "exited": "Uscito",
"partial": "Parziale" "partial": "Parziale",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Errore", "error": "Errore",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Utenti", "users": "Utenti",
"loginsLast24H": "Accessi (24h)", "loginsLast24H": "Accessi (24h)",
"failedLoginsLast24H": "Accessi Falliti (24h)" "failedLoginsLast24H": "Accessi Falliti (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+155 -147
View File
@@ -39,7 +39,9 @@
"wait": "お待ちください" "wait": "お待ちください"
}, },
"search": { "search": {
"placeholder": "検索…" "placeholder": "検索 ...",
"provider": "Search engine: {{name}}",
"select_provider": "検索エンジンを選択"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "非健全", "unhealthy": "非健全",
"not_found": "不明", "not_found": "不明",
"exited": "停止しました", "exited": "停止しました",
"partial": "部分的" "partial": "部分的",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "エラー", "error": "エラー",
@@ -125,18 +132,18 @@
"songs": "曲" "songs": "曲"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "再生中",
"transcoding": "Transcoding", "transcoding": "変換中",
"bitrate": "Bitrate", "bitrate": "ビットレート",
"no_active": "No Active Streams", "no_active": "アクティブ・ストリーム無し",
"movies": "Movies", "movies": "映画",
"series": "Series", "series": "シリーズ",
"episodes": "Episodes", "episodes": "エピソード",
"songs": "Songs", "songs": "",
"albums": "Albums" "albums": "アルバム"
}, },
"esphome": { "esphome": {
"offline": "", "offline": "オフライン ",
"offline_alt": "オフライン", "offline_alt": "オフライン",
"online": "オンライン", "online": "オンライン",
"total": "Total", "total": "Total",
@@ -234,18 +241,18 @@
}, },
"rutorrent": { "rutorrent": {
"active": "アクティブ", "active": "アクティブ",
"upload": "Upload", "upload": "アップロード",
"download": "Download" "download": "ダウンロード"
}, },
"transmission": { "transmission": {
"download": "Download", "download": "ダウンロード",
"upload": "Upload", "upload": "アップロード",
"leech": "Leech", "leech": "Leech",
"seed": "Seed" "seed": "Seed"
}, },
"qbittorrent": { "qbittorrent": {
"download": "Download", "download": "ダウンロード",
"upload": "Upload", "upload": "アップロード",
"leech": "Leech", "leech": "Leech",
"seed": "Seed" "seed": "Seed"
}, },
@@ -278,7 +285,7 @@
"queued": "待機中", "queued": "待機中",
"series": "Series", "series": "Series",
"queue": "Queue", "queue": "Queue",
"unknown": "Unknown" "unknown": "不明"
}, },
"radarr": { "radarr": {
"wanted": "Wanted", "wanted": "Wanted",
@@ -358,15 +365,15 @@
"expires": "失効", "expires": "失効",
"never": "なし", "never": "なし",
"user": "User", "user": "User",
"hostname": "Hostname", "hostname": "ホスト名",
"name": "Name", "name": "名前",
"client_version": "Client Version", "client_version": "クライアントバージョン",
"os": "OS", "os": "OS",
"created": "Created", "created": "作成済み",
"authorized": "Authorized", "authorized": "承認済み",
"is_external": "Is External", "is_external": "Is External",
"update_available": "Update Available", "update_available": "アップデートが利用できます",
"tags": "Tags", "tags": "タグ",
"last_seen": "最終日時", "last_seen": "最終日時",
"now": "現在", "now": "現在",
"years": "{{number}}年", "years": "{{number}}年",
@@ -376,11 +383,11 @@
"minutes": "{{number}}分", "minutes": "{{number}}分",
"seconds": "{{number}}秒", "seconds": "{{number}}秒",
"ago": "{{value}} 前", "ago": "{{value}} 前",
"true": "Yes", "true": "はい",
"false": "No" "false": "いいえ"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "クエリ",
"totalNoError": "成功", "totalNoError": "成功",
"totalServerFailure": "失敗", "totalServerFailure": "失敗",
"totalNxDomain": "NXドメイン", "totalNxDomain": "NXドメイン",
@@ -388,7 +395,7 @@
"totalAuthoritative": "正式", "totalAuthoritative": "正式",
"totalRecursive": "再帰的", "totalRecursive": "再帰的",
"totalCached": "キャッシュ済み", "totalCached": "キャッシュ済み",
"totalBlocked": "Blocked", "totalBlocked": "ブロック中",
"totalDropped": "ドロップ済み", "totalDropped": "ドロップ済み",
"totalClients": "クライアント" "totalClients": "クライアント"
}, },
@@ -404,13 +411,13 @@
"middleware": "ミドルウェア" "middleware": "ミドルウェア"
}, },
"trilium": { "trilium": {
"version": "Version", "version": "バージョン",
"notesCount": "Notes", "notesCount": "メモ",
"dbSize": "Database Size", "dbSize": "データベースサイズ",
"unknown": "Unknown" "unknown": "不明"
}, },
"navidrome": { "navidrome": {
"nothing_streaming": "No Active Streams", "nothing_streaming": "アクティブストリーム無し",
"please_wait": "お待ちください" "please_wait": "お待ちください"
}, },
"npm": { "npm": {
@@ -471,10 +478,11 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "ログイン (24時間)", "loginsLast24H": "ログイン (24時間)",
"failedLoginsLast24H": "ログイン失敗(24時間)" "failedLoginsLast24H": "ログイン失敗(24時間)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "メモリ",
"cpu": "CPU", "cpu": "CPU",
"lxc": "LXC", "lxc": "LXC",
"vms": "VM" "vms": "VM"
@@ -486,17 +494,17 @@
}, },
"glances": { "glances": {
"cpu": "CPU", "cpu": "CPU",
"load": "Load", "load": "負荷",
"wait": "Please wait", "wait": "お待ちください",
"temp": "TEMP", "temp": "温度",
"_temp": "温度", "_temp": "温度",
"warn": "警告", "warn": "警告",
"uptime": "UP", "uptime": "稼働時間",
"total": "Total", "total": "合計",
"free": "Free", "free": "空き容量",
"used": "Used", "used": "使用済み",
"days": "d", "days": "",
"hours": "h", "hours": "",
"crit": "クリティカル", "crit": "クリティカル",
"read": "Read", "read": "Read",
"write": "書き込み", "write": "書き込み",
@@ -537,39 +545,39 @@
"57-day": "着氷性の小雨", "57-day": "着氷性の小雨",
"57-night": "Freezing Drizzle", "57-night": "Freezing Drizzle",
"61-day": "小雨", "61-day": "小雨",
"61-night": "Light Rain", "61-night": "小雨",
"63-day": "雨", "63-day": "雨",
"63-night": "Rain", "63-night": "",
"65-day": "大雨", "65-day": "大雨",
"65-night": "Heavy Rain", "65-night": "大雨",
"66-day": "着氷性の雨", "66-day": "着氷性の雨",
"66-night": "Freezing Rain", "66-night": "着氷性の雨",
"67-day": "Freezing Rain", "67-day": "着氷性の雨",
"67-night": "Freezing Rain", "67-night": "着氷性の雨",
"71-day": "小雪", "71-day": "小雪",
"71-night": "Light Snow", "71-night": "小雪",
"73-day": "雪", "73-day": "雪",
"73-night": "Snow", "73-night": "",
"75-day": "大雪", "75-day": "大雪",
"75-night": "Heavy Snow", "75-night": "Heavy Snow",
"77-day": "霧雪", "77-day": "霧雪",
"77-night": "Snow Grains", "77-night": "Snow Grains",
"80-day": "弱いにわか雨", "80-day": "弱いにわか雨",
"80-night": "Light Showers", "80-night": "弱いにわか雨",
"81-day": "にわか雨", "81-day": "にわか雨",
"81-night": "Showers", "81-night": "にわか雨",
"82-day": "強いにわか雨", "82-day": "強いにわか雨",
"82-night": "Heavy Showers", "82-night": "強いにわか雨",
"85-day": "にわか雪", "85-day": "にわか雪",
"85-night": "Snow Showers", "85-night": "にわか雪",
"86-day": "Snow Showers", "86-day": "にわか雪",
"86-night": "Snow Showers", "86-night": "にわか雪",
"95-day": "雷雨", "95-day": "雷雨",
"95-night": "Thunderstorm", "95-night": "雷雨",
"96-day": "雷雨・ひょう", "96-day": "雷雨・ひょう",
"96-night": "Thunderstorm With Hail", "96-night": "雹を伴う雷雨",
"99-day": "Thunderstorm With Hail", "99-day": "雹を伴う雷雨",
"99-night": "Thunderstorm With Hail" "99-night": "雹を伴う雷雨"
}, },
"homebridge": { "homebridge": {
"available_update": "システム", "available_update": "システム",
@@ -637,7 +645,7 @@
"signalQuality": "クオリティ", "signalQuality": "クオリティ",
"symbolQuality": "Quality", "symbolQuality": "Quality",
"networkRate": "Bitrate", "networkRate": "Bitrate",
"clientIP": "クライアント IP" "clientIP": "クライアントIP"
}, },
"scrutiny": { "scrutiny": {
"passed": "合格", "passed": "合格",
@@ -653,7 +661,7 @@
"sites": "Sites", "sites": "Sites",
"resources": "Resources", "resources": "Resources",
"targets": "Targets", "targets": "Targets",
"traffic": "Traffic", "traffic": "トラフィック",
"in": "In", "in": "In",
"out": "Out" "out": "Out"
}, },
@@ -666,7 +674,7 @@
"low_battery": "バッテリー残量低下" "low_battery": "バッテリー残量低下"
}, },
"nextdns": { "nextdns": {
"wait": "Please Wait", "wait": "お待ちください",
"no_devices": "デバイス データを受信していません" "no_devices": "デバイス データを受信していません"
}, },
"mikrotik": { "mikrotik": {
@@ -687,7 +695,7 @@
"limit": "リミット" "limit": "リミット"
}, },
"opnsense": { "opnsense": {
"cpu": "CPU Load", "cpu": "CPU負荷",
"memory": "アクティブ・メモリ", "memory": "アクティブ・メモリ",
"wanUpload": "WANアップロード", "wanUpload": "WANアップロード",
"wanDownload": "WANダウンロード" "wanDownload": "WANダウンロード"
@@ -699,14 +707,14 @@
"layers": "レイヤー" "layers": "レイヤー"
}, },
"octoprint": { "octoprint": {
"printer_state": "Status", "printer_state": "ステータス",
"temp_tool": "ツール温度", "temp_tool": "ツール温度",
"temp_bed": "ベッド温度", "temp_bed": "ベッド温度",
"job_completion": "完了" "job_completion": "完了"
}, },
"cloudflared": { "cloudflared": {
"origin_ip": "オリジンIP", "origin_ip": "オリジンIP",
"status": "Status" "status": "ステータス"
}, },
"pfsense": { "pfsense": {
"load": "読み込み平均", "load": "読み込み平均",
@@ -725,17 +733,17 @@
"memory_usage": "メモリ" "memory_usage": "メモリ"
}, },
"immich": { "immich": {
"users": "Users", "users": "ユーザ",
"photos": "写真", "photos": "写真",
"videos": "Videos", "videos": "ビデオ",
"storage": "ストレージ" "storage": "ストレージ"
}, },
"uptimekuma": { "uptimekuma": {
"up": "サイトUp", "up": "サイトUp",
"down": "サイトDown", "down": "サイトDown",
"uptime": "Uptime", "uptime": "稼働時間",
"incident": "事件", "incident": "事件",
"m": "m" "m": ""
}, },
"atsumeru": { "atsumeru": {
"series": "Series", "series": "Series",
@@ -764,8 +772,8 @@
}, },
"photoprism": { "photoprism": {
"albums": "Albums", "albums": "Albums",
"photos": "Photos", "photos": "写真",
"videos": "Videos", "videos": "動画",
"people": "人" "people": "人"
}, },
"fileflows": { "fileflows": {
@@ -876,12 +884,12 @@
}, },
"gamedig": { "gamedig": {
"status": "Status", "status": "Status",
"online": "Online", "online": "オンライン",
"offline": "Offline", "offline": "オフライン ",
"name": "名前", "name": "名前",
"map": "マップ", "map": "マップ",
"currentPlayers": "現在のプレーヤー", "currentPlayers": "現在のプレーヤー",
"players": "Players", "players": "プレイヤー",
"maxPlayers": "最大プレイヤー数", "maxPlayers": "最大プレイヤー数",
"bots": "ボット", "bots": "ボット",
"ping": "Ping" "ping": "Ping"
@@ -894,21 +902,21 @@
}, },
"mealie": { "mealie": {
"recipes": "レシピ", "recipes": "レシピ",
"users": "Users", "users": "ユーザ",
"categories": "Categories", "categories": "カテゴリ",
"tags": "タグ" "tags": "タグ"
}, },
"openmediavault": { "openmediavault": {
"downloading": "ダウンロード中", "downloading": "ダウンロード中",
"total": "Total", "total": "合計",
"running": "Running", "running": "実行中",
"stopped": "Stopped", "stopped": "停止中",
"passed": "Passed", "passed": "Passed",
"failed": "Failed" "failed": "Failed"
}, },
"openwrt": { "openwrt": {
"uptime": "Uptime", "uptime": "Uptime",
"cpuLoad": "CPU 平均負荷(5 分)", "cpuLoad": "CPU 平均負荷(5分)",
"up": "Up", "up": "Up",
"down": "Down", "down": "Down",
"bytesTx": "送信済み", "bytesTx": "送信済み",
@@ -916,7 +924,7 @@
}, },
"uptimerobot": { "uptimerobot": {
"status": "Status", "status": "Status",
"uptime": "Uptime", "uptime": "稼働時間",
"lastDown": "最後のダウンタイム", "lastDown": "最後のダウンタイム",
"downDuration": "ダウンタイム時間", "downDuration": "ダウンタイム時間",
"sitesUp": "Sites Up", "sitesUp": "Sites Up",
@@ -946,9 +954,9 @@
}, },
"mailcow": { "mailcow": {
"domains": "Domains", "domains": "Domains",
"mailboxes": "Mailboxes", "mailboxes": "受信箱",
"mails": "Mails", "mails": "メール",
"storage": "Storage" "storage": "ストレージ"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Items Handled",
@@ -966,7 +974,7 @@
"criticals": "重大" "criticals": "重大"
}, },
"ntfy": { "ntfy": {
"title": "Title", "title": "タイトル",
"priority": "Priority", "priority": "Priority",
"lastReceived": "Last Received", "lastReceived": "Last Received",
"message": "Message", "message": "Message",
@@ -981,7 +989,7 @@
"plantit": { "plantit": {
"events": "イベント", "events": "イベント",
"plants": "植物", "plants": "植物",
"photos": "Photos", "photos": "写真",
"species": "種" "species": "種"
}, },
"gitea": { "gitea": {
@@ -1129,27 +1137,27 @@
"karakeep": { "karakeep": {
"bookmarks": "Bookmarks", "bookmarks": "Bookmarks",
"favorites": "Favorites", "favorites": "Favorites",
"archived": "Archived", "archived": "アーカイブ済み",
"highlights": "Highlights", "highlights": "ハイライト",
"lists": "Lists", "lists": "リスト",
"tags": "Tags" "tags": "タグ"
}, },
"slskd": { "slskd": {
"slskStatus": "Network", "slskStatus": "ネットワーク",
"connected": "Connected", "connected": "接続されています",
"disconnected": "Disconnected", "disconnected": "切断されています",
"updateStatus": "Update", "updateStatus": "更新",
"update_yes": "Available", "update_yes": "利用可能",
"update_no": "Up to Date", "update_no": "最新",
"downloads": "Downloads", "downloads": "ダウンロード",
"uploads": "Uploads", "uploads": "アップロード",
"sharedFiles": "Files" "sharedFiles": "ファイル"
}, },
"jellystat": { "jellystat": {
"songs": "Songs", "songs": "",
"movies": "Movies", "movies": "映画",
"episodes": "Episodes", "episodes": "エピソード",
"other": "Other" "other": "その他"
}, },
"checkmk": { "checkmk": {
"serviceErrors": "Service issues", "serviceErrors": "Service issues",
@@ -1190,11 +1198,11 @@
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks", "TOO_MANY_MISSING_DISKS": "Too Many Missing Disks",
"NEW_DISK_TOO_SMALL": "New Disk Too Small", "NEW_DISK_TOO_SMALL": "New Disk Too Small",
"NO_DATA_DISKS": "No Data Disks", "NO_DATA_DISKS": "No Data Disks",
"notifications": "Notifications", "notifications": "通知",
"status": "Status", "status": "ステータス",
"cpu": "CPU", "cpu": "CPU",
"memoryUsed": "Memory Used", "memoryUsed": "メモリ使用量",
"memoryAvailable": "Memory Available", "memoryAvailable": "使用可能なメモリ",
"arrayUsed": "Array Used", "arrayUsed": "Array Used",
"arrayFree": "Array Free", "arrayFree": "Array Free",
"poolUsed": "{{pool}} Used", "poolUsed": "{{pool}} Used",
@@ -1202,53 +1210,53 @@
}, },
"backrest": { "backrest": {
"num_plans": "Plans", "num_plans": "Plans",
"num_success_30": "Successes", "num_success_30": "成功",
"num_failure_30": "Failures", "num_failure_30": "失敗",
"num_success_latest": "Succeeding", "num_success_latest": "成功",
"num_failure_latest": "Failing", "num_failure_latest": "失敗",
"bytes_added_30": "Bytes Added" "bytes_added_30": "バイトが追加されました"
}, },
"yourspotify": { "yourspotify": {
"songs": "Songs", "songs": "",
"time": "Time", "time": "時間",
"artists": "Artists" "artists": "アーティスト"
}, },
"arcane": { "arcane": {
"containers": "Containers", "containers": "コンテナ",
"images": "Images", "images": "イメージ",
"image_updates": "Image Updates", "image_updates": "イメージの更新",
"images_unused": "Unused", "images_unused": "未使用",
"environment_required": "Environment ID Required" "environment_required": "Enviroment ID が必要です"
}, },
"dockhand": { "dockhand": {
"running": "Running", "running": "実行中",
"stopped": "Stopped", "stopped": "停止中",
"cpu": "CPU", "cpu": "CPU",
"memory": "Memory", "memory": "メモリ",
"images": "Images", "images": "イメージ",
"volumes": "Volumes", "volumes": "ボリューム",
"events_today": "Events Today", "events_today": "今日のイベント",
"pending_updates": "Pending Updates", "pending_updates": "保留中の更新",
"stacks": "Stacks", "stacks": "スタック数",
"paused": "Paused", "paused": "一時停止中",
"total": "Total", "total": "合計",
"environment_not_found": "Environment Not Found" "environment_not_found": "環境が見つかりません"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "摂取カロリー",
"burned": "Burned", "burned": "消費カロリー",
"remaining": "Remaining", "remaining": "残り",
"steps": "Steps" "steps": "ステップ"
}, },
"syncthing": { "syncthing": {
"connected": "Connected", "connected": "接続されています",
"synced": "Synced", "synced": "同期しました",
"errors": "Errors", "errors": "エラー",
"storage": "Storage" "storage": "ストレージ"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "募集中",
"queued": "Queued", "queued": "処理待ち",
"leagues": "Leagues" "leagues": "リーグ"
} }
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "잠시만 기다리세요" "wait": "잠시만 기다리세요"
}, },
"search": { "search": {
"placeholder": "검색…" "placeholder": "검색…",
"provider": "검색 엔진: {{name}}",
"select_provider": "검색 엔진 선택"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "비정상", "unhealthy": "비정상",
"not_found": "찾을 수 없음", "not_found": "찾을 수 없음",
"exited": "종료됨", "exited": "종료됨",
"partial": "부분적" "partial": "부분적",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "오류", "error": "오류",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "사용자", "users": "사용자",
"loginsLast24H": "로그인 (24시간)", "loginsLast24H": "로그인 (24시간)",
"failedLoginsLast24H": "실패한 로그인 (24시간)" "failedLoginsLast24H": "실패한 로그인 (24시간)",
"authorizationsLast24H": "인증(24시간)"
}, },
"proxmox": { "proxmox": {
"mem": "메모리", "mem": "메모리",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Lūdzu, uzgaidiet" "wait": "Lūdzu, uzgaidiet"
}, },
"search": { "search": {
"placeholder": "Meklēt…" "placeholder": "Meklēt…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Sila tunggu" "wait": "Sila tunggu"
}, },
"search": { "search": {
"placeholder": "Carian…" "placeholder": "Carian…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Kurang sihat", "unhealthy": "Kurang sihat",
"not_found": "Tidak dijumpai", "not_found": "Tidak dijumpai",
"exited": "Dimatikan", "exited": "Dimatikan",
"partial": "Sebahagian" "partial": "Sebahagian",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logmasuk (24j)", "loginsLast24H": "Logmasuk (24j)",
"failedLoginsLast24H": "Logmasuk Gagal (24j)" "failedLoginsLast24H": "Logmasuk Gagal (24j)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Even geduld" "wait": "Even geduld"
}, },
"search": { "search": {
"placeholder": "Zoeken…" "placeholder": "Zoeken…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Ongezond", "unhealthy": "Ongezond",
"not_found": "Niet Gevonden", "not_found": "Niet Gevonden",
"exited": "Gestopt", "exited": "Gestopt",
"partial": "Gedeeltelijk" "partial": "Gedeeltelijk",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Fout", "error": "Fout",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Gebruikers", "users": "Gebruikers",
"loginsLast24H": "Logins (24u)", "loginsLast24H": "Logins (24u)",
"failedLoginsLast24H": "Mislukte Logins (24u)" "failedLoginsLast24H": "Mislukte Logins (24u)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "GEH", "mem": "GEH",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Vennligst vent" "wait": "Vennligst vent"
}, },
"search": { "search": {
"placeholder": "Søk…" "placeholder": "Søk…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Usunn", "unhealthy": "Usunn",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Delvis" "partial": "Delvis",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Mislykket innlogginger (24t)" "failedLoginsLast24H": "Mislykket innlogginger (24t)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+13 -5
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Wyloguj się"
}, },
"widget": { "widget": {
"missing_type": "Brakujący typ widżetu: {{type}}", "missing_type": "Brakujący typ widżetu: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Proszę czekać" "wait": "Proszę czekać"
}, },
"search": { "search": {
"placeholder": "Szukaj…" "placeholder": "Szukaj…",
"provider": "Wyszukiwarka: {{name}}",
"select_provider": "Wybierz wyszukiwarkę"
}, },
"resources": { "resources": {
"cpu": "Procesor", "cpu": "Procesor",
@@ -97,7 +99,12 @@
"unhealthy": "Niezdrowy", "unhealthy": "Niezdrowy",
"not_found": "Nie znaleziono", "not_found": "Nie znaleziono",
"exited": "Zakończony", "exited": "Zakończony",
"partial": "Częściowy" "partial": "Częściowy",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Błąd", "error": "Błąd",
@@ -133,7 +140,7 @@
"series": "Seriale", "series": "Seriale",
"episodes": "Odcinki", "episodes": "Odcinki",
"songs": "Piosenki", "songs": "Piosenki",
"albums": "Albums" "albums": "Albumy"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Użytkownicy", "users": "Użytkownicy",
"loginsLast24H": "Logowania (24h)", "loginsLast24H": "Logowania (24h)",
"failedLoginsLast24H": "Nieudane logowania (24h)" "failedLoginsLast24H": "Nieudane logowania (24h)",
"authorizationsLast24H": "Uwierzytelnienia (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "RAM", "mem": "RAM",
File diff suppressed because it is too large Load Diff
+25 -17
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Encerrar Sessão"
}, },
"widget": { "widget": {
"missing_type": "Tipo de Widget ausente: {{type}}", "missing_type": "Tipo de Widget ausente: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Por favor, aguarde" "wait": "Por favor, aguarde"
}, },
"search": { "search": {
"placeholder": "Pesquisar…" "placeholder": "Pesquisar…",
"provider": "Motor de pesquisa: {{name}}",
"select_provider": "Selecionar motor de busca"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Não-saudável", "unhealthy": "Não-saudável",
"not_found": "Não Encontrado", "not_found": "Não Encontrado",
"exited": "Encerrado", "exited": "Encerrado",
"partial": "Parcial" "partial": "Parcial",
"created": "Criado",
"paused": "Pausado",
"restarting": "Reiniciando",
"removing": "Removendo",
"dead": "Inativo"
}, },
"ping": { "ping": {
"error": "Erro", "error": "Erro",
@@ -133,7 +140,7 @@
"series": "Séries", "series": "Séries",
"episodes": "Episódios", "episodes": "Episódios",
"songs": "Músicas", "songs": "Músicas",
"albums": "Albums" "albums": "Álbuns"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Usuários", "users": "Usuários",
"loginsLast24H": "Inícios de sessão (24h)", "loginsLast24H": "Inícios de sessão (24h)",
"failedLoginsLast24H": "Inícios de sessão falhados (24h)" "failedLoginsLast24H": "Inícios de sessão falhados (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
@@ -951,15 +959,15 @@
"storage": "Armazenamento" "storage": "Armazenamento"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Itens manuseados",
"episodesHandled": "Episodes Handled", "episodesHandled": "Episódios tratados",
"moviesHandled": "Movies Handled", "moviesHandled": "Filmes tratados",
"showsHandled": "Shows Handled", "showsHandled": "Séries tratados",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Temporadas tratadas",
"reclaimable": "Reclaimable", "reclaimable": "Reclamável",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Filme Reclamável",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Show Reclamável",
"totalCapacity": "Total Capacity" "totalCapacity": "Capacidade total"
}, },
"netdata": { "netdata": {
"warnings": "Alertas", "warnings": "Alertas",
@@ -1247,8 +1255,8 @@
"storage": "Armazenagem" "storage": "Armazenagem"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Desejado",
"queued": "Queued", "queued": "Na fila",
"leagues": "Leagues" "leagues": "Ligas"
} }
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Va rugăm așteptați" "wait": "Va rugăm așteptați"
}, },
"search": { "search": {
"placeholder": "Caută…" "placeholder": "Caută…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Procesor", "cpu": "Procesor",
@@ -97,7 +99,12 @@
"unhealthy": "Nesănătos", "unhealthy": "Nesănătos",
"not_found": "Negăsit", "not_found": "Negăsit",
"exited": "Ieşit", "exited": "Ieşit",
"partial": "Parțial" "partial": "Parțial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Autentificări (24h)", "loginsLast24H": "Autentificări (24h)",
"failedLoginsLast24H": "Conectări eșuate (24h)" "failedLoginsLast24H": "Conectări eșuate (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+71 -63
View File
@@ -21,7 +21,7 @@
"seconds": "сек" "seconds": "сек"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Выход"
}, },
"widget": { "widget": {
"missing_type": "Отсутствует тип виджета: {{type}}", "missing_type": "Отсутствует тип виджета: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Пожалуйста, подождите" "wait": "Пожалуйста, подождите"
}, },
"search": { "search": {
"placeholder": "Поиск…" "placeholder": "Поиск…",
"provider": "Поисковая система: {{name}}",
"select_provider": "Выберите поисковую систему"
}, },
"resources": { "resources": {
"cpu": "ЦП", "cpu": "ЦП",
@@ -75,13 +77,13 @@
"no_data": "Нет доступных данных о хранилище" "no_data": "Нет доступных данных о хранилище"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Работы",
"stored": "Stored", "stored": "Сохранено",
"lastBackup": "Last Run", "lastBackup": "Последний запуск",
"nextRun": "Next Run", "nextRun": "Следующий запуск",
"running": "Running", "running": "Запущено",
"warnings": "Warnings", "warnings": "Предупреждения",
"errors": "Errors" "errors": "Ошибки"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -97,7 +99,12 @@
"unhealthy": "Нездоровый", "unhealthy": "Нездоровый",
"not_found": "Не найдено", "not_found": "Не найдено",
"exited": "Вышел", "exited": "Вышел",
"partial": "Частичный" "partial": "Частичный",
"created": "Создан",
"paused": "Приостановлен",
"restarting": "Перезапуск",
"removing": "Удаление",
"dead": "Мёртв"
}, },
"ping": { "ping": {
"error": "Ошибка", "error": "Ошибка",
@@ -133,7 +140,7 @@
"series": "Сериалы", "series": "Сериалы",
"episodes": "Эпизоды", "episodes": "Эпизоды",
"songs": "Песни", "songs": "Песни",
"albums": "Albums" "albums": "Альбомы"
}, },
"esphome": { "esphome": {
"offline": "Не в сети", "offline": "Не в сети",
@@ -235,17 +242,17 @@
"rutorrent": { "rutorrent": {
"active": "Активно", "active": "Активно",
"upload": "Отдача", "upload": "Отдача",
"download": "Скачивание" "download": "Загрузка"
}, },
"transmission": { "transmission": {
"download": "Скачивание", "download": "Загрузка",
"upload": "Отдача", "upload": "Отдача",
"leech": "Лич", "leech": "Лич",
"seed": "Сид" "seed": "Сид"
}, },
"qbittorrent": { "qbittorrent": {
"download": "Скачивание", "download": "Загрузка",
"upload": "Загрузка", "upload": "Отдача",
"leech": "Лич", "leech": "Лич",
"seed": "Сид" "seed": "Сид"
}, },
@@ -258,8 +265,8 @@
"invalid": "Некорректный" "invalid": "Некорректный"
}, },
"deluge": { "deluge": {
"download": "Скачивание", "download": "Загрузка",
"upload": "Загрузка", "upload": "Отдача",
"leech": "Лич", "leech": "Лич",
"seed": "Сид" "seed": "Сид"
}, },
@@ -268,8 +275,8 @@
"cachemissbytes": "Мисс байты кэша" "cachemissbytes": "Мисс байты кэша"
}, },
"downloadstation": { "downloadstation": {
"download": "Скачивание", "download": "Загрузка",
"upload": "Загрузка", "upload": "Отдача",
"leech": "Лич", "leech": "Лич",
"seed": "Сид" "seed": "Сид"
}, },
@@ -334,8 +341,8 @@
"latency": "Задержка" "latency": "Задержка"
}, },
"speedtest": { "speedtest": {
"upload": "Загрузка", "upload": "Отдача",
"download": "Скачивание", "download": "Загрузка",
"ping": "Пинг" "ping": "Пинг"
}, },
"portainer": { "portainer": {
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Пользователи", "users": "Пользователи",
"loginsLast24H": "Входы (24ч)", "loginsLast24H": "Входы (24ч)",
"failedLoginsLast24H": "Неудачные входы (24ч)" "failedLoginsLast24H": "Неудачные входы (24ч)",
"authorizationsLast24H": "Входы (24ч)"
}, },
"proxmox": { "proxmox": {
"mem": "Память", "mem": "Память",
@@ -480,9 +488,9 @@
"vms": "Виртуальные машины" "vms": "Виртуальные машины"
}, },
"pulse": { "pulse": {
"nodes": "Nodes", "nodes": "Узлы",
"vms": "VMs", "vms": "Виртуальные машины",
"lxcs": "LXCs" "lxcs": "Контейнеры LXC"
}, },
"glances": { "glances": {
"cpu": "ЦП", "cpu": "ЦП",
@@ -581,7 +589,7 @@
"up": "В сети", "up": "В сети",
"pending": "Ожидают", "pending": "Ожидают",
"down": "Не в сети", "down": "Не в сети",
"ok": "Ok" "ok": "OK"
}, },
"healthchecks": { "healthchecks": {
"new": "Новый", "new": "Новый",
@@ -649,7 +657,7 @@
"total": "Всего" "total": "Всего"
}, },
"pangolin": { "pangolin": {
"orgs": "Orgs", "orgs": "Организации",
"sites": "Сайты", "sites": "Сайты",
"resources": "Ресурсы", "resources": "Ресурсы",
"targets": "Цели", "targets": "Цели",
@@ -687,7 +695,7 @@
"limit": "Лимит" "limit": "Лимит"
}, },
"opnsense": { "opnsense": {
"cpu": "ЦП", "cpu": "Нагрузка ЦП",
"memory": "Активно ОЗУ", "memory": "Активно ОЗУ",
"wanUpload": "WAN Загрузка", "wanUpload": "WAN Загрузка",
"wanDownload": "WAN скачивание" "wanDownload": "WAN скачивание"
@@ -822,7 +830,7 @@
"gross_percent_today": "Сегодня", "gross_percent_today": "Сегодня",
"gross_percent_1y": "Один год", "gross_percent_1y": "Один год",
"gross_percent_max": "Все время", "gross_percent_max": "Все время",
"net_worth": "Net Worth" "net_worth": "Общая стоимость"
}, },
"audiobookshelf": { "audiobookshelf": {
"podcasts": "Подкасты", "podcasts": "Подкасты",
@@ -951,15 +959,15 @@
"storage": "Хранилище" "storage": "Хранилище"
}, },
"maintainerr": { "maintainerr": {
"itemsHandled": "Items Handled", "itemsHandled": "Элементы обрабатываются",
"episodesHandled": "Episodes Handled", "episodesHandled": "Серии обрабатываются",
"moviesHandled": "Movies Handled", "moviesHandled": "Фильмы обрабатываются",
"showsHandled": "Shows Handled", "showsHandled": "Шоу обрабатываются",
"seasonsHandled": "Seasons Handled", "seasonsHandled": "Сезоны обрабатываются",
"reclaimable": "Reclaimable", "reclaimable": "Восстановленные",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Восстановленный фильм",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Восстановленное шоу",
"totalCapacity": "Total Capacity" "totalCapacity": "Общая ёмкость"
}, },
"netdata": { "netdata": {
"warnings": "Предупреждения", "warnings": "Предупреждения",
@@ -968,7 +976,7 @@
"ntfy": { "ntfy": {
"title": "Название", "title": "Название",
"priority": "Приоритет", "priority": "Приоритет",
"lastReceived": "Last Received", "lastReceived": "Последние полученные",
"message": "Сообщение", "message": "Сообщение",
"tags": "Теги", "tags": "Теги",
"none": "Отсутствует", "none": "Отсутствует",
@@ -1105,7 +1113,7 @@
"apps": "Приложения", "apps": "Приложения",
"synced": "Синхронизированные", "synced": "Синхронизированные",
"outOfSync": "Не синхронизированные", "outOfSync": "Не синхронизированные",
"healthy": "Healthy", "healthy": "Здоров",
"degraded": "Деградированные", "degraded": "Деградированные",
"progressing": "Выполняются", "progressing": "Выполняются",
"missing": "Отсутствует", "missing": "Отсутствует",
@@ -1152,18 +1160,18 @@
"other": "Другое" "other": "Другое"
}, },
"checkmk": { "checkmk": {
"serviceErrors": "Service issues", "serviceErrors": "Проблемы сервиса",
"hostErrors": "Host issues" "hostErrors": "Проблемы узла"
}, },
"komodo": { "komodo": {
"total": "Всего", "total": "Всего",
"running": "Запущено", "running": "Запущено",
"stopped": "Остановлено", "stopped": "Остановлено",
"down": "Не в сети", "down": "Не в сети",
"unhealthy": "Unhealthy", "unhealthy": "Неисправен",
"unknown": "Неизвестно", "unknown": "Неизвестно",
"servers": "Серверы", "servers": "Серверы",
"stacks": "Stacks", "stacks": "Стеки",
"containers": "Контейнеры" "containers": "Контейнеры"
}, },
"filebrowser": { "filebrowser": {
@@ -1186,7 +1194,7 @@
"DISABLE_DISK": "Диск отключен", "DISABLE_DISK": "Диск отключен",
"SWAP_DSBL": "Swap отключён", "SWAP_DSBL": "Swap отключён",
"INVALID_EXPANSION": "Неверное Расширение", "INVALID_EXPANSION": "Неверное Расширение",
"PARITY_NOT_BIGGEST": "Parity Not Biggest", "PARITY_NOT_BIGGEST": "Четность не наибольшая",
"TOO_MANY_MISSING_DISKS": "Слишком много отсутствующих дисков", "TOO_MANY_MISSING_DISKS": "Слишком много отсутствующих дисков",
"NEW_DISK_TOO_SMALL": "Новый диск слишком мал", "NEW_DISK_TOO_SMALL": "Новый диск слишком мал",
"NO_DATA_DISKS": "Нет дисков данных", "NO_DATA_DISKS": "Нет дисков данных",
@@ -1195,18 +1203,18 @@
"cpu": "ЦП", "cpu": "ЦП",
"memoryUsed": "Использовано ОЗУ", "memoryUsed": "Использовано ОЗУ",
"memoryAvailable": "Доступная память", "memoryAvailable": "Доступная память",
"arrayUsed": "Array Used", "arrayUsed": "Используемый массив",
"arrayFree": "Array Free", "arrayFree": "Свободный массив",
"poolUsed": "{{pool}} Использовано", "poolUsed": "{{pool}} Использовано",
"poolFree": "{{pool}} Свободно" "poolFree": "{{pool}} Свободно"
}, },
"backrest": { "backrest": {
"num_plans": "Plans", "num_plans": "Планы",
"num_success_30": "Successes", "num_success_30": "Успешные",
"num_failure_30": "Ошибки", "num_failure_30": "Ошибки",
"num_success_latest": "Succeeding", "num_success_latest": "Успешно",
"num_failure_latest": "Failing", "num_failure_latest": "Неудачные",
"bytes_added_30": "Bytes Added" "bytes_added_30": "Байты добавлены"
}, },
"yourspotify": { "yourspotify": {
"songs": "Треков", "songs": "Треков",
@@ -1235,20 +1243,20 @@
"environment_not_found": "Среда не найдена" "environment_not_found": "Среда не найдена"
}, },
"sparkyfitness": { "sparkyfitness": {
"eaten": "Eaten", "eaten": "Съедено",
"burned": "Burned", "burned": "Сожжено",
"remaining": "Remaining", "remaining": "Осталось",
"steps": "Steps" "steps": "Шаги"
}, },
"syncthing": { "syncthing": {
"connected": "Connected", "connected": "Подключено",
"synced": "Synced", "synced": "Синхронизировано",
"errors": "Errors", "errors": "Ошибки",
"storage": "Storage" "storage": "Хранилище"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Требуется",
"queued": "Queued", "queued": "В очереди",
"leagues": "Leagues" "leagues": "Лиги"
} }
} }
+15 -7
View File
@@ -21,7 +21,7 @@
"seconds": "s" "seconds": "s"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Odhlásiť sa"
}, },
"widget": { "widget": {
"missing_type": "Chýba typ widgetu: {{type}}", "missing_type": "Chýba typ widgetu: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Počkajte prosím" "wait": "Počkajte prosím"
}, },
"search": { "search": {
"placeholder": "Hľadať…" "placeholder": "Hľadať…",
"provider": "Vyhľadávač: {{name}}",
"select_provider": "Vyberte vyhľadávač"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Nezdravý", "unhealthy": "Nezdravý",
"not_found": "Nenájdené", "not_found": "Nenájdené",
"exited": "Ukončené", "exited": "Ukončené",
"partial": "Čiastočný" "partial": "Čiastočný",
"created": "Vytvorené",
"paused": "Pozastavené",
"restarting": "Reštartuje sa",
"removing": "Odstraňuje sa",
"dead": "Mŕtve"
}, },
"ping": { "ping": {
"error": "Chyba", "error": "Chyba",
@@ -133,7 +140,7 @@
"series": "Seriálov", "series": "Seriálov",
"episodes": "Epizód", "episodes": "Epizód",
"songs": "Skladieb", "songs": "Skladieb",
"albums": "Albums" "albums": "Albumov"
}, },
"esphome": { "esphome": {
"offline": "Nedostupné", "offline": "Nedostupné",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Používateľov", "users": "Používateľov",
"loginsLast24H": "Prihlás. (24 hod.)", "loginsLast24H": "Prihlás. (24 hod.)",
"failedLoginsLast24H": "Neúspešné prihlás. (24 hod.)" "failedLoginsLast24H": "Neúspešné prihlás. (24 hod.)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "RAM", "mem": "RAM",
@@ -959,7 +967,7 @@
"reclaimable": "Reclaimable", "reclaimable": "Reclaimable",
"movieReclaimable": "Movie Reclaimable", "movieReclaimable": "Movie Reclaimable",
"showReclaimable": "Show Reclaimable", "showReclaimable": "Show Reclaimable",
"totalCapacity": "Total Capacity" "totalCapacity": "Celková kapacita"
}, },
"netdata": { "netdata": {
"warnings": "Upozornenia", "warnings": "Upozornenia",
@@ -1248,7 +1256,7 @@
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued", "queued": "V poradí",
"leagues": "Leagues" "leagues": "Leagues"
} }
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Prosimo počakajte" "wait": "Prosimo počakajte"
}, },
"search": { "search": {
"placeholder": "Iskanje…" "placeholder": "Iskanje…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Ni zdrav", "unhealthy": "Ni zdrav",
"not_found": "Ni najden", "not_found": "Ni najden",
"exited": "V izhodu", "exited": "V izhodu",
"partial": "Delni" "partial": "Delni",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Prijave (24h)", "loginsLast24H": "Prijave (24h)",
"failedLoginsLast24H": "Neveljavne prijave (24h)" "failedLoginsLast24H": "Neveljavne prijave (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Молим сачекајте" "wait": "Молим сачекајте"
}, },
"search": { "search": {
"placeholder": "Претражи…" "placeholder": "Претражи…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "Процесор", "cpu": "Процесор",
@@ -97,7 +99,12 @@
"unhealthy": "Нездравих", "unhealthy": "Нездравих",
"not_found": "Није пронађено", "not_found": "Није пронађено",
"exited": "Напуштених", "exited": "Напуштених",
"partial": "Делимично" "partial": "Делимично",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Грешка", "error": "Грешка",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Корисника", "users": "Корисника",
"loginsLast24H": "Пријаве (24ч)", "loginsLast24H": "Пријаве (24ч)",
"failedLoginsLast24H": "Неуспешне пријаве (24ч)" "failedLoginsLast24H": "Неуспешне пријаве (24ч)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "Меморија", "mem": "Меморија",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Vänligen vänta" "wait": "Vänligen vänta"
}, },
"search": { "search": {
"placeholder": "Sök…" "placeholder": "Sök…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Inloggningar (24h)", "loginsLast24H": "Inloggningar (24h)",
"failedLoginsLast24H": "Misslyckade inloggningar (24h)" "failedLoginsLast24H": "Misslyckade inloggningar (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "దయచేసి వేచి ఉండండి" "wait": "దయచేసి వేచి ఉండండి"
}, },
"search": { "search": {
"placeholder": "వెతకండి…" "placeholder": "వెతకండి…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "సీపియూ", "cpu": "సీపియూ",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "లాగిన్లు (24గం)", "loginsLast24H": "లాగిన్లు (24గం)",
"failedLoginsLast24H": "విఫలమైన లాగిన్‌లు (24గం)" "failedLoginsLast24H": "విఫలమైన లాగిన్‌లు (24గం)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "โปรดรอ" "wait": "โปรดรอ"
}, },
"search": { "search": {
"placeholder": "ค้นหา…" "placeholder": "ค้นหา…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "ซีพียู", "cpu": "ซีพียู",
@@ -97,7 +99,12 @@
"unhealthy": "Unhealthy", "unhealthy": "Unhealthy",
"not_found": "Not Found", "not_found": "Not Found",
"exited": "Exited", "exited": "Exited",
"partial": "Partial" "partial": "Partial",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Lütfen bekleyin" "wait": "Lütfen bekleyin"
}, },
"search": { "search": {
"placeholder": "Ara…" "placeholder": "Ara…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "İşlemci", "cpu": "İşlemci",
@@ -97,7 +99,12 @@
"unhealthy": "Sağlıksız", "unhealthy": "Sağlıksız",
"not_found": "Bulunamadı", "not_found": "Bulunamadı",
"exited": "Kapandı", "exited": "Kapandı",
"partial": "Kısmi" "partial": "Kısmi",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Hata", "error": "Hata",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Kullanıcılar", "users": "Kullanıcılar",
"loginsLast24H": "Girişler (24 Saat)", "loginsLast24H": "Girişler (24 Saat)",
"failedLoginsLast24H": "Başarısız Girişler (24 Saat)" "failedLoginsLast24H": "Başarısız Girişler (24 Saat)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "Bellek", "mem": "Bellek",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "Будь ласка, зачекайте" "wait": "Будь ласка, зачекайте"
}, },
"search": { "search": {
"placeholder": "Пошук…" "placeholder": "Пошук…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "ЦП", "cpu": "ЦП",
@@ -97,7 +99,12 @@
"unhealthy": "Нездоровий", "unhealthy": "Нездоровий",
"not_found": "Не знайдено", "not_found": "Не знайдено",
"exited": "Вийшов", "exited": "Вийшов",
"partial": "Частковий" "partial": "Частковий",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Помилка", "error": "Помилка",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Користувачі", "users": "Користувачі",
"loginsLast24H": "Вхід (протягом доби)", "loginsLast24H": "Вхід (протягом доби)",
"failedLoginsLast24H": "Невдалі входи (протягом доби)" "failedLoginsLast24H": "Невдалі входи (протягом доби)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "Пам'ять", "mem": "Пам'ять",
+208 -200
View File
@@ -21,7 +21,7 @@
"seconds": "giây" "seconds": "giây"
}, },
"auth": { "auth": {
"signout": "Sign out" "signout": "Đăng xuất"
}, },
"widget": { "widget": {
"missing_type": "Thiếu loại Widget: {{type}}", "missing_type": "Thiếu loại Widget: {{type}}",
@@ -39,7 +39,9 @@
"wait": "Vui lòng chờ" "wait": "Vui lòng chờ"
}, },
"search": { "search": {
"placeholder": "Tìm kiếm…" "placeholder": "Tìm kiếm…",
"provider": "Công cụ tìm kiếm: {{name}}",
"select_provider": "Chọn công cụ tìm kiếm"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -48,7 +50,7 @@
"free": "Dư", "free": "Dư",
"used": "Đã dùng", "used": "Đã dùng",
"load": "\n", "load": "\n",
"temp": "TEMP", "temp": "Tạm thời",
"max": "Tối đa", "max": "Tối đa",
"uptime": "UP" "uptime": "UP"
}, },
@@ -64,24 +66,24 @@
"wlan_devices": "Thiết bị trong mạng WLAN", "wlan_devices": "Thiết bị trong mạng WLAN",
"lan_users": "Người dùng mạng LAN", "lan_users": "Người dùng mạng LAN",
"wlan_users": "Người dùng mạng WLAN", "wlan_users": "Người dùng mạng WLAN",
"up": "UP", "up": "Hoạt động",
"down": "DOWN", "down": "Không hoạt động",
"wait": "Vui lòng chờ", "wait": "Vui lòng chờ",
"empty_data": "Trạng thái hệ thống phụ không xác định" "empty_data": "Trạng thái hệ thống phụ không xác định"
}, },
"unifi_drive": { "unifi_drive": {
"healthy": "Healthy", "healthy": "Bình thường",
"degraded": "Degraded", "degraded": "Suy giảm",
"no_data": "No storage data available" "no_data": "Không có dữ liệu"
}, },
"duplicati": { "duplicati": {
"jobs": "Jobs", "jobs": "Tác vụ",
"stored": "Stored", "stored": "Đã lưu",
"lastBackup": "Last Run", "lastBackup": "Lần chạy cuối",
"nextRun": "Next Run", "nextRun": "Lần chạy tiếp theo",
"running": "Running", "running": "Đang chạy",
"warnings": "Warnings", "warnings": "Cảnh báo",
"errors": "Errors" "errors": "Lỗi"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -96,8 +98,13 @@
"starting": "Đang bắt đầu", "starting": "Đang bắt đầu",
"unhealthy": "Bất thường", "unhealthy": "Bất thường",
"not_found": "Không tìm thấy", "not_found": "Không tìm thấy",
"exited": "Exited", "exited": "Đã thoát",
"partial": "Partial" "partial": "Một phần",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Lỗi", "error": "Lỗi",
@@ -118,35 +125,35 @@
"playing": "Đang chơi", "playing": "Đang chơi",
"transcoding": "Chuyển định dạng", "transcoding": "Chuyển định dạng",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams", "no_active": "Không có luồng đang hoạt động",
"movies": "Phim ảnh", "movies": "Phim ảnh",
"series": "Series", "series": "Series",
"episodes": "Episodes", "episodes": "Tập",
"songs": "Bài hát" "songs": "Bài hát"
}, },
"jellyfin": { "jellyfin": {
"playing": "Playing", "playing": "Đang phát",
"transcoding": "Transcoding", "transcoding": "Đang chuyển mã",
"bitrate": "Bitrate", "bitrate": "Tốc độ bit",
"no_active": "No Active Streams", "no_active": "Không có luồng đang hoạt động",
"movies": "Movies", "movies": "Phim",
"series": "Series", "series": "Series",
"episodes": "Episodes", "episodes": "Tập",
"songs": "Songs", "songs": "Bài hát",
"albums": "Albums" "albums": "Album"
}, },
"esphome": { "esphome": {
"offline": "Offline", "offline": "Offline",
"offline_alt": "Offline", "offline_alt": "Offline",
"online": "Online", "online": "Online",
"total": "Total", "total": "Tổng cộng",
"unknown": "Không xác định" "unknown": "Không xác định"
}, },
"evcc": { "evcc": {
"pv_power": "Production", "pv_power": "Công suất phát",
"battery_soc": "Pin", "battery_soc": "Pin",
"grid_power": "Lưới", "grid_power": "Lưới",
"home_power": "Consumption", "home_power": "Công suất tiêu thụ",
"charge_power": "Bộ sạc", "charge_power": "Bộ sạc",
"kilowatt": "kW" "kilowatt": "kW"
}, },
@@ -154,7 +161,7 @@
"download": "Tải xuống", "download": "Tải xuống",
"upload": "Tải lên", "upload": "Tải lên",
"leech": "", "leech": "",
"seed": "Seed" "seed": "Seeder"
}, },
"freshrss": { "freshrss": {
"subscriptions": "Đăng ký", "subscriptions": "Đăng ký",
@@ -173,115 +180,115 @@
"maxDown": "Tải xuống tối đa", "maxDown": "Tải xuống tối đa",
"maxUp": "Tải lên tối đa", "maxUp": "Tải lên tối đa",
"down": "Down", "down": "Down",
"up": "Up", "up": "Tải lên",
"received": "Received", "received": "Đã nhận",
"sent": "Sent", "sent": "Đã gửi",
"externalIPAddress": "Ext. IP", "externalIPAddress": "Địa chỉ IP bên ngoài",
"externalIPv6Address": "Ext. IPv6", "externalIPv6Address": "Địa chỉ IPv6 bên ngoài",
"externalIPv6Prefix": "Ext. IPv6-Prefix" "externalIPv6Prefix": "Tiền tố IPv6 bên ngoài"
}, },
"caddy": { "caddy": {
"upstreams": "Upstreams", "upstreams": "Máy chủ backend",
"requests": "Current requests", "requests": "Yêu cầu hiện tại",
"requests_failed": "Failed requests" "requests_failed": "Yêu cầu thất bại"
}, },
"changedetectionio": { "changedetectionio": {
"totalObserved": "Total Observed", "totalObserved": "Tổng số đã theo dõi",
"diffsDetected": "Diffs Detected" "diffsDetected": "Số thay đổi đã phát hiện"
}, },
"channelsdvrserver": { "channelsdvrserver": {
"shows": "Shows", "shows": "Chương trình",
"recordings": "Recordings", "recordings": "Bản ghi",
"scheduled": "Scheduled", "scheduled": "Đã lên lịch",
"passes": "Passes" "passes": "Quy tắc ghi"
}, },
"tautulli": { "tautulli": {
"playing": "Playing", "playing": "Đang phát",
"transcoding": "Transcoding", "transcoding": "Đang chuyển mã",
"bitrate": "Bitrate", "bitrate": "Tốc độ bit",
"no_active": "No Active Streams", "no_active": "Không có luồng đang hoạt động",
"plex_connection_error": "Check Plex Connection" "plex_connection_error": "Kiểm tra kết nối Plex"
}, },
"tracearr": { "tracearr": {
"no_active": "No Active Streams", "no_active": "Không có luồng đang hoạt động",
"streams": "Streams", "streams": "Luồng",
"transcodes": "Transcodes", "transcodes": "Luồng chuyển mã",
"directplay": "Direct Play", "directplay": "Phát trực tiếp",
"bitrate": "Bitrate" "bitrate": "Tốc độ bit"
}, },
"omada": { "omada": {
"connectedAp": "Connected APs", "connectedAp": "AP đã kết nối",
"activeUser": "Active devices", "activeUser": "Thiết bị đang hoạt động",
"alerts": "Alerts", "alerts": "Cảnh báo",
"connectedGateways": "Connected gateways", "connectedGateways": "Gateway đã kết nối",
"connectedSwitches": "Connected switches" "connectedSwitches": "Switch đã kết nối"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "Tốc độ",
"remaining": "Remaining", "remaining": "Còn lại",
"downloaded": "Đã tải" "downloaded": "Đã tải"
}, },
"plex": { "plex": {
"streams": "Active Streams", "streams": "Luồng đang hoạt động",
"albums": "Albums", "albums": "Album",
"movies": "Movies", "movies": "Phim",
"tv": "TV Shows" "tv": "Chương trình TV"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "Tốc độ",
"queue": "Hàng chờ", "queue": "Hàng chờ",
"timeleft": "Thời gian còn lại" "timeleft": "Thời gian còn lại"
}, },
"rutorrent": { "rutorrent": {
"active": "Hoạt động", "active": "Hoạt động",
"upload": "Upload", "upload": "Tải lên",
"download": "Download" "download": "Tải xuống"
}, },
"transmission": { "transmission": {
"download": "Download", "download": "Tải xuống",
"upload": "Upload", "upload": "Tải lên",
"leech": "Leech", "leech": "Leecher",
"seed": "Seed" "seed": "Seeder"
}, },
"qbittorrent": { "qbittorrent": {
"download": "Download", "download": "Tải xuống",
"upload": "Upload", "upload": "Tải lên",
"leech": "Leech", "leech": "Leecher",
"seed": "Seed" "seed": "Seeder"
}, },
"qnap": { "qnap": {
"cpuUsage": "CPU Usage", "cpuUsage": "Mức sử dụng CPU",
"memUsage": "MEM Usage", "memUsage": "Mức sử dụng bộ nhớ",
"systemTempC": "System Temp", "systemTempC": "Nhiệt độ hệ thống",
"poolUsage": "Pool Usage", "poolUsage": "Mức sử dụng pool",
"volumeUsage": "Volume Usage", "volumeUsage": "Mức sử dụng volume",
"invalid": "Invalid" "invalid": "Không hợp lệ"
}, },
"deluge": { "deluge": {
"download": "Download", "download": "Tải xuống",
"upload": "Upload", "upload": "Tải lên",
"leech": "Leech", "leech": "Leecher",
"seed": "Seed" "seed": "Seeder"
}, },
"develancacheui": { "develancacheui": {
"cachehitbytes": "Cache Hit Bytes", "cachehitbytes": "Dung lượng cache hit",
"cachemissbytes": "Cache Miss Bytes" "cachemissbytes": "Dung lượng cache miss"
}, },
"downloadstation": { "downloadstation": {
"download": "Download", "download": "Tải xuống",
"upload": "Upload", "upload": "Tải lên",
"leech": "Leech", "leech": "Leecher",
"seed": "Seed" "seed": "Seeder"
}, },
"sonarr": { "sonarr": {
"wanted": "Wanted", "wanted": "Cần tải",
"queued": "Queued", "queued": "Queued",
"series": "Series", "series": "Phim bộ",
"queue": "Queue", "queue": "Queue",
"unknown": "Unknown" "unknown": "Unknown"
}, },
"radarr": { "radarr": {
"wanted": "Wanted", "wanted": "Cần tải",
"missing": "Missing", "missing": "Missing",
"queued": "Queued", "queued": "Queued",
"movies": "Movies", "movies": "Movies",
@@ -289,12 +296,12 @@
"unknown": "Unknown" "unknown": "Unknown"
}, },
"lidarr": { "lidarr": {
"wanted": "Wanted", "wanted": "Cần tải",
"queued": "Queued", "queued": "Queued",
"artists": "Artists" "artists": "Artists"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "Cần tải",
"queued": "Queued", "queued": "Queued",
"books": "Sách" "books": "Sách"
}, },
@@ -334,66 +341,66 @@
"latency": "Latency" "latency": "Latency"
}, },
"speedtest": { "speedtest": {
"upload": "Upload", "upload": "Tải lên",
"download": "Download", "download": "Tải xuống",
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Running", "running": "Đang chạy",
"stopped": "Stopped", "stopped": "Đã dừng",
"total": "Total" "total": "Tổng cộng"
}, },
"suwayomi": { "suwayomi": {
"download": "Downloaded", "download": "Đã tải xuống",
"nondownload": "Non-Downloaded", "nondownload": "Chưa tải xuống",
"read": "Read", "read": "Đã đọc",
"unread": "Unread", "unread": "Chưa đọc",
"downloadedread": "Downloaded & Read", "downloadedread": "Đã tải xuống & Đã đọc",
"downloadedunread": "Downloaded & Unread", "downloadedunread": "Đã tải xuống & Chưa đọc",
"nondownloadedread": "Non-Downloaded & Read", "nondownloadedread": "Chưa tải xuống & Đã đọc",
"nondownloadedunread": "Non-Downloaded & Unread" "nondownloadedunread": "Chưa tải xuống & Chưa đọc"
}, },
"tailscale": { "tailscale": {
"address": "Address", "address": "Địa chỉ",
"expires": "Expires", "expires": "Hết hạn",
"never": "Never", "never": "Không bao giờ",
"user": "User", "user": "Người dùng",
"hostname": "Hostname", "hostname": "Tên máy chủ",
"name": "Name", "name": "Tên",
"client_version": "Client Version", "client_version": "Phiên bản máy khách",
"os": "OS", "os": "Hệ điều hành",
"created": "Created", "created": "Đã tạo",
"authorized": "Authorized", "authorized": "Đã cấp quyền",
"is_external": "Is External", "is_external": "Là bên ngoài",
"update_available": "Update Available", "update_available": "Có bản cập nhật",
"tags": "Tags", "tags": "Thẻ",
"last_seen": "Last Seen", "last_seen": "Lần cuối nhìn thấy",
"now": "Now", "now": "Bây giờ",
"years": "{{number}}y", "years": "{{number}} năm",
"weeks": "{{number}}w", "weeks": "{{number}} tuần",
"days": "{{number}}d", "days": "{{number}} ngày",
"hours": "{{number}}h", "hours": "{{number}} giờ",
"minutes": "{{number}}m", "minutes": "{{number}} phút",
"seconds": "{{number}}s", "seconds": "{{number}} giây",
"ago": "{{value}} Ago", "ago": "{{value}} trước",
"true": "Yes", "true": "",
"false": "No" "false": "Không"
}, },
"technitium": { "technitium": {
"totalQueries": "Queries", "totalQueries": "Tổng số truy vấn",
"totalNoError": "Success", "totalNoError": "Thành công",
"totalServerFailure": "Failures", "totalServerFailure": "Lỗi máy chủ",
"totalNxDomain": "NX Domains", "totalNxDomain": "NX Domain",
"totalRefused": "Refused", "totalRefused": "Bị từ chối",
"totalAuthoritative": "Authoritative", "totalAuthoritative": "Authoritative",
"totalRecursive": "Recursive", "totalRecursive": "Recursive",
"totalCached": "Cached", "totalCached": "Được lưu trong bộ nhớ đệm",
"totalBlocked": "Blocked", "totalBlocked": "Bị chặn",
"totalDropped": "Dropped", "totalDropped": "Bị loại bỏ",
"totalClients": "Clients" "totalClients": "Số máy khách"
}, },
"tdarr": { "tdarr": {
"queue": "Queue", "queue": "Hàng đợi",
"processed": "Processed", "processed": "Processed",
"errored": "Errored", "errored": "Errored",
"saved": "Saved" "saved": "Saved"
@@ -410,7 +417,7 @@
"unknown": "Unknown" "unknown": "Unknown"
}, },
"navidrome": { "navidrome": {
"nothing_streaming": "No Active Streams", "nothing_streaming": "Không có luồng đang hoạt động",
"please_wait": "Please Wait" "please_wait": "Please Wait"
}, },
"npm": { "npm": {
@@ -453,9 +460,9 @@
"domain_count": "Domains" "domain_count": "Domains"
}, },
"medusa": { "medusa": {
"wanted": "Wanted", "wanted": "Cần tải",
"queued": "Queued", "queued": "Queued",
"series": "Series" "series": "Phim bộ"
}, },
"minecraft": { "minecraft": {
"players": "Players", "players": "Players",
@@ -471,66 +478,67 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "Logins (24h)", "loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)" "failedLoginsLast24H": "Failed Logins (24h)",
"authorizationsLast24H": "Lượt ủy quyền (24 giờ)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "Bộ nhớ",
"cpu": "CPU", "cpu": "CPU",
"lxc": "LXC", "lxc": "LXC",
"vms": "VMs" "vms": "VM"
}, },
"pulse": { "pulse": {
"nodes": "Nodes", "nodes": "Node",
"vms": "VMs", "vms": "VM",
"lxcs": "LXCs" "lxcs": "LXC"
}, },
"glances": { "glances": {
"cpu": "CPU", "cpu": "CPU",
"load": "Load", "load": "Tải hệ thống",
"wait": "Please wait", "wait": "Vui lòng đợi",
"temp": "TEMP", "temp": "Nhiệt độ",
"_temp": "Temp", "_temp": "Nhiệt độ",
"warn": "Warn", "warn": "Cảnh báo",
"uptime": "UP", "uptime": "Thời gian hoạt động",
"total": "Total", "total": "Tổng cộng",
"free": "Free", "free": "Còn trống",
"used": "Used", "used": "Đã sử dụng",
"days": "d", "days": "ngày",
"hours": "h", "hours": "giờ",
"crit": "Crit", "crit": "Nghiêm trọng",
"read": "Read", "read": "Đọc",
"write": "Write", "write": "Ghi",
"gpu": "GPU", "gpu": "GPU",
"mem": "Mem", "mem": "Bộ nhớ",
"swap": "Swap" "swap": "Swap"
}, },
"quicklaunch": { "quicklaunch": {
"bookmark": "Bookmark", "bookmark": "Dấu trang",
"service": "Service", "service": "Dịch vụ",
"search": "Search", "search": "Tìm kiếm",
"custom": "Custom", "custom": "Tùy chỉnh",
"visit": "Visit", "visit": "Truy cập",
"url": "URL", "url": "URL",
"searchsuggestion": "Suggestion" "searchsuggestion": "Gợi ý"
}, },
"wmo": { "wmo": {
"0-day": "Sunny", "0-day": "Trời nắng",
"0-night": "Clear", "0-night": "Trời quang",
"1-day": "Mainly Sunny", "1-day": "Chủ yếu có nắng",
"1-night": "Mainly Clear", "1-night": "Chủ yếu trời quang",
"2-day": "Partly Cloudy", "2-day": "Có mây từng phần",
"2-night": "Partly Cloudy", "2-night": "Có mây từng phần",
"3-day": "Cloudy", "3-day": "Nhiều mây",
"3-night": "Cloudy", "3-night": "Nhiều mây",
"45-day": "Foggy", "45-day": "Có sương mù",
"45-night": "Foggy", "45-night": "Có sương mù",
"48-day": "Foggy", "48-day": "Có sương mù",
"48-night": "Foggy", "48-night": "Có sương mù",
"51-day": "Light Drizzle", "51-day": "Mưa phùn nhẹ",
"51-night": "Light Drizzle", "51-night": "Mưa phùn nhẹ",
"53-day": "Drizzle", "53-day": "Mưa phùn",
"53-night": "Drizzle", "53-night": "Mưa phùn",
"55-day": "Heavy Drizzle", "55-day": "Mưa phùn lớn",
"55-night": "Heavy Drizzle", "55-night": "Heavy Drizzle",
"56-day": "Light Freezing Drizzle", "56-day": "Light Freezing Drizzle",
"56-night": "Light Freezing Drizzle", "56-night": "Light Freezing Drizzle",
@@ -738,14 +746,14 @@
"m": "m" "m": "m"
}, },
"atsumeru": { "atsumeru": {
"series": "Series", "series": "Bộ truyện",
"archives": "Archives", "archives": "Archives",
"chapters": "Chapters", "chapters": "Chapters",
"categories": "Categories" "categories": "Categories"
}, },
"komga": { "komga": {
"libraries": "Libraries", "libraries": "Libraries",
"series": "Series", "series": "Bộ truyện",
"books": "Books" "books": "Books"
}, },
"diskstation": { "diskstation": {
@@ -758,9 +766,9 @@
"streams": "Streams" "streams": "Streams"
}, },
"mylar": { "mylar": {
"series": "Series", "series": "Phim bộ",
"issues": "Issues", "issues": "Issues",
"wanted": "Wanted" "wanted": "Cần tải"
}, },
"photoprism": { "photoprism": {
"albums": "Albums", "albums": "Albums",
@@ -843,7 +851,7 @@
"books": "Books", "books": "Books",
"authors": "Authors", "authors": "Authors",
"categories": "Categories", "categories": "Categories",
"series": "Series" "series": "Bộ sách"
}, },
"booklore": { "booklore": {
"libraries": "Libraries", "libraries": "Libraries",
@@ -858,7 +866,7 @@
"downloadSpeed": "Speed" "downloadSpeed": "Speed"
}, },
"kavita": { "kavita": {
"seriesCount": "Series", "seriesCount": "Số bộ truyện",
"totalFiles": "Files" "totalFiles": "Files"
}, },
"azuredevops": { "azuredevops": {
@@ -1247,7 +1255,7 @@
"storage": "Storage" "storage": "Storage"
}, },
"sportarr": { "sportarr": {
"wanted": "Wanted", "wanted": "Cần tải",
"queued": "Queued", "queued": "Queued",
"leagues": "Leagues" "leagues": "Leagues"
} }
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "請稍後" "wait": "請稍後"
}, },
"search": { "search": {
"placeholder": "搜索緊…" "placeholder": "搜索緊…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "不健康的", "unhealthy": "不健康的",
"not_found": "未找到", "not_found": "未找到",
"exited": "已退出", "exited": "已退出",
"partial": "部分" "partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "登錄( 24小时)", "loginsLast24H": "登錄( 24小时)",
"failedLoginsLast24H": "登錄失敗( 24鐘頭)" "failedLoginsLast24H": "登錄失敗( 24鐘頭)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "请稍候" "wait": "请稍候"
}, },
"search": { "search": {
"placeholder": "搜索…" "placeholder": "搜索…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "不健康", "unhealthy": "不健康",
"not_found": "未找到", "not_found": "未找到",
"exited": "已退出", "exited": "已退出",
"partial": "部分" "partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "错误", "error": "错误",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "登录 (24h)", "loginsLast24H": "登录 (24h)",
"failedLoginsLast24H": "登录失败 (24h)" "failedLoginsLast24H": "登录失败 (24h)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "内存", "mem": "内存",
+11 -3
View File
@@ -39,7 +39,9 @@
"wait": "請稍候" "wait": "請稍候"
}, },
"search": { "search": {
"placeholder": "搜尋…" "placeholder": "搜尋…",
"provider": "Search engine: {{name}}",
"select_provider": "Select search engine"
}, },
"resources": { "resources": {
"cpu": "CPU", "cpu": "CPU",
@@ -97,7 +99,12 @@
"unhealthy": "不健康的", "unhealthy": "不健康的",
"not_found": "找不到", "not_found": "找不到",
"exited": "已退出", "exited": "已退出",
"partial": "部分" "partial": "部分",
"created": "Created",
"paused": "Paused",
"restarting": "Restarting",
"removing": "Removing",
"dead": "Dead"
}, },
"ping": { "ping": {
"error": "Error", "error": "Error",
@@ -471,7 +478,8 @@
"authentik": { "authentik": {
"users": "Users", "users": "Users",
"loginsLast24H": "登入 (過去 24 小時)", "loginsLast24H": "登入 (過去 24 小時)",
"failedLoginsLast24H": "登入失敗 (過去 24 小時)" "failedLoginsLast24H": "登入失敗 (過去 24 小時)",
"authorizationsLast24H": "Auths (24h)"
}, },
"proxmox": { "proxmox": {
"mem": "MEM", "mem": "MEM",
@@ -73,6 +73,7 @@ describe("pages/api/auth/[...nextauth]", () => {
it("routes sanitized NextAuth logs through the Homepage logger", async () => { it("routes sanitized NextAuth logs through the Homepage logger", async () => {
const mod = await import("pages/api/auth/[...nextauth]"); const mod = await import("pages/api/auth/[...nextauth]");
const sensitiveMetadata = { const sensitiveMetadata = {
error: Object.assign(new Error("State cookie was missing."), { access_token: "sensitive-access-token" }),
clientSecret: "sensitive-client-secret", clientSecret: "sensitive-client-secret",
access_token: "sensitive-access-token", access_token: "sensitive-access-token",
id_token: "sensitive-id-token", id_token: "sensitive-id-token",
@@ -82,7 +83,7 @@ describe("pages/api/auth/[...nextauth]", () => {
mod.authOptions.logger.warn("NEXTAUTH_URL", sensitiveMetadata); mod.authOptions.logger.warn("NEXTAUTH_URL", sensitiveMetadata);
mod.authOptions.logger.debug("OAUTH_CALLBACK_RESPONSE", sensitiveMetadata); mod.authOptions.logger.debug("OAUTH_CALLBACK_RESPONSE", sensitiveMetadata);
expect(errorMock).toHaveBeenCalledWith("%s", "OAUTH_CALLBACK_ERROR"); expect(errorMock).toHaveBeenCalledWith("%s: %s", "OAUTH_CALLBACK_ERROR", "State cookie was missing.");
expect(warnMock).toHaveBeenCalledWith("%s", "NEXTAUTH_URL"); expect(warnMock).toHaveBeenCalledWith("%s", "NEXTAUTH_URL");
expect(debugMock).toHaveBeenCalledWith("%s", "OAUTH_CALLBACK_RESPONSE"); expect(debugMock).toHaveBeenCalledWith("%s", "OAUTH_CALLBACK_RESPONSE");
expect(JSON.stringify([...errorMock.mock.calls, ...warnMock.mock.calls, ...debugMock.mock.calls])).not.toContain( expect(JSON.stringify([...errorMock.mock.calls, ...warnMock.mock.calls, ...debugMock.mock.calls])).not.toContain(
@@ -0,0 +1,60 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { getDockerStats, logger } = vi.hoisted(() => ({
getDockerStats: vi.fn(),
logger: { error: vi.fn() },
}));
vi.mock("utils/docker/stats", () => ({ getDockerStats }));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/stats";
describe("pages/api/docker/stats", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns the stats map for the requested server", async () => {
getDockerStats.mockResolvedValue({ stats: { app: { cpu: 20, mem: 900, rx: 4, tx: 6 } } });
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(getDockerStats).toHaveBeenCalledWith("local");
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: { app: { cpu: 20, mem: 900, rx: 4, tx: 6 } } });
});
it("returns 500 when the stats lookup reports an error", async () => {
getDockerStats.mockResolvedValue({ error: "query failed" });
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("logs and returns 500 when the stats lookup throws", async () => {
getDockerStats.mockRejectedValue(new Error("boom"));
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -1,153 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, logger } = vi.hoisted(() => {
const state = {
docker: null,
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor() {
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/stats/[...service]";
describe("pages/api/docker/stats/[...service]", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.docker = {
listContainers: vi.fn(),
getContainer: vi.fn(),
listTasks: vi.fn(),
};
});
it("returns 400 when container name/server params are missing", async () => {
const req = { query: { service: [] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ error: "docker query parameters are required" });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.docker.listContainers.mockResolvedValue(Buffer.from("bad"));
const req = { query: { service: ["c", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("returns stats for an existing container", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/myapp"], Id: "cid1" }]);
const containerStats = { cpu_stats: { cpu_usage: { total_usage: 1 } } };
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockResolvedValue(containerStats),
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: containerStats });
});
it("uses swarm tasks to locate a container and reports a friendly error when stats cannot be retrieved", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" } } },
{ Status: { ContainerStatus: { ContainerID: "remote1" } } },
]);
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockRejectedValue(new Error("nope")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ error: "Unable to retrieve stats" });
});
it("returns stats for a swarm task container when present locally", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([{ Status: { ContainerStatus: { ContainerID: "local1" } } }]);
const containerStats = { cpu_stats: { cpu_usage: { total_usage: 2 } } };
state.docker.getContainer.mockReturnValue({
stats: vi.fn().mockResolvedValue(containerStats),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ stats: containerStats });
});
it("returns 404 when no container or swarm task is found", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.listTasks.mockResolvedValue([]);
const req = { query: { service: ["missing", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ error: "not found" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -1,211 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, logger } = vi.hoisted(() => {
const state = {
docker: null,
dockerCtorArgs: [],
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor(conn) {
state.dockerCtorArgs.push(conn);
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/status/[...service]";
describe("pages/api/docker/status/[...service]", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerCtorArgs.length = 0;
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.docker = {
listContainers: vi.fn(),
getContainer: vi.fn(),
getService: vi.fn(),
listTasks: vi.fn(),
};
});
it("returns 400 when container name/server params are missing", async () => {
const req = { query: { service: [] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({ error: "docker query parameters are required" });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.docker.listContainers.mockResolvedValue(Buffer.from("bad"));
const req = { query: { service: ["c", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("inspects an existing container and returns status + health", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/myapp"], Id: "cid1" }]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ State: { Status: "running", Health: { Status: "healthy" } } }),
});
const req = { query: { service: ["myapp", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(getDockerArguments).toHaveBeenCalledWith("local");
expect(state.dockerCtorArgs).toHaveLength(1);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running", health: "healthy" });
});
it("returns 404 when container does not exist and swarm is disabled", async () => {
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
const req = { query: { service: ["missing", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ status: "not found" });
});
it("reports replicated swarm service status based on desired replicas", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Replicated: { Replicas: "2" } } } }),
});
state.docker.listTasks.mockResolvedValue([{ Status: {} }, { Status: {} }]);
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running 2/2" });
});
it("reports partial status for replicated services with fewer running tasks", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Replicated: { Replicas: "3" } } } }),
});
state.docker.listTasks.mockResolvedValue([{ Status: {} }]);
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "partial 1/3" });
});
it("handles global services by inspecting a local task container when possible", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Global: {} } } }),
});
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" }, State: "running" } },
]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ State: { Status: "running", Health: { Status: "unhealthy" } } }),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "running", health: "unhealthy" });
});
it("falls back to task status when global service container inspect fails", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "local1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockResolvedValue({ Spec: { Mode: { Global: {} } } }),
});
state.docker.listTasks.mockResolvedValue([
{ Status: { ContainerStatus: { ContainerID: "local1" }, State: "pending" } },
]);
state.docker.getContainer.mockReturnValue({
inspect: vi.fn().mockRejectedValue(new Error("nope")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ status: "pending" });
});
it("returns 404 when swarm is enabled but the service does not exist", async () => {
state.dockerArgs.swarm = true;
state.docker.listContainers.mockResolvedValue([{ Names: ["/other"], Id: "cid1" }]);
state.docker.getService.mockReturnValue({
inspect: vi.fn().mockRejectedValue(new Error("not found")),
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(404);
expect(res.body).toEqual({ status: "not found" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { service: ["svc", "local"] } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
@@ -0,0 +1,265 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import createMockRes from "test-utils/create-mock-res";
const { state, DockerCtor, getDockerArguments, containersFromConfig, hasHomepageLabels, getSettings, logger } =
vi.hoisted(() => {
const state = {
docker: null,
containers: [],
health: {},
containers: [],
health: {},
dockerArgs: { conn: { socketPath: "/var/run/docker.sock" }, swarm: false },
};
function DockerCtor() {
return state.docker;
}
return {
state,
DockerCtor,
getDockerArguments: vi.fn(() => state.dockerArgs),
containersFromConfig: vi.fn(async () => new Set()),
hasHomepageLabels: vi.fn(() => false),
getSettings: vi.fn(() => ({ instanceName: undefined })),
logger: { error: vi.fn() },
};
});
vi.mock("dockerode", () => ({
default: DockerCtor,
}));
vi.mock("utils/config/docker", () => ({
default: getDockerArguments,
}));
vi.mock("utils/config/service-helpers", () => ({
containersFromConfig,
hasHomepageLabels,
}));
vi.mock("utils/config/config", () => ({
getSettings,
}));
vi.mock("utils/logger", () => ({
default: () => logger,
}));
import handler from "pages/api/docker/statuses";
describe("pages/api/docker/statuses", () => {
beforeEach(() => {
vi.clearAllMocks();
state.dockerArgs = { conn: { socketPath: "/var/run/docker.sock" }, swarm: false };
state.containers = [];
state.health = {};
state.docker = {
listContainers: vi.fn(async (options) => {
const health = options?.filters?.health?.[0];
return health ? (state.health[health] ?? []) : state.containers;
}),
listServices: vi.fn(),
listTasks: vi.fn(),
};
containersFromConfig.mockResolvedValue(new Set());
hasHomepageLabels.mockReturnValue(false);
getSettings.mockReturnValue({ instanceName: undefined });
});
it("returns configured container statuses with health from the docker health filters", async () => {
containersFromConfig.mockResolvedValue(new Set(["glance", "share"]));
state.containers = [
{ Names: ["/glance"], Id: "cid-glance", State: "running" },
{ Names: ["/share"], Id: "cid-share", State: "exited" },
];
state.health = { healthy: [{ Id: "cid-glance" }] };
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(getDockerArguments).toHaveBeenCalledWith("local");
expect(containersFromConfig).toHaveBeenCalledWith("local");
// one list plus one per health state, constant regardless of container count
expect(state.docker.listContainers).toHaveBeenCalledTimes(4);
expect(state.docker.listServices).not.toHaveBeenCalled();
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
statuses: {
glance: { status: "running", health: "healthy" },
share: { status: "exited" },
},
});
});
it("takes health from the filter results rather than the status string", async () => {
containersFromConfig.mockResolvedValue(new Set(["app"]));
state.containers = [{ Names: ["/app"], Id: "cid1", State: "running", Status: "Up 2 hours (healthy)" }];
state.health = { unhealthy: [{ Id: "cid1" }] };
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { app: { status: "running", health: "unhealthy" } } });
});
it("omits health when the docker daemon rejects the health filter", async () => {
containersFromConfig.mockResolvedValue(new Set(["app"]));
state.containers = [{ Names: ["/app"], Id: "cid1", State: "running", Status: "Up 2 hours (healthy)" }];
state.docker.listContainers.mockImplementation(async (options) => {
if (options?.filters?.health) throw new Error("filter unsupported");
return state.containers;
});
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { app: { status: "running" } } });
});
it("omits containers that are neither configured nor labelled for homepage", async () => {
containersFromConfig.mockResolvedValue(new Set(["glance"]));
state.containers = [
{ Names: ["/glance"], State: "running", Status: "Up" },
{ Names: ["/secret-db"], State: "running", Status: "Up" },
];
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { glance: { status: "running" } } });
expect(res.body.statuses["secret-db"]).toBeUndefined();
});
it("includes containers discovered through homepage labels", async () => {
hasHomepageLabels.mockImplementation((labels) => labels?.["homepage.name"] !== undefined);
state.containers = [
{ Names: ["/labelled"], State: "running", Status: "Up", Labels: { "homepage.name": "App" } },
{ Names: ["/unlabelled"], State: "running", Status: "Up", Labels: {} },
];
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { labelled: { status: "running" } } });
});
it("includes swarm services when the server is in swarm mode", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set(["web", "api"]));
state.containers = [{ Names: ["/web"], Id: "cid1", State: "running", Status: "Up" }];
state.docker.listServices.mockResolvedValue([
{ ID: "sid", Spec: { Name: "api", Mode: { Replicated: { Replicas: "2" } } } },
]);
state.docker.listTasks.mockResolvedValue([
{ ServiceID: "sid", Status: {} },
{ ServiceID: "sid", Status: {} },
]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({
statuses: {
web: { status: "running" },
api: { status: "running 2/2" },
},
});
});
it("omits swarm services that are neither configured nor labelled", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set());
state.containers = [];
state.docker.listServices.mockResolvedValue([
{ ID: "sid", Spec: { Name: "internal", Mode: { Replicated: { Replicas: "1" } } } },
]);
state.docker.listTasks.mockResolvedValue([{ ServiceID: "sid", Status: {} }]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: {} });
});
it("includes swarm services discovered through homepage labels", async () => {
state.dockerArgs.swarm = true;
hasHomepageLabels.mockImplementation((labels) => labels?.["homepage.name"] !== undefined);
state.containers = [];
state.docker.listServices.mockResolvedValue([
{
ID: "sid",
Spec: { Name: "api", Labels: { "homepage.name": "Api" }, Mode: { Replicated: { Replicas: "1" } } },
},
]);
state.docker.listTasks.mockResolvedValue([{ ServiceID: "sid", Status: {} }]);
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.body).toEqual({ statuses: { api: { status: "running 1/1" } } });
});
it("returns only listed containers when swarm queries fail", async () => {
state.dockerArgs.swarm = true;
containersFromConfig.mockResolvedValue(new Set(["web"]));
state.containers = [{ Names: ["/web"], Id: "cid1", State: "running", Status: "Up" }];
state.docker.listServices.mockRejectedValue(new Error("no services"));
state.docker.listTasks.mockRejectedValue(new Error("no tasks"));
const req = { query: { server: "swarm" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual({ statuses: { web: { status: "running" } } });
});
it("returns 500 when docker returns a non-array containers payload", async () => {
state.containers = Buffer.from("bad");
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: "query failed" });
});
it("logs and returns 500 when the docker query throws", async () => {
getDockerArguments.mockImplementationOnce(() => {
throw new Error("boom");
});
const req = { query: { server: "local" } };
const res = createMockRes();
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res.body).toEqual({ error: { message: "boom" } });
expect(logger.error).toHaveBeenCalled();
});
});
+151 -8
View File
@@ -1,26 +1,48 @@
// @vitest-environment jsdom // @vitest-environment jsdom
import { render, screen, waitFor } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest"; import { StrictMode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { getSettingsMock } = vi.hoisted(() => ({ const { getSettingsMock, authOptionsMock, signInMock, replaceMock, routerQuery } = vi.hoisted(() => ({
getSettingsMock: vi.fn(), getSettingsMock: vi.fn(),
authOptionsMock: vi.fn(),
signInMock: vi.fn(),
replaceMock: vi.fn(),
routerQuery: {},
})); }));
vi.mock("utils/config/config", () => ({ vi.mock("utils/config/config", () => ({
getSettings: getSettingsMock, getSettings: getSettingsMock,
})); }));
vi.mock("pages/api/auth/[...nextauth]", () => ({
get authOptions() {
return authOptionsMock();
},
}));
vi.mock("next/router", () => ({ vi.mock("next/router", () => ({
useRouter: () => ({ useRouter: () => ({
query: {}, query: routerQuery,
replace: replaceMock,
}), }),
})); }));
import { getProviders } from "next-auth/react"; vi.mock("next-auth/react", () => ({ signIn: signInMock }));
import SignInPage, { getServerSideProps } from "pages/auth/signin"; import SignInPage, { getServerSideProps } from "pages/auth/signin";
const OIDC_PROVIDERS = { "homepage-oidc": { id: "homepage-oidc", name: "Homepage OIDC", type: "oauth" } };
const SETTINGS = { theme: "dark", color: "slate", title: "Homepage" };
describe("pages/auth/signin", () => { describe("pages/auth/signin", () => {
beforeEach(() => {
vi.clearAllMocks();
Object.keys(routerQuery).forEach((key) => delete routerQuery[key]);
window.sessionStorage.clear();
});
it("renders an error state when no providers are configured", async () => { it("renders an error state when no providers are configured", async () => {
render( render(
<SignInPage <SignInPage
@@ -33,7 +55,7 @@ describe("pages/auth/signin", () => {
/>, />,
); );
expect(screen.getByText("Authentication not configured")).toBeInTheDocument(); expect(screen.getByText("Authentication error")).toBeInTheDocument();
await waitFor(() => { await waitFor(() => {
expect(document.documentElement.classList.contains("dark")).toBe(true); expect(document.documentElement.classList.contains("dark")).toBe(true);
@@ -60,8 +82,69 @@ describe("pages/auth/signin", () => {
expect(screen.getByRole("button", { name: /login via oidc/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /login via oidc/i })).toBeInTheDocument();
}); });
it("redirects to the provider when auto-login is enabled", () => {
routerQuery.callbackUrl = "/some/page";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).toHaveBeenCalledWith("homepage-oidc", { callbackUrl: "/some/page" });
expect(screen.getByText(/redirecting to homepage oidc/i)).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /login via/i })).not.toBeInTheDocument();
});
it("does not auto-login when the provider returned an error", () => {
routerQuery.error = "OAuthCallback";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).not.toHaveBeenCalled();
expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
});
it("does not auto-login when it is explicitly disabled in the url", () => {
routerQuery.autologin = "0";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).not.toHaveBeenCalled();
expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
});
it("redirects once under strict mode, rather than tripping its own loop guard", () => {
render(
<StrictMode>
<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />
</StrictMode>,
);
expect(signInMock).toHaveBeenCalledTimes(1);
expect(replaceMock).not.toHaveBeenCalled();
});
it("stops auto-login and hands back the page when the session never sticks", () => {
routerQuery.callbackUrl = "/some/page";
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
render(<SignInPage providers={OIDC_PROVIDERS} settings={SETTINGS} autoLogin />);
expect(signInMock).toHaveBeenCalledTimes(1);
expect(replaceMock).toHaveBeenCalledWith("/auth/signin?autologin=0&callbackUrl=%2Fsome%2Fpage");
});
it("does not auto-login the password provider", () => {
render(
<SignInPage
providers={{ credentials: { id: "credentials", name: "Password", type: "credentials" } }}
settings={SETTINGS}
autoLogin
/>,
);
expect(signInMock).not.toHaveBeenCalled();
});
it("getServerSideProps returns providers and only public sign-in settings", async () => { it("getServerSideProps returns providers and only public sign-in settings", async () => {
getProviders.mockResolvedValueOnce({ foo: { id: "foo", name: "Foo" } }); authOptionsMock.mockReturnValueOnce({ providers: [{ id: "foo", name: "Foo", type: "oauth" }] });
getSettingsMock.mockReturnValueOnce({ getSettingsMock.mockReturnValueOnce({
theme: "dark", theme: "dark",
color: "slate", color: "slate",
@@ -79,11 +162,11 @@ describe("pages/auth/signin", () => {
const res = await getServerSideProps({}); const res = await getServerSideProps({});
expect(getProviders).toHaveBeenCalled();
expect(getSettingsMock).toHaveBeenCalled(); expect(getSettingsMock).toHaveBeenCalled();
expect(res).toEqual({ expect(res).toEqual({
props: { props: {
providers: { foo: { id: "foo", name: "Foo" } }, autoLogin: false,
providers: { foo: { id: "foo", name: "Foo", type: "oauth" } },
settings: { settings: {
theme: "dark", theme: "dark",
color: "slate", color: "slate",
@@ -96,4 +179,64 @@ describe("pages/auth/signin", () => {
expect(res.props.settings).not.toHaveProperty("providers"); expect(res.props.settings).not.toHaveProperty("providers");
expect(res.props.settings).not.toHaveProperty("layout"); expect(res.props.settings).not.toHaveProperty("layout");
}); });
it("getServerSideProps enables auto-login from the environment", async () => {
authOptionsMock.mockReturnValueOnce({ providers: [] });
getSettingsMock.mockReturnValueOnce({ theme: "dark" });
vi.stubEnv("HOMEPAGE_OIDC_AUTO_LOGIN", "true");
const res = await getServerSideProps({});
expect(res.props.autoLogin).toBe(true);
vi.unstubAllEnvs();
});
it("getServerSideProps falls back to no providers when auth options fail to load", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
authOptionsMock.mockImplementationOnce(() => {
throw new Error("Homepage auth is enabled but HOMEPAGE_EXTERNAL_URL (or NEXTAUTH_URL) is missing.");
});
getSettingsMock.mockReturnValueOnce({ theme: "dark" });
const res = await getServerSideProps({});
expect(res.props.providers).toEqual({});
expect(consoleError).toHaveBeenCalled();
consoleError.mockRestore();
});
it("getServerSideProps passes only id, name and type from each provider", async () => {
authOptionsMock.mockReturnValueOnce({
providers: [
{
id: "homepage-oidc",
name: "Homepage OIDC",
type: "oauth",
issuer: "https://oidc.example",
clientId: "canary-client-id",
clientSecret: "canary-client-secret",
wellKnown: "https://oidc.example/.well-known/openid-configuration",
authorization: { params: { scope: "openid email profile" } },
profile: () => ({}),
},
{
id: "credentials",
name: "Password",
type: "credentials",
credentials: { password: { label: "Password", type: "password" } },
authorize: () => null,
},
],
});
getSettingsMock.mockReturnValueOnce({ theme: "dark" });
const res = await getServerSideProps({});
expect(res.props.providers).toEqual({
"homepage-oidc": { id: "homepage-oidc", name: "Homepage OIDC", type: "oauth" },
credentials: { id: "credentials", name: "Password", type: "credentials" },
});
// These props get serialized into the sign-in page, which is unauthenticated
expect(JSON.stringify(res.props)).not.toMatch(/canary-client-secret|canary-client-id|oidc\.example/);
});
}); });
+53 -5
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom // @vitest-environment jsdom
import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import { ColorContext } from "utils/contexts/color"; import { ColorContext } from "utils/contexts/color";
@@ -25,6 +25,7 @@ const {
throwIn: null, throwIn: null,
validateData: [], validateData: [],
hashData: null, hashData: null,
hashConfig: null,
mutateHash: vi.fn(), mutateHash: vi.fn(),
servicesData: [], servicesData: [],
bookmarksData: [], bookmarksData: [],
@@ -59,9 +60,12 @@ const {
const serverSideTranslations = vi.fn(async (language) => ({ _translations: language })); const serverSideTranslations = vi.fn(async (language) => ({ _translations: language }));
const logger = { error: vi.fn() }; const logger = { error: vi.fn() };
const useSWR = vi.fn((key) => { const useSWR = vi.fn((key, config) => {
if (key === "/api/validate") return { data: state.validateData }; if (key === "/api/validate") return { data: state.validateData };
if (key === "/api/hash") return { data: state.hashData, mutate: state.mutateHash }; if (key === "/api/hash") {
state.hashConfig = config;
return { data: state.hashData, mutate: state.mutateHash };
}
if (key === "/api/services") return { data: state.servicesData }; if (key === "/api/services") return { data: state.servicesData };
if (key === "/api/bookmarks") return { data: state.bookmarksData }; if (key === "/api/bookmarks") return { data: state.bookmarksData };
if (key === "/api/widgets") return { data: state.widgetsData }; if (key === "/api/widgets") return { data: state.widgetsData };
@@ -172,6 +176,7 @@ describe("pages/index getStaticProps", () => {
state.throwIn = null; state.throwIn = null;
state.validateData = []; state.validateData = [];
state.hashData = null; state.hashData = null;
state.hashConfig = null;
state.servicesData = []; state.servicesData = [];
state.bookmarksData = []; state.bookmarksData = [];
state.widgetsData = []; state.widgetsData = [];
@@ -351,6 +356,7 @@ describe("pages/index Index routing + SWR branches", () => {
} }
await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } }); await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } });
act(() => state.hashConfig.onSuccess(state.hashData));
await waitFor(() => { await waitFor(() => {
expect(fetchSpy).toHaveBeenCalledWith("/api/revalidate"); expect(fetchSpy).toHaveBeenCalledWith("/api/revalidate");
@@ -379,11 +385,24 @@ describe("pages/index Index routing + SWR branches", () => {
localStorage.removeItem("hash"); localStorage.removeItem("hash");
await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } }); await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } });
act(() => state.hashConfig.onSuccess(state.hashData));
await waitFor(() => { await waitFor(() => {
expect(localStorage.getItem("hash")).toBe("first-hash"); expect(localStorage.getItem("hash")).toBe("first-hash");
}); });
}); });
it("ignores a hash response without a hash", async () => {
state.validateData = [];
localStorage.setItem("hash", "old-hash");
await renderIndex({ initialSettings: { title: "Homepage", layout: {} }, settings: { layout: {} } });
expect(() => act(() => state.hashConfig.onSuccess(null))).not.toThrow();
expect(() => act(() => state.hashConfig.onSuccess({}))).not.toThrow();
expect(localStorage.getItem("hash")).toBe("old-hash");
expect(document.querySelector(".animate-spin")).toBeFalsy();
});
}); });
describe("pages/index Home behavior", () => { describe("pages/index Home behavior", () => {
@@ -419,11 +438,27 @@ describe("pages/index Home behavior", () => {
fireEvent.keyDown(document.body, { key: "a" }); fireEvent.keyDown(document.body, { key: "a" });
expect(screen.getByTestId("quicklaunch")).toHaveTextContent("open:3"); expect(screen.getByTestId("quicklaunch")).toHaveTextContent("open:3");
expect(state.quickLaunchProps.searchString).toBe("a");
fireEvent.keyDown(document.body, { key: "Escape" }); fireEvent.keyDown(document.body, { key: "Escape" });
expect(screen.getByTestId("quicklaunch")).toHaveTextContent("closed:3"); expect(screen.getByTestId("quicklaunch")).toHaveTextContent("closed:3");
}); });
it("opens search on space without seeding the query with whitespace", async () => {
await renderIndex({
initialSettings: { title: "Homepage", layout: {} },
settings: { title: "Homepage", layout: {}, language: "en" },
});
await waitFor(() => {
expect(state.quickLaunchProps).toBeTruthy();
});
fireEvent.keyDown(document.body, { key: " " });
expect(screen.getByTestId("quicklaunch")).toHaveTextContent("open:3");
expect(state.quickLaunchProps.searchString).toBe("");
});
it("renders services and bookmark groups when present", async () => { it("renders services and bookmark groups when present", async () => {
await renderIndex({ await renderIndex({
initialSettings: { title: "Homepage", layout: {} }, initialSettings: { title: "Homepage", layout: {} },
@@ -489,7 +524,7 @@ describe("pages/index Home behavior", () => {
state.widgetsData = []; state.widgetsData = [];
const { setTheme, setColor, setSettings } = await renderIndex({ const { setTheme, setColor, setSettings } = await renderIndex({
initialSettings: { title: "Homepage", layout: {} }, initialSettings: { title: "Homepage", layout: {}, favicon: "/x.ico" },
settings: { settings: {
title: "Homepage", title: "Homepage",
layout: {}, layout: {},
@@ -498,7 +533,6 @@ describe("pages/index Home behavior", () => {
color: "emerald", color: "emerald",
disableIndexing: true, disableIndexing: true,
base: "/base/", base: "/base/",
favicon: "/x.ico",
}, },
theme: "dark", theme: "dark",
color: "slate", color: "slate",
@@ -516,6 +550,20 @@ describe("pages/index Home behavior", () => {
expect(document.querySelector('link[rel="icon"]')?.getAttribute("href")).toBe("/x.ico"); expect(document.querySelector('link[rel="icon"]')?.getAttribute("href")).toBe("/x.ico");
}); });
// Safari reads the head without running JS, so the favicon has to be there before the
// settings context is populated, and mask-icon must not be
it("renders a custom favicon before the settings context is populated", async () => {
await renderIndex({
initialSettings: { title: "Homepage", layout: {}, favicon: "/x.ico" },
settings: {},
});
expect(document.querySelector('link[rel="icon"]')?.getAttribute("href")).toBe("/x.ico");
expect(document.querySelector('link[rel="apple-touch-icon"]')?.getAttribute("href")).toBe("/x.ico");
expect(document.querySelector('link[rel="mask-icon"]')).toBeNull();
expect(document.querySelector('link[rel="shortcut icon"]')).toBeNull();
});
it("marks information widgets as right-aligned for known widget types", async () => { it("marks information widgets as right-aligned for known widget types", async () => {
await renderIndex({ await renderIndex({
initialSettings: { title: "Homepage", layout: {} }, initialSettings: { title: "Homepage", layout: {} },
+3 -2
View File
@@ -1,10 +1,11 @@
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
import classNames from "classnames"; import classNames from "classnames";
import { useEffect, useRef } from "react";
import { MdKeyboardArrowDown } from "react-icons/md";
import List from "components/bookmarks/list"; import List from "components/bookmarks/list";
import ErrorBoundary from "components/errorboundry"; import ErrorBoundary from "components/errorboundry";
import ResolvedIcon from "components/resolvedicon"; import ResolvedIcon from "components/resolvedicon";
import { useEffect, useRef } from "react";
import { MdKeyboardArrowDown } from "react-icons/md";
export default function BookmarksGroup({ export default function BookmarksGroup({
bookmarks, bookmarks,
+2 -1
View File
@@ -1,6 +1,7 @@
import classNames from "classnames"; import classNames from "classnames";
import ResolvedIcon from "components/resolvedicon";
import { useContext } from "react"; import { useContext } from "react";
import ResolvedIcon from "components/resolvedicon";
import { SettingsContext } from "utils/contexts/settings"; import { SettingsContext } from "utils/contexts/settings";
export default function Item({ bookmark, iconOnly = false }) { export default function Item({ bookmark, iconOnly = false }) {
+2 -1
View File
@@ -1,8 +1,9 @@
import classNames from "classnames"; import classNames from "classnames";
import Item from "components/bookmarks/item";
import { columnMap } from "../../utils/layout/columns"; import { columnMap } from "../../utils/layout/columns";
import Item from "components/bookmarks/item";
export default function List({ bookmarks, layout, bookmarksStyle }) { export default function List({ bookmarks, layout, bookmarksStyle }) {
let classes = layout?.style === "row" ? `grid ${columnMap[layout?.columns]} gap-x-2` : "flex flex-col bookmark-list"; let classes = layout?.style === "row" ? `grid ${columnMap[layout?.columns]} gap-x-2` : "flex flex-col bookmark-list";
const style = {}; const style = {};
-1
View File
@@ -15,7 +15,6 @@ export default class ErrorBoundary extends React.Component {
// You can also log error messages to an error reporting service here // You can also log error messages to an error reporting service here
if (error || errorInfo) { if (error || errorInfo) {
// eslint-disable-next-line no-console
console.error("component error: %s, info: %s", error, errorInfo); console.error("component error: %s, info: %s", error, errorInfo);
} }
} }
+1 -1
View File
@@ -1,8 +1,8 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
/* eslint-disable jsx-a11y/alt-text */ /* eslint-disable jsx-a11y/alt-text */
import { useContext, useEffect, useRef } from "react"; import { useContext, useEffect, useRef } from "react";
import { ColorContext } from "utils/contexts/color";
import { ColorContext } from "utils/contexts/color";
import themes from "utils/styles/themes"; import themes from "utils/styles/themes";
export function Svg({ svgRef = null }) { export function Svg({ svgRef = null }) {
+134 -114
View File
@@ -3,11 +3,12 @@ import { useTranslation } from "next-i18next/pages";
import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { FiSearch } from "react-icons/fi"; import { FiSearch } from "react-icons/fi";
import useSWR from "swr"; import useSWR from "swr";
import { SettingsContext } from "utils/contexts/settings";
import ResolvedIcon from "./resolvedicon"; import ResolvedIcon from "./resolvedicon";
import { getStoredProvider, searchProviders } from "./widgets/search/search"; import { getStoredProvider, searchProviders } from "./widgets/search/search";
import { SettingsContext } from "utils/contexts/settings";
const MOBILE_BUTTON_POSITIONS = { const MOBILE_BUTTON_POSITIONS = {
"top-left": "top-4 left-4", "top-left": "top-4 left-4",
"top-right": "top-4 right-4", "top-right": "top-4 right-4",
@@ -15,6 +16,69 @@ const MOBILE_BUTTON_POSITIONS = {
"bottom-right": "bottom-4 right-4", "bottom-right": "bottom-4 right-4",
}; };
function parseUrl(searchString) {
if (!/.+[.:].+/.test(searchString)) return null; // basic test for probably a url
try {
return new URL(searchString.toLowerCase().startsWith("http") ? searchString : `https://${searchString}`);
} catch {
return null;
}
}
function getSearchResults({
hideVisitURL,
searchDescriptions,
searchProvider,
searchString,
searchSuggestions,
servicesAndBookmarks,
t,
url,
}) {
if (searchString.trim().length === 0) return [];
const results = servicesAndBookmarks.flatMap((result) => {
const nameMatch = result.name.toLowerCase().includes(searchString);
const descriptionMatch = searchDescriptions && result.description?.toLowerCase().includes(searchString);
if (!nameMatch && !descriptionMatch) return [];
return [{ ...result, ...(searchDescriptions && { priority: nameMatch ? 2 : +descriptionMatch }) }];
});
if (searchDescriptions) {
results.sort((a, b) => b.priority - a.priority);
}
if (searchProvider) {
results.push({
href: searchProvider.url + encodeURIComponent(searchString),
name: `${searchProvider.name ?? t("quicklaunch.custom")} ${t("quicklaunch.search")}`,
type: "search",
});
if (searchProvider.showSearchSuggestions && searchProvider.suggestionUrl && searchSuggestions[1]) {
results.push(
...searchSuggestions[1].map((suggestion) => ({
href: searchProvider.url + encodeURIComponent(suggestion),
name: suggestion,
type: "searchSuggestion",
})),
);
}
}
if (!hideVisitURL && url) {
results.unshift({
href: url.toString(),
name: `${t("quicklaunch.visit")} URL`,
type: "url",
});
}
return results;
}
export default function QuickLaunch({ servicesAndBookmarks, searchString, setSearchString, isOpen, setSearching }) { export default function QuickLaunch({ servicesAndBookmarks, searchString, setSearchString, isOpen, setSearching }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -23,9 +87,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
const searchField = useRef(); const searchField = useRef();
const [results, setResults] = useState([]);
const [currentItemIndex, setCurrentItemIndex] = useState(null); const [currentItemIndex, setCurrentItemIndex] = useState(null);
const [url, setUrl] = useState(null); const url = parseUrl(searchString);
const [searchSuggestions, setSearchSuggestions] = useState([]); const [searchSuggestions, setSearchSuggestions] = useState([]);
const { data: widgets } = useSWR("/api/widgets"); const { data: widgets } = useSWR("/api/widgets");
@@ -61,8 +124,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
? MOBILE_BUTTON_POSITIONS[settings.quicklaunch.mobileButtonPosition] ? MOBILE_BUTTON_POSITIONS[settings.quicklaunch.mobileButtonPosition]
: null; : null;
function openCurrentItem(newWindow) { function openCurrentItem(newWindow, index = activeItemIndex) {
const result = results[currentItemIndex]; const result = results[index];
window.open( window.open(
result.href, result.href,
newWindow ? "_blank" : (result.target ?? searchProvider?.target ?? settings.target ?? "_blank"), newWindow ? "_blank" : (result.target ?? searchProvider?.target ?? settings.target ?? "_blank"),
@@ -81,17 +144,9 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
function handleSearchChange(event) { function handleSearchChange(event) {
const rawSearchString = event.target.value; const rawSearchString = event.target.value;
try { setCurrentItemIndex(null);
if (!/.+[.:].+/g.test(rawSearchString)) throw new Error(); // basic test for probably a url // urls keep their casing, everything else is lowercased for matching
let urlString = rawSearchString; setSearchString(parseUrl(rawSearchString) ? rawSearchString : rawSearchString.toLowerCase());
if (urlString.toLowerCase().indexOf("http") !== 0) urlString = `https://${rawSearchString}`;
setUrl(new URL(urlString)); // basic validation
setSearchString(rawSearchString);
return;
} catch (e) {
setUrl(null);
}
setSearchString(rawSearchString.toLowerCase());
} }
function handleSearchKeyDown(event) { function handleSearchKeyDown(event) {
@@ -103,18 +158,19 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
} else if (event.key === "Enter" && results.length) { } else if (event.key === "Enter" && results.length) {
closeAndReset(); closeAndReset();
openCurrentItem(event.metaKey); openCurrentItem(event.metaKey);
} else if (event.key === "ArrowDown" && results[currentItemIndex + 1]) { } else if (event.key === "ArrowDown" && results[activeItemIndex + 1]) {
setCurrentItemIndex(currentItemIndex + 1); setCurrentItemIndex(activeItemIndex + 1);
event.preventDefault(); event.preventDefault();
} else if (event.key === "ArrowUp" && currentItemIndex > 0) { } else if (event.key === "ArrowUp" && activeItemIndex > 0) {
setCurrentItemIndex(currentItemIndex - 1); setCurrentItemIndex(activeItemIndex - 1);
event.preventDefault(); event.preventDefault();
} else if ( } else if (
event.key === "ArrowRight" && event.key === "ArrowRight" &&
results[currentItemIndex] && results[activeItemIndex] &&
results[currentItemIndex].type === "searchSuggestion" results[activeItemIndex].type === "searchSuggestion"
) { ) {
setSearchString(results[currentItemIndex].name); setCurrentItemIndex(null);
setSearchString(results[activeItemIndex].name);
} }
} }
@@ -124,7 +180,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
function handleItemClick(event) { function handleItemClick(event) {
closeAndReset(); closeAndReset();
openCurrentItem(event.metaKey); // in case hover doesnt fire, use the clicked item, not the highlighted one
openCurrentItem(event.metaKey, parseInt(event.currentTarget.dataset.index, 10));
} }
function handleItemKeyDown(event) { function handleItemKeyDown(event) {
@@ -138,88 +195,51 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
} }
useEffect(() => { useEffect(() => {
const abortController = new AbortController(); if (
searchString.trim().length === 0 ||
if (searchString.trim().length === 0) setResults([]); !searchProvider?.showSearchSuggestions ||
else { !searchProvider.suggestionUrl ||
let newResults = servicesAndBookmarks.filter((r) => { searchString.trim() === searchSuggestions[0]?.trim()
const nameMatch = r.name.toLowerCase().includes(searchString); ) {
let descriptionMatch; return undefined;
if (searchDescriptions) {
descriptionMatch = r.description?.toLowerCase().includes(searchString);
r.priority = nameMatch ? 2 * +nameMatch : +descriptionMatch; // eslint-disable-line no-param-reassign
}
return nameMatch || descriptionMatch;
});
if (searchDescriptions) {
newResults = newResults.sort((a, b) => b.priority - a.priority);
}
if (searchProvider) {
newResults.push({
href: searchProvider.url + encodeURIComponent(searchString),
name: `${searchProvider.name ?? t("quicklaunch.custom")} ${t("quicklaunch.search")}`,
type: "search",
});
if (searchProvider.showSearchSuggestions && searchProvider.suggestionUrl) {
if (searchString.trim() !== searchSuggestions[0]?.trim()) {
fetch(
`/api/search/searchSuggestion?query=${encodeURIComponent(searchString)}&providerName=${
searchProvider.name ?? "Custom"
}`,
{ signal: abortController.signal },
)
.then(async (searchSuggestionResult) => {
const newSearchSuggestions = await searchSuggestionResult.json();
if (newSearchSuggestions) {
if (newSearchSuggestions[1].length > 4) {
newSearchSuggestions[1] = newSearchSuggestions[1].splice(0, 4);
}
setSearchSuggestions(newSearchSuggestions);
}
})
.catch(() => {
// If there is an error, just ignore it. There just will be no search suggestions.
});
}
if (searchSuggestions[1]) {
newResults = newResults.concat(
searchSuggestions[1].map((suggestion) => ({
href: searchProvider.url + encodeURIComponent(suggestion),
name: suggestion,
type: "searchSuggestion",
})),
);
}
}
}
if (!hideVisitURL && url) {
newResults.unshift({
href: url.toString(),
name: `${t("quicklaunch.visit")} URL`,
type: "url",
});
}
setResults(newResults);
if (newResults.length) {
setCurrentItemIndex(0);
}
} }
return () => { const abortController = new AbortController();
abortController.abort();
}; fetch(
// eslint-disable-next-line react-hooks/exhaustive-deps `/api/search/searchSuggestion?query=${encodeURIComponent(searchString)}&providerName=${
}, [searchString, servicesAndBookmarks, searchDescriptions, hideVisitURL, searchSuggestions, searchProvider, url]); searchProvider.name ?? "Custom"
}`,
{ signal: abortController.signal },
)
.then(async (searchSuggestionResult) => {
const newSearchSuggestions = await searchSuggestionResult.json();
if (newSearchSuggestions) {
setCurrentItemIndex(null);
setSearchSuggestions([newSearchSuggestions[0], newSearchSuggestions[1].slice(0, 4)]);
}
})
.catch(() => {
// If there is an error, just ignore it. There just will be no search suggestions.
});
return () => abortController.abort();
}, [searchProvider, searchString, searchSuggestions]);
const results = getSearchResults({
hideVisitURL,
searchDescriptions,
searchProvider,
searchString,
searchSuggestions,
servicesAndBookmarks,
t,
url,
});
const activeItemIndex = currentItemIndex ?? (results.length ? 0 : null);
const [hidden, setHidden] = useState(true);
useEffect(() => { useEffect(() => {
function handleBackdropClick(event) { function handleBackdropClick(event) {
if (event.target?.tagName === "DIV") closeAndReset(); if (event.target?.tagName === "DIV") closeAndReset();
@@ -228,14 +248,11 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
if (isOpen) { if (isOpen) {
searchField.current.focus(); searchField.current.focus();
document.body.addEventListener("click", handleBackdropClick); document.body.addEventListener("click", handleBackdropClick);
setHidden(false);
} else { } else {
document.body.removeEventListener("click", handleBackdropClick);
searchField.current.blur(); searchField.current.blur();
setTimeout(() => {
setHidden(true);
}, 300); // disable on close
} }
return () => document.body.removeEventListener("click", handleBackdropClick);
}, [isOpen, closeAndReset]); }, [isOpen, closeAndReset]);
function highlightText(text) { function highlightText(text) {
@@ -244,7 +261,6 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
<span> <span>
{parts.map((part, i) => {parts.map((part, i) =>
part.toLowerCase() === searchString.toLowerCase() ? ( part.toLowerCase() === searchString.toLowerCase() ? (
// eslint-disable-next-line react/no-array-index-key
<span key={`${searchString}_${i}`} className="bg-theme-300/10"> <span key={`${searchString}_${i}`} className="bg-theme-300/10">
{part} {part}
</span> </span>
@@ -260,13 +276,17 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
<> <>
<div <div
className={classNames( className={classNames(
"relative z-40 ease-in-out duration-300 transition-opacity", "relative z-40 ease-in-out",
hidden && !isOpen && "hidden", isOpen ? "visible opacity-100" : "invisible opacity-0 pointer-events-none",
!hidden && isOpen && "opacity-100",
!isOpen && "opacity-0",
)} )}
style={{
transitionProperty: "opacity, visibility",
transitionDuration: "300ms, 0s",
transitionDelay: isOpen ? "0s, 0s" : "0s, 300ms",
}}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-hidden={!isOpen}
> >
<div className="fixed inset-0 bg-gray-500 opacity-50" /> <div className="fixed inset-0 bg-gray-500 opacity-50" />
<div className="fixed inset-0 z-20 overflow-y-auto"> <div className="fixed inset-0 z-20 overflow-y-auto">
@@ -298,7 +318,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
onKeyDown={handleItemKeyDown} onKeyDown={handleItemKeyDown}
className={classNames( className={classNames(
"flex flex-row w-full items-center justify-between rounded-md text-sm md:text-xl py-2 px-4 cursor-pointer text-theme-700 dark:text-theme-200", "flex flex-row w-full items-center justify-between rounded-md text-sm md:text-xl py-2 px-4 cursor-pointer text-theme-700 dark:text-theme-200",
i === currentItemIndex && "bg-theme-300/50 dark:bg-theme-700/50", i === activeItemIndex && "bg-theme-300/50 dark:bg-theme-700/50",
)} )}
> >
<div className="flex flex-row items-center mr-4 pointer-events-none"> <div className="flex flex-row items-center mr-4 pointer-events-none">
+79 -7
View File
@@ -46,13 +46,25 @@ function Wrapper({ servicesAndBookmarks = [], initialOpen = true } = {}) {
const [isOpen, setSearching] = useState(initialOpen); const [isOpen, setSearching] = useState(initialOpen);
return ( return (
<QuickLaunch <>
servicesAndBookmarks={servicesAndBookmarks} <button
searchString={searchString} type="button"
setSearchString={setSearchString} data-testid="seed-key"
isOpen={isOpen} onClick={() => {
setSearching={setSearching} setSearchString((current) => `${current}g`);
/> setSearching(true);
}}
>
seed
</button>
<QuickLaunch
servicesAndBookmarks={servicesAndBookmarks}
searchString={searchString}
setSearchString={setSearchString}
isOpen={isOpen}
setSearching={setSearching}
/>
</>
); );
} }
@@ -151,6 +163,35 @@ describe("components/quicklaunch", () => {
openSpy.mockRestore(); openSpy.mockRestore();
}); });
it("does not carry a previous url result into a search seeded by a keypress", async () => {
renderWithProviders(<Wrapper />, {
settings: {
target: "_self",
quicklaunch: {
provider: "duckduckgo",
showSearchSuggestions: false,
},
},
});
const input = screen.getByPlaceholderText("Search");
await waitFor(() => expect(input).toHaveFocus());
fireEvent.change(input, { target: { value: "example.com" } });
expect(await screen.findByText("quicklaunch.visit URL")).toBeInTheDocument();
fireEvent.keyDown(input, { key: "Escape" });
await act(async () => {
await new Promise((r) => setTimeout(r, 350));
});
// reopen by "typing" a character, the way pages/index does
fireEvent.click(screen.getByTestId("seed-key"));
expect(input).toHaveValue("g");
expect(screen.queryByText("quicklaunch.visit URL")).not.toBeInTheDocument();
});
it("closes on Escape and clears the search string after the timeout", async () => { it("closes on Escape and clears the search string after the timeout", async () => {
renderWithProviders(<Wrapper />, { renderWithProviders(<Wrapper />, {
settings: { settings: {
@@ -222,6 +263,37 @@ describe("components/quicklaunch", () => {
openSpy.mockRestore(); openSpy.mockRestore();
}); });
it("opens the clicked result even without a preceding hover event", async () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
renderWithProviders(
<Wrapper
servicesAndBookmarks={[
{ name: "Alpha", href: "https://alpha.example" },
{ name: "Alpine", href: "https://alpine.example" },
{ name: "Almond", href: "https://almond.example" },
]}
/>,
{ settings: { target: "_self", quicklaunch: { showSearchSuggestions: false } } },
);
const input = screen.getByPlaceholderText("Search");
await waitFor(() => expect(input).toHaveFocus());
fireEvent.change(input, { target: { value: "al" } });
await waitFor(() => expect(document.querySelector('button[data-index="2"]')).toBeTruthy());
// touch devices don't fire mouseEnter, so the highlighted item is still the first one
fireEvent.click(document.querySelector('button[data-index="2"]'));
await act(async () => {
await new Promise((r) => setTimeout(r, 350));
});
expect(openSpy).toHaveBeenCalledWith("https://almond.example", "_self", "noreferrer");
openSpy.mockRestore();
});
it("handles Escape on a result button (not just the input)", async () => { it("handles Escape on a result button (not just the input)", async () => {
renderWithProviders(<Wrapper servicesAndBookmarks={[{ name: "Alpha", href: "https://alpha.example" }]} />, { renderWithProviders(<Wrapper servicesAndBookmarks={[{ name: "Alpha", href: "https://alpha.example" }]} />, {
settings: { quicklaunch: { showSearchSuggestions: false } }, settings: { quicklaunch: { showSearchSuggestions: false } },
+1
View File
@@ -1,5 +1,6 @@
import Image from "next/image"; import Image from "next/image";
import { useContext } from "react"; import { useContext } from "react";
import { SettingsContext } from "utils/contexts/settings"; import { SettingsContext } from "utils/contexts/settings";
import { ThemeContext } from "utils/contexts/theme"; import { ThemeContext } from "utils/contexts/theme";
+3 -2
View File
@@ -1,12 +1,13 @@
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
import classNames from "classnames"; import classNames from "classnames";
import ResolvedIcon from "components/resolvedicon";
import List from "components/services/list";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { MdKeyboardArrowDown } from "react-icons/md"; import { MdKeyboardArrowDown } from "react-icons/md";
import { columnMap } from "../../utils/layout/columns"; import { columnMap } from "../../utils/layout/columns";
import ResolvedIcon from "components/resolvedicon";
import List from "components/services/list";
export default function ServicesGroup({ export default function ServicesGroup({
group, group,
layout, layout,
+6 -5
View File
@@ -1,10 +1,5 @@
import classNames from "classnames"; import classNames from "classnames";
import ResolvedIcon from "components/resolvedicon";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { SettingsContext } from "utils/contexts/settings";
import Docker from "widgets/docker/component";
import Kubernetes from "widgets/kubernetes/component";
import ProxmoxVM from "widgets/proxmoxvm/component";
import KubernetesStatus from "./kubernetes-status"; import KubernetesStatus from "./kubernetes-status";
import Ping from "./ping"; import Ping from "./ping";
@@ -13,6 +8,12 @@ import SiteMonitor from "./site-monitor";
import Status from "./status"; import Status from "./status";
import Widget from "./widget"; import Widget from "./widget";
import ResolvedIcon from "components/resolvedicon";
import { SettingsContext } from "utils/contexts/settings";
import Docker from "widgets/docker/component";
import Kubernetes from "widgets/kubernetes/component";
import ProxmoxVM from "widgets/proxmoxvm/component";
export default function Item({ service, groupName, useEqualHeights }) { export default function Item({ service, groupName, useEqualHeights }) {
const hasLink = service.href && service.href !== "#"; const hasLink = service.href && service.href !== "#";
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
+2 -1
View File
@@ -1,8 +1,9 @@
import classNames from "classnames"; import classNames from "classnames";
import Item from "components/services/item";
import { columnMap } from "../../utils/layout/columns"; import { columnMap } from "../../utils/layout/columns";
import Item from "components/services/item";
export default function List({ groupName, services, layout, useEqualHeights, header }) { export default function List({ groupName, services, layout, useEqualHeights, header }) {
return ( return (
<ul <ul
+40 -24
View File
@@ -1,45 +1,61 @@
import { useTranslation } from "next-i18next/pages"; import { useTranslation } from "next-i18next/pages";
import useSWR from "swr"; import useSWR from "swr";
const WARNING_CLASS = "text-orange-400/50 dark:text-orange-400/80";
// docker container states other than running
const STATE_LABELS = {
created: "docker.created",
dead: "docker.dead",
exited: "docker.exited",
"not found": "docker.not_found",
paused: "docker.paused",
removing: "docker.removing",
restarting: "docker.restarting",
};
// states that mean something is wrong rather than merely not started
const WARNING_STATES = new Set(["dead", "exited", "not found", "restarting"]);
export default function Status({ service, style }) { export default function Status({ service, style }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`); const { data: response, error } = useSWR(`/api/docker/statuses?server=${encodeURIComponent(service.server || "")}`);
const statusError = error ?? response?.error;
const { statuses } = response ?? {};
const data = statuses ? (statuses[service.container] ?? { status: "not found" }) : undefined;
let statusLabel = t("docker.unknown"); let statusLabel = t("docker.unknown");
let backgroundClass = "px-1.5 py-0.5 bg-theme-500/10 dark:bg-theme-900/50"; let backgroundClass = "px-1.5 py-0.5 bg-theme-500/10 dark:bg-theme-900/50";
let colorClass = "text-black/20 dark:text-white/40 "; let colorClass = "text-black/20 dark:text-white/40 ";
if (error) { if (statusError) {
statusLabel = t("docker.error"); statusLabel = t("docker.error");
colorClass = "text-rose-500/80"; colorClass = "text-rose-500/80";
} else if (data) { } else if (data?.status?.includes("running")) {
if (data.status?.includes("running")) { colorClass = "text-emerald-500/80";
colorClass = "text-emerald-500/80";
if (!data.health) { if (!data.health) {
statusLabel = data.status.replace("running", t("docker.running")); statusLabel = data.status.replace("running", t("docker.running"));
} else { } else {
statusLabel = data.health === "healthy" ? t("docker.healthy") : data.health; statusLabel = data.health === "healthy" ? t("docker.healthy") : data.health;
if (data.health === "starting") { if (data.health === "starting") {
statusLabel = t("docker.starting"); statusLabel = t("docker.starting");
colorClass = "text-blue-500/80"; colorClass = "text-blue-500/80";
} }
if (data.health === "unhealthy") { if (data.health === "unhealthy") {
statusLabel = t("docker.unhealthy"); statusLabel = t("docker.unhealthy");
colorClass = "text-orange-400/50 dark:text-orange-400/80"; colorClass = WARNING_CLASS;
}
} }
} }
} else if (data?.status?.startsWith("partial")) {
if (data.status === "not found" || data.status === "exited" || data.status?.startsWith("partial")) { statusLabel = data.status.replace("partial", t("docker.partial"));
if (data.status === "not found") statusLabel = t("docker.not_found"); colorClass = WARNING_CLASS;
else if (data.status === "exited") statusLabel = t("docker.exited"); } else if (data && STATE_LABELS[data.status]) {
else statusLabel = data.status.replace("partial", t("docker.partial")); statusLabel = t(STATE_LABELS[data.status]);
colorClass = "text-orange-400/50 dark:text-orange-400/80"; if (WARNING_STATES.has(data.status)) colorClass = WARNING_CLASS;
}
} }
if (style === "dot") { if (style === "dot") {
+60 -8
View File
@@ -21,8 +21,11 @@ describe("components/services/status", () => {
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(useSWR).toHaveBeenCalledWith("/api/docker/status/c/s"); expect(useSWR).toHaveBeenCalledWith("/api/docker/statuses?server=s");
expect(screen.getByText("docker.unknown")).toBeInTheDocument(); expect(screen.getByText("docker.unknown")).toBeInTheDocument();
render(<Status service={{ container: "c" }} />);
expect(useSWR).toHaveBeenCalledWith("/api/docker/statuses?server=");
}); });
it("renders error when SWR fails", () => { it("renders error when SWR fails", () => {
@@ -33,38 +36,87 @@ describe("components/services/status", () => {
expect(screen.getByText("docker.error")).toBeInTheDocument(); expect(screen.getByText("docker.error")).toBeInTheDocument();
}); });
it("renders error when the api returns an error payload with a 200-parsed body", () => {
useSWR.mockReturnValue({ data: { error: "query failed" }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.error")).toBeInTheDocument();
expect(screen.queryByText("docker.not_found")).not.toBeInTheDocument();
});
it("renders healthy/unhealthy and partial/exited/not found statuses", () => { it("renders healthy/unhealthy and partial/exited/not found statuses", () => {
useSWR.mockReturnValue({ data: { status: "running", health: "healthy" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "healthy" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.healthy")).toBeInTheDocument(); expect(screen.getByText("docker.healthy")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "running", health: "unhealthy" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "unhealthy" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.unhealthy")).toBeInTheDocument(); expect(screen.getByText("docker.unhealthy")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "partial 1/2" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "partial 1/2" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.partial 1/2")).toBeInTheDocument(); expect(screen.getByText("docker.partial 1/2")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "exited" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "exited" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.exited")).toBeInTheDocument(); expect(screen.getByText("docker.exited")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { status: "not found" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: {} }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.not_found")).toBeInTheDocument(); expect(screen.getByText("docker.not_found")).toBeInTheDocument();
}); });
it("renders starting health when container is running and starting", () => { it("renders starting health when container is running and starting", () => {
useSWR.mockReturnValue({ data: { status: "running", health: "starting" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "running", health: "starting" } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />); render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.starting")).toBeInTheDocument(); expect(screen.getByText("docker.starting")).toBeInTheDocument();
}); });
it("renders the remaining container states instead of falling through to unknown", () => {
const states = {
created: "docker.created",
paused: "docker.paused",
restarting: "docker.restarting",
removing: "docker.removing",
dead: "docker.dead",
};
Object.entries(states).forEach(([status, label]) => {
useSWR.mockReturnValue({ data: { statuses: { c: { status } } }, error: undefined });
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText(label)).toBeInTheDocument();
});
expect(screen.queryByText("docker.unknown")).not.toBeInTheDocument();
});
it("colors problem states as warnings and merely stopped states neutrally", () => {
useSWR.mockReturnValue({ data: { statuses: { c: { status: "restarting" } } }, error: undefined });
const { container: restarting } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
expect(restarting.querySelector(".bg-orange-400")).toBeInTheDocument();
useSWR.mockReturnValue({ data: { statuses: { c: { status: "paused" } } }, error: undefined });
const { container: paused } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
expect(paused.querySelector(".bg-orange-400")).not.toBeInTheDocument();
});
it("does not surface health for containers that are not running", () => {
useSWR.mockReturnValue({
data: { statuses: { c: { status: "paused", health: "unhealthy" } } },
error: undefined,
});
render(<Status service={{ container: "c", server: "s" }} />);
expect(screen.getByText("docker.paused")).toBeInTheDocument();
expect(screen.queryByText("docker.unhealthy")).not.toBeInTheDocument();
});
it("renders a dot when style is dot", () => { it("renders a dot when style is dot", () => {
useSWR.mockReturnValue({ data: { status: "running" }, error: undefined }); useSWR.mockReturnValue({ data: { statuses: { c: { status: "running" } } }, error: undefined });
const { container } = render(<Status service={{ container: "c", server: "s" }} style="dot" />); const { container } = render(<Status service={{ container: "c", server: "s" }} style="dot" />);
+1 -1
View File
@@ -1,6 +1,6 @@
import ErrorBoundary from "components/errorboundry";
import { useTranslation } from "next-i18next/pages"; import { useTranslation } from "next-i18next/pages";
import ErrorBoundary from "components/errorboundry";
import components from "widgets/components"; import components from "widgets/components";
export default function Widget({ widget, service }) { export default function Widget({ widget, service }) {
+1 -1
View File
@@ -1,9 +1,9 @@
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import { SettingsContext } from "utils/contexts/settings";
import Error from "./error"; import Error from "./error";
import { BlockHighlightContext } from "./highlight-context"; import { BlockHighlightContext } from "./highlight-context";
import { SettingsContext } from "utils/contexts/settings";
import { buildHighlightConfig } from "utils/highlights"; import { buildHighlightConfig } from "utils/highlights";
const ALIASED_WIDGETS = { const ALIASED_WIDGETS = {
+3 -3
View File
@@ -13,13 +13,13 @@ export default function Error({ error }) {
const { t } = useTranslation(); const { t } = useTranslation();
if (typeof error === "string") { if (typeof error === "string") {
error = { message: error }; // eslint-disable-line no-param-reassign error = { message: error };
} else if (typeof error === "number") { } else if (typeof error === "number") {
error = { message: `Error ${error}` }; // eslint-disable-line no-param-reassign error = { message: `Error ${error}` };
} }
if (error?.data?.error) { if (error?.data?.error) {
error = error.data.error; // eslint-disable-line no-param-reassign error = error.data.error;
} }
return ( return (
+1
View File
@@ -1,5 +1,6 @@
import classNames from "classnames"; import classNames from "classnames";
import { useContext } from "react"; import { useContext } from "react";
import { TabContext } from "utils/contexts/tab"; import { TabContext } from "utils/contexts/tab";
function slugify(tabName) { function slugify(tabName) {
+1
View File
@@ -2,6 +2,7 @@ import { Popover, Transition } from "@headlessui/react";
import classNames from "classnames"; import classNames from "classnames";
import { Fragment, useContext } from "react"; import { Fragment, useContext } from "react";
import { IoColorPalette } from "react-icons/io5"; import { IoColorPalette } from "react-icons/io5";
import { ColorContext } from "utils/contexts/color"; import { ColorContext } from "utils/contexts/color";
const colors = [ const colors = [
+5 -1
View File
@@ -12,7 +12,11 @@ export default function SignOut() {
return ( return (
<div id="signout" className="rounded-full flex align-middle self-center mr-3"> <div id="signout" className="rounded-full flex align-middle self-center mr-3">
<button type="button" onClick={() => signOut({ callbackUrl: "/" })} className="outline-hidden"> <button
type="button"
onClick={() => signOut({ callbackUrl: "/auth/signin?autologin=0" })}
className="outline-hidden"
>
<MdLogout className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" aria-hidden="true" /> <MdLogout className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" aria-hidden="true" />
<span className="sr-only">{t("auth.signout")}</span> <span className="sr-only">{t("auth.signout")}</span>
</button> </button>
+2 -1
View File
@@ -32,6 +32,7 @@ describe("components/toggles/signout", () => {
const { getByRole } = render(<SignOut />); const { getByRole } = render(<SignOut />);
fireEvent.click(getByRole("button")); fireEvent.click(getByRole("button"));
expect(signOut).toHaveBeenCalledWith({ callbackUrl: "/" }); // Not "/", which would bounce straight back into the provider when auto-login is on
expect(signOut).toHaveBeenCalledWith({ callbackUrl: "/auth/signin?autologin=0" });
}); });
}); });
+1
View File
@@ -1,5 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
import { MdDarkMode, MdLightMode, MdToggleOff, MdToggleOn } from "react-icons/md"; import { MdDarkMode, MdLightMode, MdToggleOff, MdToggleOn } from "react-icons/md";
import { ThemeContext } from "utils/contexts/theme"; import { ThemeContext } from "utils/contexts/theme";
export default function ThemeToggle() { export default function ThemeToggle() {
+6 -11
View File
@@ -1,9 +1,11 @@
import { useTranslation } from "next-i18next/pages"; import { useTranslation } from "next-i18next/pages";
import { useEffect, useState } from "react"; import { useMemo } from "react";
import Container from "../widget/container"; import Container from "../widget/container";
import Raw from "../widget/raw"; import Raw from "../widget/raw";
import useCurrentTime from "utils/hooks/use-current-time";
const textSizes = { const textSizes = {
"4xl": "text-4xl", "4xl": "text-4xl",
"3xl": "text-3xl", "3xl": "text-3xl",
@@ -18,17 +20,10 @@ const textSizes = {
export default function DateTime({ options }) { export default function DateTime({ options }) {
const { text_size: textSize, locale, format } = options; const { text_size: textSize, locale, format } = options;
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const [date, setDate] = useState("");
const dateLocale = locale ?? i18n.language; const dateLocale = locale ?? i18n.language;
const currentTime = useCurrentTime(1000);
useEffect(() => { const dateFormatter = useMemo(() => new Intl.DateTimeFormat(dateLocale, { ...format }), [dateLocale, format]);
const dateFormat = new Intl.DateTimeFormat(dateLocale, { ...format }); const date = currentTime === null ? "" : dateFormatter.format(new Date(currentTime));
setDate(dateFormat.format(new Date()));
const interval = setInterval(() => {
setDate(dateFormat.format(new Date()));
}, 1000);
return () => clearInterval(interval);
}, [date, setDate, dateLocale, format]);
return ( return (
<Container options={options} additionalClassNames="information-widget-datetime"> <Container options={options} additionalClassNames="information-widget-datetime">
+2 -1
View File
@@ -4,13 +4,14 @@ import { useContext } from "react";
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa"; import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu, FiHardDrive } from "react-icons/fi"; import { FiCpu, FiHardDrive } from "react-icons/fi";
import useSWR from "swr"; import useSWR from "swr";
import { SettingsContext } from "utils/contexts/settings";
import Error from "../widget/error"; import Error from "../widget/error";
import Resource from "../widget/resource"; import Resource from "../widget/resource";
import Resources from "../widget/resources"; import Resources from "../widget/resources";
import WidgetLabel from "../widget/widget_label"; import WidgetLabel from "../widget/widget_label";
import { SettingsContext } from "utils/contexts/settings";
const defaultCpuSensorLabels = ["cpu_thermal", "Core", "Tctl", "Temperature"]; const defaultCpuSensorLabels = ["cpu_thermal", "Core", "Tctl", "Temperature"];
function convertToFahrenheit(t) { function convertToFahrenheit(t) {
+2 -2
View File
@@ -1,8 +1,8 @@
import ResolvedIcon from "components/resolvedicon";
import Container from "../widget/container"; import Container from "../widget/container";
import Raw from "../widget/raw"; import Raw from "../widget/raw";
import ResolvedIcon from "components/resolvedicon";
export default function Logo({ options }) { export default function Logo({ options }) {
return ( return (
<Container <Container
+2 -3
View File
@@ -7,10 +7,9 @@ import Resource from "../widget/resource";
export default function Network({ options, refresh = 1500 }) { export default function Network({ options, refresh = 1500 }) {
const { t } = useTranslation(); const { t } = useTranslation();
// eslint-disable-next-line no-param-reassign const network = options.network === true ? "default" : options.network;
if (options.network === true) options.network = "default";
const { data, error } = useSWR(`/api/widgets/resources?type=network&interfaceName=${options.network}`, { const { data, error } = useSWR(`/api/widgets/resources?type=network&interfaceName=${network}`, {
refreshInterval: refresh, refreshInterval: refresh,
}); });
@@ -22,10 +22,12 @@ describe("components/widgets/resources/network", () => {
it("normalizes options.network=true to default interfaceName in the request", () => { it("normalizes options.network=true to default interfaceName in the request", () => {
useSWR.mockReturnValue({ data: undefined, error: undefined }); useSWR.mockReturnValue({ data: undefined, error: undefined });
const options = Object.freeze({ network: true });
render(<Network options={{ network: true }} />); render(<Network options={options} />);
expect(useSWR).toHaveBeenCalledWith(expect.stringContaining("interfaceName=default"), expect.any(Object)); expect(useSWR).toHaveBeenCalledWith(expect.stringContaining("interfaceName=default"), expect.any(Object));
expect(options.network).toBe(true);
}); });
it("renders rates and usage percentage when data is present", () => { it("renders rates and usage percentage when data is present", () => {

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