Compare commits

..
56 Commits
Author SHA1 Message Date
Releases Homarr 58ef0960b5 chore(release): 1.17.0 [skip ci] 2025-04-18 19:14:53 +00:00
homarr-releases[bot] 68d6967d24 chore(release): automatic release v1.17.0 2025-04-18 19:14:04 +00:00
homarr-renovate[bot] e5f0bb9792 chore(deps): update dependency eslint to ^9.25.0 (#2897)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-18 18:46:49 +00:00
Jacob HemmingandMeier Lukas 3d68f4d128 feat: add api-key support for tdarr (#2890)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-04-18 20:11:51 +02:00
Jacob Hemming cfca0ba583 fix: calculate available space correctly and filter unmounted drives (#2885) 2025-04-18 10:49:59 +00:00
Meier Lukas 9570abb2c3 chore(docs): update docs-sitemap 2025-04-18 10:52:02 +02:00
homarr-renovate[bot] 5fcb7a3b9c fix(deps): update dependency recharts to ^2.15.3 (#2893)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-18 05:18:26 +00:00
homarr-crowdin[bot] a55acb9eb9 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-18 00:49:15 +00:00
homarr-renovate[bot] d37cdbd080 fix(deps): update nextjs monorepo to v15.3.1 (#2889)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-17 15:56:13 +00:00
homarr-renovate[bot] 17174b6b96 fix(deps): update tanstack-query monorepo to ^5.74.4 (#2888)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-17 09:54:31 +00:00
homarr-crowdin[bot] 0466a2808a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-17 00:49:43 +00:00
homarr-renovate[bot] 1bfe397d68 fix(deps): update dependency zod to ^3.24.3 (#2886)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-16 22:40:20 +00:00
Meier Lukas 60de6361bb chore(docs): update docs-sitemap 2025-04-16 22:58:28 +02:00
homarr-renovate[bot] 47d7abc0d7 fix(deps): update dependency drizzle-orm to ^0.42.0 (#2882)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-16 18:51:44 +00:00
rezstjeandManuel 2d12c1e641 fix: Tdarr widget empty statistics tab (#2883)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2025-04-16 18:34:06 +00:00
Thomas Camlong 00053bda7b feat: Prefer primary color (#2665) 2025-04-16 11:34:33 +02:00
homarr-renovate[bot] 97390c6030 fix(deps): update dependency drizzle-kit to ^0.31.0 (#2881)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 13:58:27 +00:00
homarr-renovate[bot] 865a52ae7d chore(deps): update dependency @vitejs/plugin-react to ^4.4.0 (#2879)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 09:40:02 +00:00
homarr-renovate[bot] fdca513929 fix(deps): update dependency typescript-eslint to ^8.30.1 (#2876)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 01:02:52 +00:00
homarr-crowdin[bot] ce5784b6c4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-15 00:50:50 +00:00
homarr-renovate[bot] 1a4c136d41 chore(deps): update dependency @types/react to v19.1.2 (#2874)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 19:17:29 +00:00
homarr-renovate[bot] 089734b293 fix(deps): update dependency typescript-eslint to ^8.30.0 (#2873)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 17:37:45 +00:00
homarr-renovate[bot] 2e59bfcd60 chore(deps): update dependency @types/dockerode to ^3.3.38 (#2872)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 15:55:56 +00:00
homarr-renovate[bot] 02fb1b9dae fix(deps): update tanstack-query monorepo to ^5.74.3 (#2871)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 13:09:11 +00:00
homarr-renovate[bot] bf4a1cf1e8 fix(deps): update tanstack-query monorepo to ^5.74.2 (#2869)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 11:39:37 +00:00
homarr-renovate[bot] 4a0fb5d0bb chore(deps): update pnpm to v10.8.1 (#2870)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 11:31:01 +00:00
homarr-renovate[bot] c57b63b506 fix(deps): update dependency jotai to ^2.12.3 (#2868)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 01:03:30 +00:00
homarr-crowdin[bot] fa0e694ce0 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-14 00:52:46 +00:00
homarr-renovate[bot] ff1263b217 fix(deps): update dependency swagger-ui-react to ^5.21.0 (#2866)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 19:53:29 +00:00
homarr-renovate[bot] ac78e87042 fix(deps): update tanstack-query monorepo to ^5.74.0 (#2864)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 18:21:57 +00:00
homarr-renovate[bot] ec7adb03b6 chore(deps): update dependency jsdom to ^26.1.0 (#2863)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 10:41:29 +00:00
homarr-crowdin[bot] f5f39bc094 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-13 02:16:30 +00:00
homarr-renovate[bot] 4e6ab037c9 fix(deps): update tanstack-query monorepo to ^5.73.3 (#2860)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-12 16:18:00 +00:00
Andrea a29556ee8e fix(widget): format stock values and changes for better readability (#2836) 2025-04-12 15:53:40 +00:00
homarr-crowdin[bot] 71d8f7920d chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-12 00:48:49 +00:00
homarr-renovate[bot] 835e007334 chore(deps): update dependency @types/node to ^22.14.1 (#2857)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-11 21:43:33 +00:00
Meier Lukas d46b8f63c7 chore(issue-template): add missing homarr version 1.16.0 2025-04-11 21:56:00 +02:00
Releases Homarr a40ebb10c7 chore(release): 1.16.0 [skip ci] 2025-04-11 19:15:22 +00:00
homarr-releases[bot] 05dd6227fb chore(release): automatic release v1.16.0 2025-04-11 19:14:40 +00:00
Thomas CamlongandMeier Lukas 7a3c836a70 feat(boards): add quick app add menu item (#2681)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-04-11 18:55:00 +00:00
Meier Lukas 4baec7e3ff fix(boards): add missing upload button for background in settings (#2853) 2025-04-11 20:17:10 +02:00
homarr-renovate[bot] 93bd09c85e chore(deps): update dependency @types/react to v19.1.1 (#2852)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-11 15:54:41 +00:00
homarr-renovate[bot] 0ed6a79a5b fix(deps): update dependency ioredis to v5.6.1 (#2851)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-11 13:08:22 +00:00
homarr-renovate[bot] 0fe4ea9dd9 fix(deps): update trpc monorepo to ^11.1.0 (#2850)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-11 12:27:29 +00:00
homarr-crowdin[bot] d6a48d132a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-11 00:50:01 +00:00
homarr-renovate[bot] c84dd2fd79 fix(deps): update dependency dotenv to ^16.5.0 (#2848)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 22:39:51 +00:00
Kudou Sterain 94263c445b feat: support aria2 integration (#2226) 2025-04-10 19:40:40 +00:00
homarr-renovate[bot] 4b0b892250 fix(deps): update dependency octokit to ^4.1.3 (#2847)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 15:20:33 +00:00
homarr-renovate[bot] 057ff2cfc1 fix(deps): update testcontainers-node monorepo to ^10.24.2 (#2846)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 13:45:18 +00:00
homarr-renovate[bot] 45d285a76b fix(deps): update dependency swagger-ui-react to ^5.20.8 (#2844)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 11:55:51 +00:00
homarr-renovate[bot] 924a7d076e fix(deps): update dependency eslint-config-prettier to ^10.1.2 (#2842)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 10:58:06 +00:00
homarr-renovate[bot] f13be4a708 fix(deps): update dependency undici to v7.8.0 (#2841)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 10:56:42 +00:00
homarr-renovate[bot] 7fb6beceef chore(deps): update dependency proxmox-api&gt;undici to v7.8.0 (#2840)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-10 10:18:59 +00:00
homarr-renovate[bot] a1fa4297ea fix(deps): update nextjs monorepo to v15.3.0 (#2838)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-09 20:38:56 +00:00
homarr-renovate[bot] f5e3138c81 fix(deps): update tanstack-query monorepo to ^5.72.2 (#2834)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-09 14:59:53 +00:00
Meier Lukas d21755426f chore(issue-template): add missing homarr version 1.15.0 2025-04-09 16:38:50 +02:00
134 changed files with 3074 additions and 2611 deletions

No files matched your search

+2
View File
@@ -31,6 +31,8 @@ body:
label: Version
description: What version of Homarr are you running?
options:
- 1.16.0
- 1.15.0
- 1.14.0
- 1.13.1
- 1.13.0
+48
View File
@@ -1,3 +1,51 @@
## [1.17.0](https://github.com/homarr-labs/homarr/compare/v1.16.0...v1.17.0) (2025-04-18)
### Features
* add api-key support for tdarr ([#2890](https://github.com/homarr-labs/homarr/issues/2890)) ([3d68f4d](https://github.com/homarr-labs/homarr/commit/3d68f4d1283b6cb27f9532d1adf52cef78a0fb03))
* Prefer primary color ([#2665](https://github.com/homarr-labs/homarr/issues/2665)) ([00053bd](https://github.com/homarr-labs/homarr/commit/00053bda7bbcaaf359074c3b35573ee0afc1409d))
### Bug Fixes
* calculate available space correctly and filter unmounted drives ([#2885](https://github.com/homarr-labs/homarr/issues/2885)) ([cfca0ba](https://github.com/homarr-labs/homarr/commit/cfca0ba58323ec09f8f64da2fee251ef14ba914a))
* **deps:** update dependency drizzle-kit to ^0.31.0 ([#2881](https://github.com/homarr-labs/homarr/issues/2881)) ([97390c6](https://github.com/homarr-labs/homarr/commit/97390c60300f3d16dabf0710fa435c6b3ae86a6f))
* **deps:** update dependency drizzle-orm to ^0.42.0 ([#2882](https://github.com/homarr-labs/homarr/issues/2882)) ([47d7abc](https://github.com/homarr-labs/homarr/commit/47d7abc0d739b91328834e0eaea033637632f22c))
* **deps:** update dependency jotai to ^2.12.3 ([#2868](https://github.com/homarr-labs/homarr/issues/2868)) ([c57b63b](https://github.com/homarr-labs/homarr/commit/c57b63b506733459ea0c09776917042fa878829b))
* **deps:** update dependency recharts to ^2.15.3 ([#2893](https://github.com/homarr-labs/homarr/issues/2893)) ([5fcb7a3](https://github.com/homarr-labs/homarr/commit/5fcb7a3b9c279c1b402e120a4a6f08265e8d5912))
* **deps:** update dependency swagger-ui-react to ^5.21.0 ([#2866](https://github.com/homarr-labs/homarr/issues/2866)) ([ff1263b](https://github.com/homarr-labs/homarr/commit/ff1263b217e96cbb2bc6b8e98ff117c44ca9f00d))
* **deps:** update dependency typescript-eslint to ^8.30.0 ([#2873](https://github.com/homarr-labs/homarr/issues/2873)) ([089734b](https://github.com/homarr-labs/homarr/commit/089734b293a21d6eeaa7c5df5ee9930853ed2fcf))
* **deps:** update dependency typescript-eslint to ^8.30.1 ([#2876](https://github.com/homarr-labs/homarr/issues/2876)) ([fdca513](https://github.com/homarr-labs/homarr/commit/fdca513929e9e594af2d59ad17c915cbc7ac2e08))
* **deps:** update dependency zod to ^3.24.3 ([#2886](https://github.com/homarr-labs/homarr/issues/2886)) ([1bfe397](https://github.com/homarr-labs/homarr/commit/1bfe397d68a88037e35701aab8a7b4c9027ec1e3))
* **deps:** update nextjs monorepo to v15.3.1 ([#2889](https://github.com/homarr-labs/homarr/issues/2889)) ([d37cdbd](https://github.com/homarr-labs/homarr/commit/d37cdbd080c2b3a4df536ab7dad4fc4a4b3c915f))
* **deps:** update tanstack-query monorepo to ^5.73.3 ([#2860](https://github.com/homarr-labs/homarr/issues/2860)) ([4e6ab03](https://github.com/homarr-labs/homarr/commit/4e6ab037c9245103b05afe977e9c288082a2b539))
* **deps:** update tanstack-query monorepo to ^5.74.0 ([#2864](https://github.com/homarr-labs/homarr/issues/2864)) ([ac78e87](https://github.com/homarr-labs/homarr/commit/ac78e8704287f45d6a8ca26cba9b87146e1c6161))
* **deps:** update tanstack-query monorepo to ^5.74.2 ([#2869](https://github.com/homarr-labs/homarr/issues/2869)) ([bf4a1cf](https://github.com/homarr-labs/homarr/commit/bf4a1cf1e8c99ada97adf6ee118ddaafc00115e9))
* **deps:** update tanstack-query monorepo to ^5.74.3 ([#2871](https://github.com/homarr-labs/homarr/issues/2871)) ([02fb1b9](https://github.com/homarr-labs/homarr/commit/02fb1b9dae501e475353e8e2510e4f8938c4e124))
* **deps:** update tanstack-query monorepo to ^5.74.4 ([#2888](https://github.com/homarr-labs/homarr/issues/2888)) ([17174b6](https://github.com/homarr-labs/homarr/commit/17174b6b96ae16b480e2bc5eb9f8ab7b914bc843))
* Tdarr widget empty statistics tab ([#2883](https://github.com/homarr-labs/homarr/issues/2883)) ([2d12c1e](https://github.com/homarr-labs/homarr/commit/2d12c1e641398e20a70f8bfb24a48de02498496a))
* **widget:** format stock values and changes for better readability ([#2836](https://github.com/homarr-labs/homarr/issues/2836)) ([a29556e](https://github.com/homarr-labs/homarr/commit/a29556ee8ec50aa384a9a46f6f5a5fa5dfa4cdac))
## [1.16.0](https://github.com/homarr-labs/homarr/compare/v1.15.0...v1.16.0) (2025-04-11)
### Features
* **boards:** add quick app add menu item ([#2681](https://github.com/homarr-labs/homarr/issues/2681)) ([7a3c836](https://github.com/homarr-labs/homarr/commit/7a3c836a70a9b9656c6fe74fe6a4bf8e7166a030))
* support aria2 integration ([#2226](https://github.com/homarr-labs/homarr/issues/2226)) ([94263c4](https://github.com/homarr-labs/homarr/commit/94263c445b8a0d0cdbea70ebe45ef932b426204d))
### Bug Fixes
* **boards:** add missing upload button for background in settings ([#2853](https://github.com/homarr-labs/homarr/issues/2853)) ([4baec7e](https://github.com/homarr-labs/homarr/commit/4baec7e3ff33ac0973daf0ac1f0171e349787bdb))
* **deps:** update dependency dotenv to ^16.5.0 ([#2848](https://github.com/homarr-labs/homarr/issues/2848)) ([c84dd2f](https://github.com/homarr-labs/homarr/commit/c84dd2fd797ca674e4f553ddc71926e9b533daa4))
* **deps:** update dependency eslint-config-prettier to ^10.1.2 ([#2842](https://github.com/homarr-labs/homarr/issues/2842)) ([924a7d0](https://github.com/homarr-labs/homarr/commit/924a7d076ef8bdd67afcce2e9f850bf334b43c3a))
* **deps:** update dependency ioredis to v5.6.1 ([#2851](https://github.com/homarr-labs/homarr/issues/2851)) ([0ed6a79](https://github.com/homarr-labs/homarr/commit/0ed6a79a5bba25fa7a90bec00d3815b3fffae427))
* **deps:** update dependency octokit to ^4.1.3 ([#2847](https://github.com/homarr-labs/homarr/issues/2847)) ([4b0b892](https://github.com/homarr-labs/homarr/commit/4b0b89225000137f02f3b89376c46d4d01387128))
* **deps:** update dependency swagger-ui-react to ^5.20.8 ([#2844](https://github.com/homarr-labs/homarr/issues/2844)) ([45d285a](https://github.com/homarr-labs/homarr/commit/45d285a76ba46d4d4c594f1f2e822d784a281b72))
* **deps:** update dependency undici to v7.8.0 ([#2841](https://github.com/homarr-labs/homarr/issues/2841)) ([f13be4a](https://github.com/homarr-labs/homarr/commit/f13be4a70870aca99a7f49f5068dadcd7fe01c22))
* **deps:** update nextjs monorepo to v15.3.0 ([#2838](https://github.com/homarr-labs/homarr/issues/2838)) ([a1fa429](https://github.com/homarr-labs/homarr/commit/a1fa4297ead54a9f8c842b2d23001f725359e000))
* **deps:** update tanstack-query monorepo to ^5.72.2 ([#2834](https://github.com/homarr-labs/homarr/issues/2834)) ([f5e3138](https://github.com/homarr-labs/homarr/commit/f5e3138c8118295d8230c3655af1c3f11ee5d4ff))
* **deps:** update testcontainers-node monorepo to ^10.24.2 ([#2846](https://github.com/homarr-labs/homarr/issues/2846)) ([057ff2c](https://github.com/homarr-labs/homarr/commit/057ff2cfc1cf062a3c48deb28699df8e1ba89f75))
* **deps:** update trpc monorepo to ^11.1.0 ([#2850](https://github.com/homarr-labs/homarr/issues/2850)) ([0fe4ea9](https://github.com/homarr-labs/homarr/commit/0fe4ea9dd9cbfb2def1bede9db8eaf649f79f1b0))
## [1.15.0](https://github.com/homarr-labs/homarr/compare/v1.14.0...v1.15.0) (2025-04-09)
### Features
+15 -15
View File
@@ -56,25 +56,25 @@
"@mantine/tiptap": "^7.17.4",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.31.0",
"@tanstack/react-query": "^5.72.1",
"@tanstack/react-query-devtools": "^5.72.1",
"@tanstack/react-query-next-experimental": "^5.72.1",
"@trpc/client": "^11.0.4",
"@trpc/next": "^11.0.4",
"@trpc/react-query": "^11.0.4",
"@trpc/server": "^11.0.4",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-devtools": "^5.74.4",
"@tanstack/react-query-next-experimental": "^5.74.4",
"@trpc/client": "^11.1.0",
"@trpc/next": "^11.1.0",
"@trpc/react-query": "^11.1.0",
"@trpc/server": "^11.1.0",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0",
"chroma-js": "^3.1.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"flag-icons": "^7.3.2",
"glob": "^11.0.1",
"jotai": "^2.12.2",
"jotai": "^2.12.3",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.2.5",
"next": "15.3.1",
"postcss-preset-mantine": "^1.17.0",
"prismjs": "^1.30.0",
"react": "19.1.0",
@@ -83,22 +83,22 @@
"react-simple-code-editor": "^0.14.1",
"sass": "^1.86.3",
"superjson": "2.2.2",
"swagger-ui-react": "^5.20.7",
"swagger-ui-react": "^5.21.0",
"use-deep-compare-effect": "^1.8.1",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1",
"@types/node": "^22.14.0",
"@types/node": "^22.14.1",
"@types/prismjs": "^1.26.5",
"@types/react": "19.1.0",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.1.2",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"node-loader": "^2.1.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
@@ -25,9 +25,11 @@ import { useEditMode } from "@homarr/boards/edit-mode";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { env } from "@homarr/common/env";
import { useConfirmModal, useModalAction } from "@homarr/modals";
import { AppSelectModal } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useItemActions } from "~/components/board/items/item-actions";
import { ItemSelectModal } from "~/components/board/items/item-select-modal";
import { useBoardPermissions } from "~/components/board/permissions/client";
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
@@ -62,8 +64,10 @@ export const BoardContentHeaderActions = () => {
const AddMenu = () => {
const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal);
const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal);
const { openModal: openAppSelectModal } = useModalAction(AppSelectModal);
const { addCategoryToEnd } = useCategoryActions();
const { addDynamicSection } = useDynamicSectionActions();
const { createItem } = useItemActions();
const t = useI18n();
const handleAddCategory = useCallback(
@@ -90,6 +94,17 @@ const AddMenu = () => {
openItemSelectModal();
}, [openItemSelectModal]);
const handleSelectApp = useCallback(() => {
openAppSelectModal({
onSelect: (appId) => {
createItem({
kind: "app",
options: { appId },
});
},
});
}, [openAppSelectModal, createItem]);
return (
<Menu position="bottom-end" withArrow>
<Menu.Target>
@@ -101,10 +116,14 @@ const AddMenu = () => {
</HeaderButton>
</Menu.Target>
<Menu.Dropdown style={{ transform: "translate(-3px, 0)" }}>
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectItem}>
<Menu.Item leftSection={<IconResize size={20} />} onClick={handleSelectItem}>
{t("item.action.create")}
</Menu.Item>
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectApp}>
{t("app.action.add")}
</Menu.Item>
<Menu.Divider />
<Menu.Item leftSection={<IconBoxAlignTop size={20} />} onClick={handleAddCategory}>
@@ -123,7 +123,7 @@ export const ColorSettingsContent = ({ board }: Props) => {
</Grid.Col>
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -1,13 +1,15 @@
"use client";
import { Autocomplete, Button, Center, Grid, Group, Popover, Stack, Text } from "@mantine/core";
import { startTransition } from "react";
import { ActionIcon, Autocomplete, Button, Center, Grid, Group, Popover, Stack, Text } from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import { IconPhotoOff } from "@tabler/icons-react";
import { IconPhotoOff, IconUpload } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions";
import { useZodForm } from "@homarr/form";
import { UploadMedia } from "@homarr/forms-collection";
import type { TranslationObject } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import type { SelectItemWithDescriptionBadge } from "@homarr/ui";
@@ -62,58 +64,76 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
<Stack>
<Grid>
<Grid.Col span={12}>
<Autocomplete
leftSection={
form.values.backgroundImageUrl &&
form.values.backgroundImageUrl.trim().length >= 2 && (
<Popover width={300} withArrow>
<Popover.Target>
<Center h="100%">
<ImagePreview src={form.values.backgroundImageUrl} w={20} h={20} />
</Center>
</Popover.Target>
<Popover.Dropdown>
<ImagePreview src={form.values.backgroundImageUrl} w="100%" />
</Popover.Dropdown>
</Popover>
)
}
// We filter it on the server
filter={({ options }) => options}
label={t("board.field.backgroundImageUrl.label")}
placeholder={`${t("board.field.backgroundImageUrl.placeholder")}...`}
renderOption={({ option }) => {
const current = imageMap.get(option.value);
if (!current) return null;
<Group wrap="nowrap" gap="xs" w="100%" align="start">
<Autocomplete
flex={1}
leftSection={
form.values.backgroundImageUrl &&
form.values.backgroundImageUrl.trim().length >= 2 && (
<Popover width={300} withArrow>
<Popover.Target>
<Center h="100%">
<ImagePreview src={form.values.backgroundImageUrl} w={20} h={20} />
</Center>
</Popover.Target>
<Popover.Dropdown>
<ImagePreview src={form.values.backgroundImageUrl} w="100%" />
</Popover.Dropdown>
</Popover>
)
}
// We filter it on the server
filter={({ options }) => options}
label={t("board.field.backgroundImageUrl.label")}
placeholder={`${t("board.field.backgroundImageUrl.placeholder")}...`}
renderOption={({ option }) => {
const current = imageMap.get(option.value);
if (!current) return null;
return (
<Group gap="sm">
<ImagePreview src={option.value} w={20} h={20} />
<Stack gap={0}>
<Text size="sm">{current.name}</Text>
<Text size="xs" c="dimmed">
{option.value}
</Text>
</Stack>
</Group>
);
}}
data={[
{
group: t("board.field.backgroundImageUrl.group.your"),
items: images
.filter((media) => media.creatorId === session?.user.id)
.map((media) => `/api/user-medias/${media.id}`),
},
{
group: t("board.field.backgroundImageUrl.group.other"),
items: images
.filter((media) => media.creatorId !== session?.user.id)
.map((media) => `/api/user-medias/${media.id}`),
},
]}
{...form.getInputProps("backgroundImageUrl")}
/>
return (
<Group gap="sm">
<ImagePreview src={option.value} w={20} h={20} />
<Stack gap={0}>
<Text size="sm">{current.name}</Text>
<Text size="xs" c="dimmed">
{option.value}
</Text>
</Stack>
</Group>
);
}}
data={[
{
group: t("board.field.backgroundImageUrl.group.your"),
items: images
.filter((media) => media.creatorId === session?.user.id)
.map((media) => `/api/user-medias/${media.id}`),
},
{
group: t("board.field.backgroundImageUrl.group.other"),
items: images
.filter((media) => media.creatorId !== session?.user.id)
.map((media) => `/api/user-medias/${media.id}`),
},
]}
{...form.getInputProps("backgroundImageUrl")}
/>
{session?.user.permissions.includes("media-upload") && (
<UploadMedia
onSuccess={({ url }) =>
startTransition(() => {
form.setFieldValue("backgroundImageUrl", url);
})
}
>
{({ onClick, loading }) => (
<ActionIcon onClick={onClick} loading={loading} mt={24} size={36} variant="default">
<IconUpload size={16} stroke={1.5} />
</ActionIcon>
)}
</UploadMedia>
)}
</Group>
</Grid.Col>
<Grid.Col span={12}>
<SelectWithDescriptionBadge
@@ -139,7 +159,7 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -38,7 +38,7 @@ export const BehaviorSettingsContent = ({ board }: Props) => {
/>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -46,7 +46,7 @@ export const CustomCssSettingsContent = ({ board }: Props) => {
</Alert>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -116,7 +116,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
</Grid.Col>
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -105,7 +105,7 @@ export const LayoutSettingsContent = ({ board }: Props) => {
</Stack>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -73,7 +73,7 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -66,7 +66,7 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -71,7 +71,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
</Radio.Group>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -57,7 +57,7 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -81,7 +81,7 @@ export const UserProfileForm = ({ user }: UserProfileFormProps) => {
{isProviderCredentials && (
<Group justify="end">
<Button type="submit" color="teal" disabled={!form.isDirty()} loading={isPending}>
<Button type="submit" disabled={!form.isDirty()} loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -85,7 +85,7 @@ export const ChangePasswordForm = ({ user }: ChangePasswordFormProps) => {
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.confirm")}
</Button>
</Group>
@@ -68,7 +68,7 @@ export const RenameGroupForm = ({ group, disabled }: RenameGroupFormProps) => {
{!disabled && (
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -40,9 +40,5 @@ export const AddGroupMember = ({ groupId, presentUserIds }: AddGroupMemberProps)
);
}, [openModal, presentUserIds, groupId, mutateAsync, tMembersAdd]);
return (
<MobileAffixButton color="teal" onClick={handleAddMember}>
{tMembersAdd("label")}
</MobileAffixButton>
);
return <MobileAffixButton onClick={handleAddMember}>{tMembersAdd("label")}</MobileAffixButton>;
};
@@ -1,7 +1,7 @@
"use client";
import { useCallback } from "react";
import type { PropsWithChildren } from "react";
import { useCallback } from "react";
import { Button, Card, Group, Switch, Text, Transition } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
@@ -108,7 +108,7 @@ export const SaveAffix = ({ groupId }: SaveAffixProps) => {
<Button disabled={isPending} onClick={form.reset}>
{t("common.action.discard")}
</Button>
<Button color="teal" loading={isPending} onClick={handleSubmit}>
<Button loading={isPending} onClick={handleSubmit}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -71,7 +71,7 @@ export const GroupHomeBoards = ({ homeBoardId, mobileHomeBoardId, groupId }: Gro
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -57,9 +57,5 @@ const AddGroup = () => {
openModal();
}, [openModal]);
return (
<MobileAffixButton onClick={handleAddGroup} color="teal">
{t("group.action.create.label")}
</MobileAffixButton>
);
return <MobileAffixButton onClick={handleAddGroup}>{t("group.action.create.label")}</MobileAffixButton>;
};
@@ -264,7 +264,7 @@ const SaveAffix = ({ visible, isPending, onDiscard, onSave }: SaveAffixProps) =>
<Button disabled={isPending} onClick={onDiscard}>
{t("common.action.discard")}
</Button>
<Button color="teal" loading={isPending} onClick={onSave}>
<Button loading={isPending} onClick={onSave}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -89,11 +89,7 @@ const RenderTopToolbarCustomActions = () => {
openModal();
}, [openModal]);
return (
<Button color="teal" onClick={handleNewInvite}>
{t("action.new.title")}
</Button>
);
return <Button onClick={handleNewInvite}>{t("action.new.title")}</Button>;
};
const RenderRowActions = ({ row }: { row: MRT_Row<RouterOutputs["invite"]["getAll"][number]> }) => {
@@ -82,7 +82,7 @@ export const GroupAccessForm = <TPermission extends string>({
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
{t("common.action.add")}
</Button>
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("permission.action.saveGroup")}
</Button>
</Group>
@@ -104,7 +104,7 @@ export const UsersAccessForm = <TPermission extends string>({
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
{t("common.action.add")}
</Button>
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("permission.action.saveUser")}
</Button>
</Group>
@@ -9,10 +9,11 @@ import { getSectionElements } from "./section-elements";
export interface CreateItemInput {
kind: WidgetKind;
options?: Record<string, unknown>;
}
export const createItemCallback =
({ kind }: CreateItemInput) =>
({ kind, options = {} }: CreateItemInput) =>
(previous: Board): Board => {
const firstSection = previous.sections
.filter((section): section is EmptySection => section.kind === "empty")
@@ -24,7 +25,7 @@ export const createItemCallback =
const widget = {
id: createId(),
kind,
options: {},
options,
layouts: createItemLayouts(previous, firstSection),
integrationIds: [],
advancedOptions: {
@@ -40,9 +40,7 @@ export const CategoryEditModal = createModal<InnerProps>(({ actions, innerProps
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{innerProps.submitLabel}
</Button>
<Button type="submit">{innerProps.submitLabel}</Button>
</Group>
</Stack>
</form>
@@ -48,9 +48,7 @@ export const DynamicSectionEditModal = createModal<ModalProps>(({ actions, inner
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{t("common.action.saveChanges")}
</Button>
<Button type="submit">{t("common.action.saveChanges")}</Button>
</Group>
</Group>
</Stack>
+4 -4
View File
@@ -36,17 +36,17 @@
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"superjson": "2.2.2",
"undici": "7.7.0"
"undici": "7.8.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.14.0",
"@types/node": "^22.14.1",
"dotenv-cli": "^8.0.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"prettier": "^3.5.3",
"tsx": "4.19.3",
"typescript": "^5.8.3"
+2 -2
View File
@@ -25,7 +25,7 @@
"@homarr/log": "workspace:^",
"@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"tsx": "4.19.3",
"ws": "^8.18.1"
},
@@ -34,7 +34,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.15.0",
"version": "1.17.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -39,21 +39,21 @@
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.3",
"@turbo/gen": "^2.5.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.4.0",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/ui": "^3.1.1",
"conventional-changelog-conventionalcommits": "^8.0.0",
"cross-env": "^7.0.3",
"jsdom": "^26.0.0",
"jsdom": "^26.1.0",
"prettier": "^3.5.3",
"semantic-release": "^24.2.3",
"testcontainers": "^10.24.1",
"testcontainers": "^10.24.2",
"turbo": "^2.5.0",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.1"
},
"packageManager": "pnpm@10.8.0",
"packageManager": "pnpm@10.8.1",
"engines": {
"node": ">=22.14.0"
},
@@ -70,7 +70,7 @@
"tree-sitter-json"
],
"overrides": {
"proxmox-api>undici": "7.7.0"
"proxmox-api>undici": "7.8.0"
},
"patchedDependencies": {
"pretty-print-error": "patches/pretty-print-error.patch"
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+6 -6
View File
@@ -41,23 +41,23 @@
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.1.2",
"@trpc/client": "^11.0.4",
"@trpc/react-query": "^11.0.4",
"@trpc/server": "^11.0.4",
"@trpc/client": "^11.1.0",
"@trpc/react-query": "^11.1.0",
"@trpc/server": "^11.1.0",
"lodash.clonedeep": "^4.5.0",
"next": "15.2.5",
"next": "15.3.1",
"pretty-print-error": "^1.1.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"superjson": "2.2.2",
"trpc-to-openapi": "^2.1.5",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
@@ -101,6 +101,18 @@ const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedInteg
);
if (onlyFormSecretsKindOptions.length >= 1) {
// If the first option is no secret it would always be selected even if we want to have a secret
if (
onlyFormSecretsKindOptions.length >= 2 &&
onlyFormSecretsKindOptions.some((secretKinds) => secretKinds.length === 0)
) {
return (
// Will never be undefined because of the check above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
onlyFormSecretsKindOptions.find((secretKinds) => secretKinds.length >= 1) ?? onlyFormSecretsKindOptions[0]!
);
}
// Will never be undefined because of the check above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return onlyFormSecretsKindOptions[0]!;
+3 -3
View File
@@ -35,12 +35,12 @@
"bcrypt": "^5.1.1",
"cookies": "^0.9.1",
"ldapts": "7.4.0",
"next": "15.2.5",
"next": "15.3.1",
"next-auth": "5.0.0-beta.25",
"pretty-print-error": "^1.1.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -48,7 +48,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "5.0.2",
"@types/cookies": "0.9.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -23,13 +23,13 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"undici": "7.7.0"
"undici": "7.8.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -28,13 +28,13 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.4.7"
"dotenv": "^16.5.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+4 -4
View File
@@ -30,17 +30,17 @@
"@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"dayjs": "^1.11.13",
"next": "15.2.5",
"next": "15.3.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"undici": "7.7.0",
"zod": "^3.24.2"
"undici": "7.8.0",
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+5 -5
View File
@@ -46,11 +46,11 @@
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^7.17.4",
"@paralleldrive/cuid2": "^2.2.2",
"@testcontainers/mysql": "^10.24.1",
"@testcontainers/mysql": "^10.24.2",
"better-sqlite3": "^11.9.1",
"dotenv": "^16.4.7",
"drizzle-kit": "^0.30.6",
"drizzle-orm": "^0.41.0",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.7.1",
"mysql2": "3.14.0"
},
@@ -60,7 +60,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"dotenv-cli": "^8.0.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"prettier": "^3.5.3",
"tsx": "4.19.3",
"typescript": "^5.8.3"
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
@@ -37,883 +37,6 @@ export type HomarrDocumentationPath =
| "/blog/tags/version"
| "/blog/translations"
| "/search"
| "/docs/1.10.0/tags"
| "/docs/1.10.0/tags/active-directory"
| "/docs/1.10.0/tags/ad-guard"
| "/docs/1.10.0/tags/ad-guard-home"
| "/docs/1.10.0/tags/administration"
| "/docs/1.10.0/tags/advanced"
| "/docs/1.10.0/tags/analytics"
| "/docs/1.10.0/tags/api"
| "/docs/1.10.0/tags/apps"
| "/docs/1.10.0/tags/banner"
| "/docs/1.10.0/tags/blocking"
| "/docs/1.10.0/tags/boards"
| "/docs/1.10.0/tags/bookmark"
| "/docs/1.10.0/tags/bookmarks"
| "/docs/1.10.0/tags/caddy"
| "/docs/1.10.0/tags/certificates"
| "/docs/1.10.0/tags/checklist"
| "/docs/1.10.0/tags/code"
| "/docs/1.10.0/tags/community"
| "/docs/1.10.0/tags/configuration"
| "/docs/1.10.0/tags/connections"
| "/docs/1.10.0/tags/customization"
| "/docs/1.10.0/tags/data-sources"
| "/docs/1.10.0/tags/database"
| "/docs/1.10.0/tags/developer"
| "/docs/1.10.0/tags/development"
| "/docs/1.10.0/tags/dns"
| "/docs/1.10.0/tags/docker"
| "/docs/1.10.0/tags/donation"
| "/docs/1.10.0/tags/edit-mode"
| "/docs/1.10.0/tags/env"
| "/docs/1.10.0/tags/environment-variables"
| "/docs/1.10.0/tags/feeds"
| "/docs/1.10.0/tags/getting-started"
| "/docs/1.10.0/tags/google"
| "/docs/1.10.0/tags/grafana"
| "/docs/1.10.0/tags/groups"
| "/docs/1.10.0/tags/hardware"
| "/docs/1.10.0/tags/health"
| "/docs/1.10.0/tags/help"
| "/docs/1.10.0/tags/icon-picker"
| "/docs/1.10.0/tags/icon-repositories"
| "/docs/1.10.0/tags/icons"
| "/docs/1.10.0/tags/iframe"
| "/docs/1.10.0/tags/images"
| "/docs/1.10.0/tags/installation"
| "/docs/1.10.0/tags/integrade"
| "/docs/1.10.0/tags/integration"
| "/docs/1.10.0/tags/integrations"
| "/docs/1.10.0/tags/interface"
| "/docs/1.10.0/tags/jellyserr"
| "/docs/1.10.0/tags/layout"
| "/docs/1.10.0/tags/ldap"
| "/docs/1.10.0/tags/links"
| "/docs/1.10.0/tags/lists"
| "/docs/1.10.0/tags/management"
| "/docs/1.10.0/tags/media"
| "/docs/1.10.0/tags/minecraft"
| "/docs/1.10.0/tags/monitoring"
| "/docs/1.10.0/tags/news"
| "/docs/1.10.0/tags/notebook"
| "/docs/1.10.0/tags/notes"
| "/docs/1.10.0/tags/oidc"
| "/docs/1.10.0/tags/open-collective"
| "/docs/1.10.0/tags/open-media-vault"
| "/docs/1.10.0/tags/overseerr"
| "/docs/1.10.0/tags/permissions"
| "/docs/1.10.0/tags/pgid"
| "/docs/1.10.0/tags/pi-hole"
| "/docs/1.10.0/tags/ping"
| "/docs/1.10.0/tags/programming"
| "/docs/1.10.0/tags/proxmox"
| "/docs/1.10.0/tags/proxy"
| "/docs/1.10.0/tags/puid"
| "/docs/1.10.0/tags/responsive"
| "/docs/1.10.0/tags/roles"
| "/docs/1.10.0/tags/rss"
| "/docs/1.10.0/tags/search"
| "/docs/1.10.0/tags/search-engines"
| "/docs/1.10.0/tags/security"
| "/docs/1.10.0/tags/self-signed"
| "/docs/1.10.0/tags/seo"
| "/docs/1.10.0/tags/server"
| "/docs/1.10.0/tags/settings"
| "/docs/1.10.0/tags/sinkhole"
| "/docs/1.10.0/tags/sso"
| "/docs/1.10.0/tags/system"
| "/docs/1.10.0/tags/table"
| "/docs/1.10.0/tags/technical-documentation"
| "/docs/1.10.0/tags/text"
| "/docs/1.10.0/tags/torrent"
| "/docs/1.10.0/tags/traefik"
| "/docs/1.10.0/tags/translations"
| "/docs/1.10.0/tags/unraid"
| "/docs/1.10.0/tags/uploads"
| "/docs/1.10.0/tags/usenet"
| "/docs/1.10.0/tags/users"
| "/docs/1.10.0/tags/variables"
| "/docs/1.10.0/tags/widgets"
| "/docs/1.10.0/advanced/command-line"
| "/docs/1.10.0/advanced/command-line/fix-usernames"
| "/docs/1.10.0/advanced/command-line/password-recovery"
| "/docs/1.10.0/advanced/development/getting-started"
| "/docs/1.10.0/advanced/environment-variables"
| "/docs/1.10.0/advanced/icons"
| "/docs/1.10.0/advanced/keyboard-shortcuts"
| "/docs/1.10.0/advanced/proxy"
| "/docs/1.10.0/advanced/running-as-different-user"
| "/docs/1.10.0/advanced/single-sign-on"
| "/docs/1.10.0/category/advanced"
| "/docs/1.10.0/category/community"
| "/docs/1.10.0/category/developer-guides"
| "/docs/1.10.0/category/getting-started"
| "/docs/1.10.0/category/installation"
| "/docs/1.10.0/category/installation-1"
| "/docs/1.10.0/category/integrations"
| "/docs/1.10.0/category/management"
| "/docs/1.10.0/category/widgets"
| "/docs/1.10.0/community/donate"
| "/docs/1.10.0/community/faq"
| "/docs/1.10.0/community/get-in-touch"
| "/docs/1.10.0/community/license"
| "/docs/1.10.0/community/translations"
| "/docs/1.10.0/getting-started"
| "/docs/1.10.0/getting-started/after-the-installation"
| "/docs/1.10.0/getting-started/glossary"
| "/docs/1.10.0/getting-started/installation/docker"
| "/docs/1.10.0/getting-started/installation/easy-panel"
| "/docs/1.10.0/getting-started/installation/helm"
| "/docs/1.10.0/getting-started/installation/home-assistant"
| "/docs/1.10.0/getting-started/installation/portainer"
| "/docs/1.10.0/getting-started/installation/qnap"
| "/docs/1.10.0/getting-started/installation/railway"
| "/docs/1.10.0/getting-started/installation/saltbox"
| "/docs/1.10.0/getting-started/installation/source"
| "/docs/1.10.0/getting-started/installation/synology"
| "/docs/1.10.0/getting-started/installation/unraid"
| "/docs/1.10.0/integrations/containers"
| "/docs/1.10.0/integrations/dns"
| "/docs/1.10.0/integrations/hardware"
| "/docs/1.10.0/integrations/media-requester"
| "/docs/1.10.0/integrations/media-server"
| "/docs/1.10.0/integrations/servarr"
| "/docs/1.10.0/integrations/torrent"
| "/docs/1.10.0/integrations/usenet"
| "/docs/1.10.0/management/api"
| "/docs/1.10.0/management/apps"
| "/docs/1.10.0/management/boards"
| "/docs/1.10.0/management/certificates"
| "/docs/1.10.0/management/integrations"
| "/docs/1.10.0/management/media"
| "/docs/1.10.0/management/search-engines"
| "/docs/1.10.0/management/settings"
| "/docs/1.10.0/management/users"
| "/docs/1.10.0/widgets/bookmarks"
| "/docs/1.10.0/widgets/calendar"
| "/docs/1.10.0/widgets/clock"
| "/docs/1.10.0/widgets/dns-hole"
| "/docs/1.10.0/widgets/downloads"
| "/docs/1.10.0/widgets/health-monitoring"
| "/docs/1.10.0/widgets/home-assistant"
| "/docs/1.10.0/widgets/iframe"
| "/docs/1.10.0/widgets/indexer-manager"
| "/docs/1.10.0/widgets/media-requests"
| "/docs/1.10.0/widgets/media-server"
| "/docs/1.10.0/widgets/minecraft-server-status"
| "/docs/1.10.0/widgets/notebook"
| "/docs/1.10.0/widgets/rss"
| "/docs/1.10.0/widgets/video"
| "/docs/1.10.0/widgets/weather"
| "/docs/1.11.0/tags"
| "/docs/1.11.0/tags/active-directory"
| "/docs/1.11.0/tags/ad-guard"
| "/docs/1.11.0/tags/ad-guard-home"
| "/docs/1.11.0/tags/administration"
| "/docs/1.11.0/tags/advanced"
| "/docs/1.11.0/tags/analytics"
| "/docs/1.11.0/tags/api"
| "/docs/1.11.0/tags/apps"
| "/docs/1.11.0/tags/banner"
| "/docs/1.11.0/tags/blocking"
| "/docs/1.11.0/tags/boards"
| "/docs/1.11.0/tags/bookmark"
| "/docs/1.11.0/tags/bookmarks"
| "/docs/1.11.0/tags/caddy"
| "/docs/1.11.0/tags/certificates"
| "/docs/1.11.0/tags/checklist"
| "/docs/1.11.0/tags/code"
| "/docs/1.11.0/tags/community"
| "/docs/1.11.0/tags/configuration"
| "/docs/1.11.0/tags/connections"
| "/docs/1.11.0/tags/customization"
| "/docs/1.11.0/tags/data-sources"
| "/docs/1.11.0/tags/database"
| "/docs/1.11.0/tags/developer"
| "/docs/1.11.0/tags/development"
| "/docs/1.11.0/tags/dns"
| "/docs/1.11.0/tags/docker"
| "/docs/1.11.0/tags/donation"
| "/docs/1.11.0/tags/edit-mode"
| "/docs/1.11.0/tags/env"
| "/docs/1.11.0/tags/environment-variables"
| "/docs/1.11.0/tags/feeds"
| "/docs/1.11.0/tags/getting-started"
| "/docs/1.11.0/tags/google"
| "/docs/1.11.0/tags/grafana"
| "/docs/1.11.0/tags/groups"
| "/docs/1.11.0/tags/hardware"
| "/docs/1.11.0/tags/health"
| "/docs/1.11.0/tags/help"
| "/docs/1.11.0/tags/icon-picker"
| "/docs/1.11.0/tags/icon-repositories"
| "/docs/1.11.0/tags/icons"
| "/docs/1.11.0/tags/iframe"
| "/docs/1.11.0/tags/images"
| "/docs/1.11.0/tags/installation"
| "/docs/1.11.0/tags/integrade"
| "/docs/1.11.0/tags/integration"
| "/docs/1.11.0/tags/integrations"
| "/docs/1.11.0/tags/interface"
| "/docs/1.11.0/tags/jellyserr"
| "/docs/1.11.0/tags/layout"
| "/docs/1.11.0/tags/ldap"
| "/docs/1.11.0/tags/links"
| "/docs/1.11.0/tags/lists"
| "/docs/1.11.0/tags/management"
| "/docs/1.11.0/tags/media"
| "/docs/1.11.0/tags/minecraft"
| "/docs/1.11.0/tags/monitoring"
| "/docs/1.11.0/tags/news"
| "/docs/1.11.0/tags/notebook"
| "/docs/1.11.0/tags/notes"
| "/docs/1.11.0/tags/oidc"
| "/docs/1.11.0/tags/open-collective"
| "/docs/1.11.0/tags/open-media-vault"
| "/docs/1.11.0/tags/overseerr"
| "/docs/1.11.0/tags/permissions"
| "/docs/1.11.0/tags/pgid"
| "/docs/1.11.0/tags/pi-hole"
| "/docs/1.11.0/tags/ping"
| "/docs/1.11.0/tags/programming"
| "/docs/1.11.0/tags/proxmox"
| "/docs/1.11.0/tags/proxy"
| "/docs/1.11.0/tags/puid"
| "/docs/1.11.0/tags/responsive"
| "/docs/1.11.0/tags/roles"
| "/docs/1.11.0/tags/rss"
| "/docs/1.11.0/tags/search"
| "/docs/1.11.0/tags/search-engines"
| "/docs/1.11.0/tags/security"
| "/docs/1.11.0/tags/self-signed"
| "/docs/1.11.0/tags/seo"
| "/docs/1.11.0/tags/server"
| "/docs/1.11.0/tags/settings"
| "/docs/1.11.0/tags/sinkhole"
| "/docs/1.11.0/tags/sso"
| "/docs/1.11.0/tags/system"
| "/docs/1.11.0/tags/table"
| "/docs/1.11.0/tags/technical-documentation"
| "/docs/1.11.0/tags/text"
| "/docs/1.11.0/tags/torrent"
| "/docs/1.11.0/tags/traefik"
| "/docs/1.11.0/tags/translations"
| "/docs/1.11.0/tags/unraid"
| "/docs/1.11.0/tags/uploads"
| "/docs/1.11.0/tags/usenet"
| "/docs/1.11.0/tags/users"
| "/docs/1.11.0/tags/variables"
| "/docs/1.11.0/tags/widgets"
| "/docs/1.11.0/advanced/command-line"
| "/docs/1.11.0/advanced/command-line/fix-usernames"
| "/docs/1.11.0/advanced/command-line/password-recovery"
| "/docs/1.11.0/advanced/development/getting-started"
| "/docs/1.11.0/advanced/development/kubernetes"
| "/docs/1.11.0/advanced/environment-variables"
| "/docs/1.11.0/advanced/icons"
| "/docs/1.11.0/advanced/keyboard-shortcuts"
| "/docs/1.11.0/advanced/proxy"
| "/docs/1.11.0/advanced/running-as-different-user"
| "/docs/1.11.0/advanced/single-sign-on"
| "/docs/1.11.0/category/advanced"
| "/docs/1.11.0/category/community"
| "/docs/1.11.0/category/developer-guides"
| "/docs/1.11.0/category/getting-started"
| "/docs/1.11.0/category/installation"
| "/docs/1.11.0/category/installation-1"
| "/docs/1.11.0/category/integrations"
| "/docs/1.11.0/category/management"
| "/docs/1.11.0/category/widgets"
| "/docs/1.11.0/community/donate"
| "/docs/1.11.0/community/faq"
| "/docs/1.11.0/community/get-in-touch"
| "/docs/1.11.0/community/license"
| "/docs/1.11.0/community/translations"
| "/docs/1.11.0/getting-started"
| "/docs/1.11.0/getting-started/after-the-installation"
| "/docs/1.11.0/getting-started/glossary"
| "/docs/1.11.0/getting-started/installation/docker"
| "/docs/1.11.0/getting-started/installation/easy-panel"
| "/docs/1.11.0/getting-started/installation/helm"
| "/docs/1.11.0/getting-started/installation/home-assistant"
| "/docs/1.11.0/getting-started/installation/portainer"
| "/docs/1.11.0/getting-started/installation/qnap"
| "/docs/1.11.0/getting-started/installation/railway"
| "/docs/1.11.0/getting-started/installation/saltbox"
| "/docs/1.11.0/getting-started/installation/source"
| "/docs/1.11.0/getting-started/installation/synology"
| "/docs/1.11.0/getting-started/installation/unraid"
| "/docs/1.11.0/integrations/containers"
| "/docs/1.11.0/integrations/dns"
| "/docs/1.11.0/integrations/hardware"
| "/docs/1.11.0/integrations/kubernetes"
| "/docs/1.11.0/integrations/media-requester"
| "/docs/1.11.0/integrations/media-server"
| "/docs/1.11.0/integrations/servarr"
| "/docs/1.11.0/integrations/torrent"
| "/docs/1.11.0/integrations/usenet"
| "/docs/1.11.0/management/api"
| "/docs/1.11.0/management/apps"
| "/docs/1.11.0/management/boards"
| "/docs/1.11.0/management/certificates"
| "/docs/1.11.0/management/integrations"
| "/docs/1.11.0/management/media"
| "/docs/1.11.0/management/search-engines"
| "/docs/1.11.0/management/settings"
| "/docs/1.11.0/management/users"
| "/docs/1.11.0/widgets/bookmarks"
| "/docs/1.11.0/widgets/calendar"
| "/docs/1.11.0/widgets/clock"
| "/docs/1.11.0/widgets/dns-hole"
| "/docs/1.11.0/widgets/downloads"
| "/docs/1.11.0/widgets/health-monitoring"
| "/docs/1.11.0/widgets/home-assistant"
| "/docs/1.11.0/widgets/iframe"
| "/docs/1.11.0/widgets/indexer-manager"
| "/docs/1.11.0/widgets/media-requests"
| "/docs/1.11.0/widgets/media-server"
| "/docs/1.11.0/widgets/minecraft-server-status"
| "/docs/1.11.0/widgets/notebook"
| "/docs/1.11.0/widgets/rss"
| "/docs/1.11.0/widgets/video"
| "/docs/1.11.0/widgets/weather"
| "/docs/1.12.0/tags"
| "/docs/1.12.0/tags/active-directory"
| "/docs/1.12.0/tags/ad-guard"
| "/docs/1.12.0/tags/ad-guard-home"
| "/docs/1.12.0/tags/administration"
| "/docs/1.12.0/tags/advanced"
| "/docs/1.12.0/tags/analytics"
| "/docs/1.12.0/tags/api"
| "/docs/1.12.0/tags/apps"
| "/docs/1.12.0/tags/banner"
| "/docs/1.12.0/tags/blocking"
| "/docs/1.12.0/tags/boards"
| "/docs/1.12.0/tags/bookmark"
| "/docs/1.12.0/tags/bookmarks"
| "/docs/1.12.0/tags/caddy"
| "/docs/1.12.0/tags/certificates"
| "/docs/1.12.0/tags/checklist"
| "/docs/1.12.0/tags/code"
| "/docs/1.12.0/tags/community"
| "/docs/1.12.0/tags/configuration"
| "/docs/1.12.0/tags/connections"
| "/docs/1.12.0/tags/customization"
| "/docs/1.12.0/tags/data-sources"
| "/docs/1.12.0/tags/database"
| "/docs/1.12.0/tags/developer"
| "/docs/1.12.0/tags/development"
| "/docs/1.12.0/tags/dns"
| "/docs/1.12.0/tags/docker"
| "/docs/1.12.0/tags/donation"
| "/docs/1.12.0/tags/edit-mode"
| "/docs/1.12.0/tags/env"
| "/docs/1.12.0/tags/environment-variables"
| "/docs/1.12.0/tags/feeds"
| "/docs/1.12.0/tags/finance"
| "/docs/1.12.0/tags/getting-started"
| "/docs/1.12.0/tags/google"
| "/docs/1.12.0/tags/grafana"
| "/docs/1.12.0/tags/groups"
| "/docs/1.12.0/tags/hardware"
| "/docs/1.12.0/tags/health"
| "/docs/1.12.0/tags/help"
| "/docs/1.12.0/tags/icon-picker"
| "/docs/1.12.0/tags/icon-repositories"
| "/docs/1.12.0/tags/icons"
| "/docs/1.12.0/tags/iframe"
| "/docs/1.12.0/tags/images"
| "/docs/1.12.0/tags/installation"
| "/docs/1.12.0/tags/integrade"
| "/docs/1.12.0/tags/integration"
| "/docs/1.12.0/tags/integrations"
| "/docs/1.12.0/tags/interface"
| "/docs/1.12.0/tags/jellyserr"
| "/docs/1.12.0/tags/layout"
| "/docs/1.12.0/tags/ldap"
| "/docs/1.12.0/tags/links"
| "/docs/1.12.0/tags/lists"
| "/docs/1.12.0/tags/management"
| "/docs/1.12.0/tags/market"
| "/docs/1.12.0/tags/media"
| "/docs/1.12.0/tags/minecraft"
| "/docs/1.12.0/tags/monitoring"
| "/docs/1.12.0/tags/news"
| "/docs/1.12.0/tags/notebook"
| "/docs/1.12.0/tags/notes"
| "/docs/1.12.0/tags/oidc"
| "/docs/1.12.0/tags/open-collective"
| "/docs/1.12.0/tags/open-media-vault"
| "/docs/1.12.0/tags/overseerr"
| "/docs/1.12.0/tags/permissions"
| "/docs/1.12.0/tags/pgid"
| "/docs/1.12.0/tags/pi-hole"
| "/docs/1.12.0/tags/ping"
| "/docs/1.12.0/tags/programming"
| "/docs/1.12.0/tags/proxmox"
| "/docs/1.12.0/tags/proxy"
| "/docs/1.12.0/tags/puid"
| "/docs/1.12.0/tags/responsive"
| "/docs/1.12.0/tags/roles"
| "/docs/1.12.0/tags/rss"
| "/docs/1.12.0/tags/search"
| "/docs/1.12.0/tags/search-engines"
| "/docs/1.12.0/tags/security"
| "/docs/1.12.0/tags/self-signed"
| "/docs/1.12.0/tags/seo"
| "/docs/1.12.0/tags/server"
| "/docs/1.12.0/tags/settings"
| "/docs/1.12.0/tags/sinkhole"
| "/docs/1.12.0/tags/sso"
| "/docs/1.12.0/tags/stocks"
| "/docs/1.12.0/tags/system"
| "/docs/1.12.0/tags/table"
| "/docs/1.12.0/tags/technical-documentation"
| "/docs/1.12.0/tags/text"
| "/docs/1.12.0/tags/torrent"
| "/docs/1.12.0/tags/traefik"
| "/docs/1.12.0/tags/translations"
| "/docs/1.12.0/tags/unraid"
| "/docs/1.12.0/tags/uploads"
| "/docs/1.12.0/tags/usenet"
| "/docs/1.12.0/tags/users"
| "/docs/1.12.0/tags/variables"
| "/docs/1.12.0/tags/widgets"
| "/docs/1.12.0/advanced/command-line"
| "/docs/1.12.0/advanced/command-line/fix-usernames"
| "/docs/1.12.0/advanced/command-line/password-recovery"
| "/docs/1.12.0/advanced/development/getting-started"
| "/docs/1.12.0/advanced/development/kubernetes"
| "/docs/1.12.0/advanced/environment-variables"
| "/docs/1.12.0/advanced/icons"
| "/docs/1.12.0/advanced/keyboard-shortcuts"
| "/docs/1.12.0/advanced/proxy"
| "/docs/1.12.0/advanced/running-as-different-user"
| "/docs/1.12.0/advanced/single-sign-on"
| "/docs/1.12.0/category/advanced"
| "/docs/1.12.0/category/community"
| "/docs/1.12.0/category/developer-guides"
| "/docs/1.12.0/category/getting-started"
| "/docs/1.12.0/category/installation"
| "/docs/1.12.0/category/installation-1"
| "/docs/1.12.0/category/integrations"
| "/docs/1.12.0/category/management"
| "/docs/1.12.0/category/widgets"
| "/docs/1.12.0/community/donate"
| "/docs/1.12.0/community/faq"
| "/docs/1.12.0/community/get-in-touch"
| "/docs/1.12.0/community/license"
| "/docs/1.12.0/community/translations"
| "/docs/1.12.0/getting-started"
| "/docs/1.12.0/getting-started/after-the-installation"
| "/docs/1.12.0/getting-started/glossary"
| "/docs/1.12.0/getting-started/installation/docker"
| "/docs/1.12.0/getting-started/installation/easy-panel"
| "/docs/1.12.0/getting-started/installation/helm"
| "/docs/1.12.0/getting-started/installation/home-assistant"
| "/docs/1.12.0/getting-started/installation/portainer"
| "/docs/1.12.0/getting-started/installation/proxmox"
| "/docs/1.12.0/getting-started/installation/qnap"
| "/docs/1.12.0/getting-started/installation/railway"
| "/docs/1.12.0/getting-started/installation/saltbox"
| "/docs/1.12.0/getting-started/installation/source"
| "/docs/1.12.0/getting-started/installation/synology"
| "/docs/1.12.0/getting-started/installation/unraid"
| "/docs/1.12.0/integrations/containers"
| "/docs/1.12.0/integrations/dns"
| "/docs/1.12.0/integrations/hardware"
| "/docs/1.12.0/integrations/kubernetes"
| "/docs/1.12.0/integrations/media-requester"
| "/docs/1.12.0/integrations/media-server"
| "/docs/1.12.0/integrations/servarr"
| "/docs/1.12.0/integrations/torrent"
| "/docs/1.12.0/integrations/usenet"
| "/docs/1.12.0/management/api"
| "/docs/1.12.0/management/apps"
| "/docs/1.12.0/management/boards"
| "/docs/1.12.0/management/certificates"
| "/docs/1.12.0/management/integrations"
| "/docs/1.12.0/management/media"
| "/docs/1.12.0/management/search-engines"
| "/docs/1.12.0/management/settings"
| "/docs/1.12.0/management/users"
| "/docs/1.12.0/widgets/bookmarks"
| "/docs/1.12.0/widgets/calendar"
| "/docs/1.12.0/widgets/clock"
| "/docs/1.12.0/widgets/dns-hole"
| "/docs/1.12.0/widgets/downloads"
| "/docs/1.12.0/widgets/health-monitoring"
| "/docs/1.12.0/widgets/home-assistant"
| "/docs/1.12.0/widgets/iframe"
| "/docs/1.12.0/widgets/indexer-manager"
| "/docs/1.12.0/widgets/media-requests"
| "/docs/1.12.0/widgets/media-server"
| "/docs/1.12.0/widgets/minecraft-server-status"
| "/docs/1.12.0/widgets/notebook"
| "/docs/1.12.0/widgets/rss"
| "/docs/1.12.0/widgets/stocks"
| "/docs/1.12.0/widgets/video"
| "/docs/1.12.0/widgets/weather"
| "/docs/1.13.1/tags"
| "/docs/1.13.1/tags/active-directory"
| "/docs/1.13.1/tags/ad-guard"
| "/docs/1.13.1/tags/ad-guard-home"
| "/docs/1.13.1/tags/administration"
| "/docs/1.13.1/tags/advanced"
| "/docs/1.13.1/tags/analytics"
| "/docs/1.13.1/tags/api"
| "/docs/1.13.1/tags/apps"
| "/docs/1.13.1/tags/banner"
| "/docs/1.13.1/tags/blocking"
| "/docs/1.13.1/tags/boards"
| "/docs/1.13.1/tags/bookmark"
| "/docs/1.13.1/tags/bookmarks"
| "/docs/1.13.1/tags/caddy"
| "/docs/1.13.1/tags/certificates"
| "/docs/1.13.1/tags/checklist"
| "/docs/1.13.1/tags/code"
| "/docs/1.13.1/tags/community"
| "/docs/1.13.1/tags/configuration"
| "/docs/1.13.1/tags/connections"
| "/docs/1.13.1/tags/customization"
| "/docs/1.13.1/tags/data-sources"
| "/docs/1.13.1/tags/database"
| "/docs/1.13.1/tags/developer"
| "/docs/1.13.1/tags/development"
| "/docs/1.13.1/tags/dns"
| "/docs/1.13.1/tags/docker"
| "/docs/1.13.1/tags/donation"
| "/docs/1.13.1/tags/edit-mode"
| "/docs/1.13.1/tags/env"
| "/docs/1.13.1/tags/environment-variables"
| "/docs/1.13.1/tags/feeds"
| "/docs/1.13.1/tags/finance"
| "/docs/1.13.1/tags/getting-started"
| "/docs/1.13.1/tags/google"
| "/docs/1.13.1/tags/grafana"
| "/docs/1.13.1/tags/groups"
| "/docs/1.13.1/tags/hardware"
| "/docs/1.13.1/tags/health"
| "/docs/1.13.1/tags/help"
| "/docs/1.13.1/tags/icon-picker"
| "/docs/1.13.1/tags/icon-repositories"
| "/docs/1.13.1/tags/icons"
| "/docs/1.13.1/tags/iframe"
| "/docs/1.13.1/tags/images"
| "/docs/1.13.1/tags/installation"
| "/docs/1.13.1/tags/integrade"
| "/docs/1.13.1/tags/integration"
| "/docs/1.13.1/tags/integrations"
| "/docs/1.13.1/tags/interface"
| "/docs/1.13.1/tags/jellyserr"
| "/docs/1.13.1/tags/layout"
| "/docs/1.13.1/tags/ldap"
| "/docs/1.13.1/tags/links"
| "/docs/1.13.1/tags/lists"
| "/docs/1.13.1/tags/management"
| "/docs/1.13.1/tags/market"
| "/docs/1.13.1/tags/media"
| "/docs/1.13.1/tags/minecraft"
| "/docs/1.13.1/tags/monitoring"
| "/docs/1.13.1/tags/news"
| "/docs/1.13.1/tags/notebook"
| "/docs/1.13.1/tags/notes"
| "/docs/1.13.1/tags/oidc"
| "/docs/1.13.1/tags/open-collective"
| "/docs/1.13.1/tags/open-media-vault"
| "/docs/1.13.1/tags/overseerr"
| "/docs/1.13.1/tags/permissions"
| "/docs/1.13.1/tags/pgid"
| "/docs/1.13.1/tags/pi-hole"
| "/docs/1.13.1/tags/ping"
| "/docs/1.13.1/tags/programming"
| "/docs/1.13.1/tags/proxmox"
| "/docs/1.13.1/tags/proxy"
| "/docs/1.13.1/tags/puid"
| "/docs/1.13.1/tags/responsive"
| "/docs/1.13.1/tags/roles"
| "/docs/1.13.1/tags/rss"
| "/docs/1.13.1/tags/search"
| "/docs/1.13.1/tags/search-engines"
| "/docs/1.13.1/tags/security"
| "/docs/1.13.1/tags/self-signed"
| "/docs/1.13.1/tags/seo"
| "/docs/1.13.1/tags/server"
| "/docs/1.13.1/tags/settings"
| "/docs/1.13.1/tags/sinkhole"
| "/docs/1.13.1/tags/sso"
| "/docs/1.13.1/tags/stocks"
| "/docs/1.13.1/tags/system"
| "/docs/1.13.1/tags/table"
| "/docs/1.13.1/tags/technical-documentation"
| "/docs/1.13.1/tags/text"
| "/docs/1.13.1/tags/torrent"
| "/docs/1.13.1/tags/traefik"
| "/docs/1.13.1/tags/translations"
| "/docs/1.13.1/tags/unraid"
| "/docs/1.13.1/tags/uploads"
| "/docs/1.13.1/tags/usenet"
| "/docs/1.13.1/tags/users"
| "/docs/1.13.1/tags/variables"
| "/docs/1.13.1/tags/widgets"
| "/docs/1.13.1/advanced/command-line"
| "/docs/1.13.1/advanced/command-line/fix-usernames"
| "/docs/1.13.1/advanced/command-line/password-recovery"
| "/docs/1.13.1/advanced/development/getting-started"
| "/docs/1.13.1/advanced/development/kubernetes"
| "/docs/1.13.1/advanced/environment-variables"
| "/docs/1.13.1/advanced/icons"
| "/docs/1.13.1/advanced/keyboard-shortcuts"
| "/docs/1.13.1/advanced/proxy"
| "/docs/1.13.1/advanced/running-as-different-user"
| "/docs/1.13.1/advanced/single-sign-on"
| "/docs/1.13.1/advanced/styling"
| "/docs/1.13.1/category/advanced"
| "/docs/1.13.1/category/community"
| "/docs/1.13.1/category/developer-guides"
| "/docs/1.13.1/category/getting-started"
| "/docs/1.13.1/category/installation"
| "/docs/1.13.1/category/installation-1"
| "/docs/1.13.1/category/integrations"
| "/docs/1.13.1/category/management"
| "/docs/1.13.1/category/widgets"
| "/docs/1.13.1/community/donate"
| "/docs/1.13.1/community/faq"
| "/docs/1.13.1/community/get-in-touch"
| "/docs/1.13.1/community/license"
| "/docs/1.13.1/community/translations"
| "/docs/1.13.1/getting-started"
| "/docs/1.13.1/getting-started/after-the-installation"
| "/docs/1.13.1/getting-started/glossary"
| "/docs/1.13.1/getting-started/installation/docker"
| "/docs/1.13.1/getting-started/installation/easy-panel"
| "/docs/1.13.1/getting-started/installation/helm"
| "/docs/1.13.1/getting-started/installation/home-assistant"
| "/docs/1.13.1/getting-started/installation/portainer"
| "/docs/1.13.1/getting-started/installation/proxmox"
| "/docs/1.13.1/getting-started/installation/qnap"
| "/docs/1.13.1/getting-started/installation/railway"
| "/docs/1.13.1/getting-started/installation/saltbox"
| "/docs/1.13.1/getting-started/installation/source"
| "/docs/1.13.1/getting-started/installation/synology"
| "/docs/1.13.1/getting-started/installation/unraid"
| "/docs/1.13.1/integrations/cloud"
| "/docs/1.13.1/integrations/containers"
| "/docs/1.13.1/integrations/dns"
| "/docs/1.13.1/integrations/hardware"
| "/docs/1.13.1/integrations/kubernetes"
| "/docs/1.13.1/integrations/media-requester"
| "/docs/1.13.1/integrations/media-server"
| "/docs/1.13.1/integrations/servarr"
| "/docs/1.13.1/integrations/torrent"
| "/docs/1.13.1/integrations/usenet"
| "/docs/1.13.1/management/api"
| "/docs/1.13.1/management/apps"
| "/docs/1.13.1/management/boards"
| "/docs/1.13.1/management/certificates"
| "/docs/1.13.1/management/integrations"
| "/docs/1.13.1/management/media"
| "/docs/1.13.1/management/search-engines"
| "/docs/1.13.1/management/settings"
| "/docs/1.13.1/management/users"
| "/docs/1.13.1/widgets/bookmarks"
| "/docs/1.13.1/widgets/calendar"
| "/docs/1.13.1/widgets/clock"
| "/docs/1.13.1/widgets/dns-hole"
| "/docs/1.13.1/widgets/downloads"
| "/docs/1.13.1/widgets/health-monitoring"
| "/docs/1.13.1/widgets/home-assistant"
| "/docs/1.13.1/widgets/iframe"
| "/docs/1.13.1/widgets/indexer-manager"
| "/docs/1.13.1/widgets/media-requests"
| "/docs/1.13.1/widgets/media-server"
| "/docs/1.13.1/widgets/minecraft-server-status"
| "/docs/1.13.1/widgets/notebook"
| "/docs/1.13.1/widgets/rss"
| "/docs/1.13.1/widgets/stocks"
| "/docs/1.13.1/widgets/video"
| "/docs/1.13.1/widgets/weather"
| "/docs/next/tags"
| "/docs/next/tags/active-directory"
| "/docs/next/tags/ad-guard"
| "/docs/next/tags/ad-guard-home"
| "/docs/next/tags/administration"
| "/docs/next/tags/advanced"
| "/docs/next/tags/analytics"
| "/docs/next/tags/api"
| "/docs/next/tags/apps"
| "/docs/next/tags/banner"
| "/docs/next/tags/blocking"
| "/docs/next/tags/boards"
| "/docs/next/tags/bookmark"
| "/docs/next/tags/bookmarks"
| "/docs/next/tags/caddy"
| "/docs/next/tags/certificates"
| "/docs/next/tags/checklist"
| "/docs/next/tags/code"
| "/docs/next/tags/community"
| "/docs/next/tags/configuration"
| "/docs/next/tags/connections"
| "/docs/next/tags/customization"
| "/docs/next/tags/data-sources"
| "/docs/next/tags/database"
| "/docs/next/tags/developer"
| "/docs/next/tags/development"
| "/docs/next/tags/dns"
| "/docs/next/tags/docker"
| "/docs/next/tags/donation"
| "/docs/next/tags/edit-mode"
| "/docs/next/tags/env"
| "/docs/next/tags/environment-variables"
| "/docs/next/tags/feeds"
| "/docs/next/tags/finance"
| "/docs/next/tags/getting-started"
| "/docs/next/tags/google"
| "/docs/next/tags/grafana"
| "/docs/next/tags/groups"
| "/docs/next/tags/hardware"
| "/docs/next/tags/health"
| "/docs/next/tags/help"
| "/docs/next/tags/icon-picker"
| "/docs/next/tags/icon-repositories"
| "/docs/next/tags/icons"
| "/docs/next/tags/iframe"
| "/docs/next/tags/images"
| "/docs/next/tags/installation"
| "/docs/next/tags/integrade"
| "/docs/next/tags/integration"
| "/docs/next/tags/integrations"
| "/docs/next/tags/interface"
| "/docs/next/tags/jellyserr"
| "/docs/next/tags/layout"
| "/docs/next/tags/ldap"
| "/docs/next/tags/links"
| "/docs/next/tags/lists"
| "/docs/next/tags/management"
| "/docs/next/tags/market"
| "/docs/next/tags/media"
| "/docs/next/tags/minecraft"
| "/docs/next/tags/monitoring"
| "/docs/next/tags/news"
| "/docs/next/tags/notebook"
| "/docs/next/tags/notes"
| "/docs/next/tags/oidc"
| "/docs/next/tags/open-collective"
| "/docs/next/tags/open-media-vault"
| "/docs/next/tags/overseerr"
| "/docs/next/tags/permissions"
| "/docs/next/tags/pgid"
| "/docs/next/tags/pi-hole"
| "/docs/next/tags/ping"
| "/docs/next/tags/programming"
| "/docs/next/tags/proxmox"
| "/docs/next/tags/proxy"
| "/docs/next/tags/puid"
| "/docs/next/tags/responsive"
| "/docs/next/tags/roles"
| "/docs/next/tags/rss"
| "/docs/next/tags/search"
| "/docs/next/tags/search-engines"
| "/docs/next/tags/security"
| "/docs/next/tags/self-signed"
| "/docs/next/tags/seo"
| "/docs/next/tags/server"
| "/docs/next/tags/settings"
| "/docs/next/tags/sinkhole"
| "/docs/next/tags/sso"
| "/docs/next/tags/stocks"
| "/docs/next/tags/system"
| "/docs/next/tags/table"
| "/docs/next/tags/technical-documentation"
| "/docs/next/tags/text"
| "/docs/next/tags/torrent"
| "/docs/next/tags/traefik"
| "/docs/next/tags/translations"
| "/docs/next/tags/unraid"
| "/docs/next/tags/uploads"
| "/docs/next/tags/usenet"
| "/docs/next/tags/users"
| "/docs/next/tags/variables"
| "/docs/next/tags/widgets"
| "/docs/next/advanced/command-line"
| "/docs/next/advanced/command-line/fix-usernames"
| "/docs/next/advanced/command-line/password-recovery"
| "/docs/next/advanced/development/getting-started"
| "/docs/next/advanced/development/kubernetes"
| "/docs/next/advanced/environment-variables"
| "/docs/next/advanced/icons"
| "/docs/next/advanced/keyboard-shortcuts"
| "/docs/next/advanced/proxy"
| "/docs/next/advanced/running-as-different-user"
| "/docs/next/advanced/single-sign-on"
| "/docs/next/advanced/styling"
| "/docs/next/category/advanced"
| "/docs/next/category/community"
| "/docs/next/category/developer-guides"
| "/docs/next/category/getting-started"
| "/docs/next/category/installation"
| "/docs/next/category/installation-1"
| "/docs/next/category/integrations"
| "/docs/next/category/management"
| "/docs/next/category/widgets"
| "/docs/next/community/donate"
| "/docs/next/community/faq"
| "/docs/next/community/get-in-touch"
| "/docs/next/community/license"
| "/docs/next/community/translations"
| "/docs/next/getting-started"
| "/docs/next/getting-started/after-the-installation"
| "/docs/next/getting-started/glossary"
| "/docs/next/getting-started/installation/docker"
| "/docs/next/getting-started/installation/easy-panel"
| "/docs/next/getting-started/installation/helm"
| "/docs/next/getting-started/installation/home-assistant"
| "/docs/next/getting-started/installation/portainer"
| "/docs/next/getting-started/installation/proxmox"
| "/docs/next/getting-started/installation/qnap"
| "/docs/next/getting-started/installation/railway"
| "/docs/next/getting-started/installation/saltbox"
| "/docs/next/getting-started/installation/source"
| "/docs/next/getting-started/installation/synology"
| "/docs/next/getting-started/installation/unraid"
| "/docs/next/integrations/cloud"
| "/docs/next/integrations/containers"
| "/docs/next/integrations/dns"
| "/docs/next/integrations/hardware"
| "/docs/next/integrations/kubernetes"
| "/docs/next/integrations/media-requester"
| "/docs/next/integrations/media-server"
| "/docs/next/integrations/servarr"
| "/docs/next/integrations/torrent"
| "/docs/next/integrations/usenet"
| "/docs/next/management/api"
| "/docs/next/management/apps"
| "/docs/next/management/boards"
| "/docs/next/management/certificates"
| "/docs/next/management/integrations"
| "/docs/next/management/media"
| "/docs/next/management/search-engines"
| "/docs/next/management/settings"
| "/docs/next/management/users"
| "/docs/next/widgets/bookmarks"
| "/docs/next/widgets/calendar"
| "/docs/next/widgets/clock"
| "/docs/next/widgets/dns-hole"
| "/docs/next/widgets/downloads"
| "/docs/next/widgets/health-monitoring"
| "/docs/next/widgets/home-assistant"
| "/docs/next/widgets/iframe"
| "/docs/next/widgets/indexer-manager"
| "/docs/next/widgets/media-requests"
| "/docs/next/widgets/media-server"
| "/docs/next/widgets/minecraft-server-status"
| "/docs/next/widgets/notebook"
| "/docs/next/widgets/rss"
| "/docs/next/widgets/stocks"
| "/docs/next/widgets/video"
| "/docs/next/widgets/weather"
| "/docs/tags"
| "/docs/tags/active-directory"
| "/docs/tags/ad-guard"
+12 -5
View File
@@ -49,6 +49,12 @@ export const integrationDefs = {
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg",
category: ["downloadClient", "torrent"],
},
aria2: {
name: "Aria2",
secretKinds: [[], ["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg",
category: ["downloadClient", "torrent", "miscellaneous"],
},
sonarr: {
name: "Sonarr",
secretKinds: [["apiKey"]],
@@ -136,26 +142,26 @@ export const integrationDefs = {
dashDot: {
name: "Dash.",
secretKinds: [[]],
category: ["healthMonitoring"],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/dashdot.png",
category: ["healthMonitoring"],
},
tdarr: {
name: "Tdarr",
secretKinds: [[]],
category: ["mediaTranscoding"],
secretKinds: [[], ["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png",
category: ["mediaTranscoding"],
},
proxmox: {
name: "Proxmox",
secretKinds: [["username", "tokenId", "apiKey", "realm"]],
category: ["healthMonitoring"],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/proxmox.svg",
category: ["healthMonitoring"],
},
nextcloud: {
name: "Nextcloud",
secretKinds: [["username", "password"]],
category: ["calendar"],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nextcloud.svg",
category: ["calendar"],
},
unifiController: {
name: "Unifi Controller",
@@ -211,6 +217,7 @@ export type IntegrationCategory =
| "downloadClient"
| "usenet"
| "torrent"
| "miscellaneous"
| "smartHomeServer"
| "indexerManager"
| "healthMonitoring"
+2 -2
View File
@@ -31,8 +31,8 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/dockerode": "^3.3.37",
"eslint": "^9.24.0",
"@types/dockerode": "^3.3.38",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -24,13 +24,13 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@t3-oss/env-nextjs": "^0.12.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -27,13 +27,13 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^7.17.4",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -31,13 +31,13 @@
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^7.17.4",
"react": "19.1.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+4 -3
View File
@@ -36,19 +36,20 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@jellyfin/sdk": "^0.11.0",
"maria2": "^0.4.0",
"node-ical": "^0.20.1",
"proxmox-api": "1.1.1",
"tsdav": "^2.1.3",
"undici": "7.7.0",
"undici": "7.8.0",
"xml2js": "^0.6.2",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/xml2js": "^0.4.14",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
@@ -5,6 +5,7 @@ import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions
import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration";
import { DashDotIntegration } from "../dashdot/dashdot-integration";
import { Aria2Integration } from "../download-client/aria2/aria2-integration";
import { DelugeIntegration } from "../download-client/deluge/deluge-integration";
import { NzbGetIntegration } from "../download-client/nzbget/nzbget-integration";
import { QBitTorrentIntegration } from "../download-client/qbittorrent/qbittorrent-integration";
@@ -78,6 +79,7 @@ export const integrationCreators = {
qBittorrent: QBitTorrentIntegration,
deluge: DelugeIntegration,
transmission: TransmissionIntegration,
aria2: Aria2Integration,
jellyseerr: JellyseerrIntegration,
overseerr: OverseerrIntegration,
prowlarr: ProwlarrIntegration,
@@ -30,13 +30,15 @@ export class DashDotIntegration extends Integration {
cpuUtilization: cpuLoad.sumLoad,
memUsed: `${memoryLoad.loadInBytes}`,
memAvailable: `${info.maxAvailableMemoryBytes - memoryLoad.loadInBytes}`,
fileSystem: info.storage.map((storage, index) => ({
deviceName: `Storage ${index + 1}: (${storage.disks.map((disk) => disk.device).join(", ")})`,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
used: humanFileSize(storageLoad[index]!),
available: `${storage.size}`,
percentage: storageLoad[index] ? (storageLoad[index] / storage.size) * 100 : 0,
})),
fileSystem: info.storage
.filter((_, index) => storageLoad[index] !== -1) // filter out undermoutned drives, they display as -1 in the load API
.map((storage, index) => ({
deviceName: `Storage ${index + 1}: (${storage.disks.map((disk) => disk.device).join(", ")})`,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
used: humanFileSize(storageLoad[index]!),
available: storageLoad[index] ? `${storage.size - storageLoad[index]}` : `${storage.size}`,
percentage: storageLoad[index] ? (storageLoad[index] / storage.size) * 100 : 0,
})),
cpuModelName: info.cpuModel === "" ? `Unknown Model (${info.cpuBrand})` : `${info.cpuModel} (${info.cpuBrand})`,
cpuTemp: cpuLoad.averageTemperature,
availablePkgUpdates: 0,
@@ -0,0 +1,180 @@
import path from "path";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import type { DownloadClientJobsAndStatus } from "../../interfaces/downloads/download-client-data";
import { DownloadClientIntegration } from "../../interfaces/downloads/download-client-integration";
import type { DownloadClientItem } from "../../interfaces/downloads/download-client-items";
import type { Aria2Download, Aria2GetClient } from "./aria2-types";
export class Aria2Integration extends DownloadClientIntegration {
public async getClientJobsAndStatusAsync(): Promise<DownloadClientJobsAndStatus> {
const client = this.getClient();
const keys: (keyof Aria2Download)[] = [
"bittorrent",
"uploadLength",
"uploadSpeed",
"downloadSpeed",
"totalLength",
"completedLength",
"files",
"status",
"gid",
];
const [activeDownloads, waitingDownloads, stoppedDownloads, globalStats] = await Promise.all([
client.tellActive(),
client.tellWaiting(0, 1000, keys),
client.tellStopped(0, 1000, keys),
client.getGlobalStat(),
]);
const downloads = [...activeDownloads, ...waitingDownloads, ...stoppedDownloads];
const allPaused = downloads.every((download) => download.status === "paused");
return {
status: {
types: ["torrent", "miscellaneous"],
paused: allPaused,
rates: {
up: Number(globalStats.uploadSpeed),
down: Number(globalStats.downloadSpeed),
},
},
items: downloads.map((download, index) => {
const totalSize = Number(download.totalLength);
const completedSize = Number(download.completedLength);
const progress = totalSize > 0 ? completedSize / totalSize : 0;
const itemName = download.bittorrent?.info?.name ?? path.basename(download.files[0]?.path ?? "Unknown");
return {
index,
id: download.gid,
name: itemName,
type: download.bittorrent ? "torrent" : "miscellaneous",
size: totalSize,
sent: Number(download.uploadLength),
downSpeed: Number(download.downloadSpeed),
upSpeed: Number(download.uploadSpeed),
time: this.calculateEta(completedSize, totalSize, Number(download.downloadSpeed)),
state: this.getState(download.status, Boolean(download.bittorrent)),
category: [],
progress,
};
}),
} as DownloadClientJobsAndStatus;
}
public async pauseQueueAsync(): Promise<void> {
const client = this.getClient();
await client.pauseAll();
}
public async pauseItemAsync(item: DownloadClientItem): Promise<void> {
const client = this.getClient();
await client.pause(item.id);
}
public async resumeQueueAsync(): Promise<void> {
const client = this.getClient();
await client.unpauseAll();
}
public async resumeItemAsync(item: DownloadClientItem): Promise<void> {
const client = this.getClient();
await client.unpause(item.id);
}
public async deleteItemAsync(item: DownloadClientItem, fromDisk: boolean): Promise<void> {
const client = this.getClient();
// Note: Remove download file is not support by aria2, replace with forceremove
if (item.state in ["downloading", "leeching", "paused"]) {
await (fromDisk ? client.remove(item.id) : client.forceRemove(item.id));
} else {
await client.removeDownloadResult(item.id);
}
}
public async testConnectionAsync(): Promise<void> {
const client = this.getClient();
await client.getVersion();
}
private getClient() {
const url = this.url("/jsonrpc");
return new Proxy(
{},
{
get: (target, method: keyof Aria2GetClient) => {
return async (...args: Parameters<Aria2GetClient[typeof method]>) => {
let params = [...args];
if (this.hasSecretValue("apiKey")) {
params = [`token:${this.getSecretValue("apiKey")}`, ...params];
}
const body = JSON.stringify({
jsonrpc: "2.0",
id: btoa(["Homarr", Date.now().toString(), Math.random()].join(".")), // unique id per request
method: `aria2.${method}`,
params,
});
return await fetchWithTrustedCertificatesAsync(url, { method: "POST", body })
.then(async (response) => {
const responseBody = (await response.json()) as { result: ReturnType<Aria2GetClient[typeof method]> };
if (!response.ok) {
throw new Error(response.statusText);
}
return responseBody.result;
})
.catch((error) => {
if (error instanceof Error) {
throw new Error(error.message);
} else {
throw new Error("Error communicating with Aria2");
}
});
};
},
},
) as Aria2GetClient;
}
private getState(aria2Status: Aria2Download["status"], isTorrent: boolean): DownloadClientItem["state"] {
return isTorrent ? this.getTorrentState(aria2Status) : this.getNonTorrentState(aria2Status);
}
private getNonTorrentState(aria2Status: Aria2Download["status"]): DownloadClientItem["state"] {
switch (aria2Status) {
case "active":
return "downloading";
case "waiting":
return "queued";
case "paused":
return "paused";
case "complete":
return "completed";
case "error":
return "failed";
case "removed":
default:
return "unknown";
}
}
private getTorrentState(aria2Status: Aria2Download["status"]): DownloadClientItem["state"] {
switch (aria2Status) {
case "active":
return "leeching";
case "waiting":
return "queued";
case "paused":
return "paused";
case "complete":
return "completed";
case "error":
return "failed";
case "removed":
default:
return "unknown";
}
}
private calculateEta(completed: number, total: number, speed: number): number {
if (speed === 0 || completed >= total) return 0;
return Math.floor((total - completed) / speed) * 1000; // Convert to milliseconds
}
}
@@ -0,0 +1,80 @@
export interface Aria2GetClient {
getVersion: Aria2VoidFunc<Aria2GetVersion>;
getGlobalStat: Aria2VoidFunc<Aria2GetGlobalStat>;
tellActive: Aria2VoidFunc<Aria2Download[]>;
tellWaiting: Aria2VoidFunc<Aria2Download[], Aria2TellStatusListParams>;
tellStopped: Aria2VoidFunc<Aria2Download[], Aria2TellStatusListParams>;
tellStatus: Aria2GidFunc<Aria2Download, Aria2TellStatusListParams>;
pause: Aria2GidFunc<AriaGID>;
pauseAll: Aria2VoidFunc<"OK">;
unpause: Aria2GidFunc<AriaGID>;
unpauseAll: Aria2VoidFunc<"OK">;
remove: Aria2GidFunc<AriaGID>;
forceRemove: Aria2GidFunc<AriaGID>;
removeDownloadResult: Aria2GidFunc<"OK">;
}
type AriaGID = string;
type Aria2GidFunc<R = void, T extends unknown[] = []> = (gid: string, ...args: T) => Promise<R>;
type Aria2VoidFunc<R = void, T extends unknown[] = []> = (...args: T) => Promise<R>;
type Aria2TellStatusListParams = [offset: number, num: number, keys?: [keyof Aria2Download] | (keyof Aria2Download)[]];
export interface Aria2GetVersion {
enabledFeatures: string[];
version: string;
}
export interface Aria2GetGlobalStat {
downloadSpeed: string;
uploadSpeed: string;
numActive: string;
numWaiting: string;
numStopped: string;
numStoppedTotal: string;
}
export interface Aria2Download {
gid: AriaGID;
status: "active" | "waiting" | "paused" | "error" | "complete" | "removed";
totalLength: string;
completedLength: string;
uploadLength: string;
bitfield: string;
downloadSpeed: string;
uploadSpeed: string;
infoHash?: string;
numSeeders?: string;
seeder?: "true" | "false";
pieceLength: string;
numPieces: string;
connections: string;
errorCode?: string;
errorMessage?: string;
followedBy?: AriaGID[];
following?: AriaGID;
belongsTo?: AriaGID;
dir: string;
files: {
index: number;
path: string;
length: string;
completedLength: string;
selected: "true" | "false";
uris: {
status: "waiting" | "used";
uri: string;
}[];
}[];
bittorrent?: {
announceList: string[];
comment?: string | { "utf-8": string };
creationDate?: number;
mode?: "single" | "multi";
info?: {
name: string | { "utf-8": string };
};
verifiedLength?: number;
verifyIntegrityPending?: boolean;
};
}
@@ -32,7 +32,7 @@ export class DelugeIntegration extends DownloadClientIntegration {
down: Math.floor(download_rate),
up: Math.floor(upload_rate),
},
type,
types: [type],
};
const items = torrents.map((torrent): DownloadClientItem => {
const state = DelugeIntegration.getTorrentState(torrent.state);
@@ -21,7 +21,7 @@ export class NzbGetIntegration extends DownloadClientIntegration {
const status: DownloadClientStatus = {
paused: nzbGetStatus.DownloadPaused,
rates: { down: nzbGetStatus.DownloadRate },
type,
types: [type],
};
const items = queue
.map((file): DownloadClientItem => {
@@ -24,7 +24,7 @@ export class QBitTorrentIntegration extends DownloadClientIntegration {
);
const paused =
torrents.find(({ state }) => QBitTorrentIntegration.getTorrentState(state) !== "paused") === undefined;
const status: DownloadClientStatus = { paused, rates, type };
const status: DownloadClientStatus = { paused, rates, types: [type] };
const items = torrents.map((torrent): DownloadClientItem => {
const state = QBitTorrentIntegration.getTorrentState(torrent.state);
return {
@@ -24,7 +24,7 @@ export class SabnzbdIntegration extends DownloadClientIntegration {
const status: DownloadClientStatus = {
paused: queue.paused,
rates: { down: Math.floor(Number(queue.kbpersec) * 1024) }, //Actually rounded kiBps ()
type,
types: [type],
};
const items = queue.slots
.map((slot): DownloadClientItem => {
@@ -24,7 +24,7 @@ export class TransmissionIntegration extends DownloadClientIntegration {
);
const paused =
torrents.find(({ status }) => TransmissionIntegration.getTorrentState(status) !== "paused") === undefined;
const status: DownloadClientStatus = { paused, rates, type };
const status: DownloadClientStatus = { paused, rates, types: [type] };
const items = torrents.map((torrent): DownloadClientItem => {
const state = TransmissionIntegration.getTorrentState(torrent.status);
return {
+1
View File
@@ -5,6 +5,7 @@ export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
export { QBitTorrentIntegration } from "./download-client/qbittorrent/qbittorrent-integration";
export { SabnzbdIntegration } from "./download-client/sabnzbd/sabnzbd-integration";
export { TransmissionIntegration } from "./download-client/transmission/transmission-integration";
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration";
export { DownloadClientIntegration } from "./interfaces/downloads/download-client-integration";
export { JellyfinIntegration } from "./jellyfin/jellyfin-integration";
@@ -20,8 +20,8 @@ export const downloadClientItemSchema = z.object({
index: z.number(),
/** Filename */
name: z.string(),
/** Torrent/Usenet identifier */
type: z.enum(["torrent", "usenet"]),
/** Download Client identifier */
type: z.enum(["torrent", "usenet", "miscellaneous"]),
/** Item size in Bytes */
size: z.number(),
/** Total uploaded in Bytes, only required for Torrent items */
@@ -8,7 +8,7 @@ export interface DownloadClientStatus {
down: number;
up?: number;
};
type: "usenet" | "torrent";
types: ("usenet" | "torrent" | "miscellaneous")[];
}
export interface ExtendedClientStatus {
integration: Pick<Integration, "id" | "name" | "kind"> & { updatedAt: Date };
@@ -4,6 +4,7 @@ export interface TdarrPieSegment {
}
export interface TdarrStatistics {
libraryName: string;
totalFileCount: number;
totalTranscodeCount: number;
totalHealthCheckCount: number;
@@ -11,19 +12,12 @@ export interface TdarrStatistics {
failedHealthCheckCount: number;
stagedTranscodeCount: number;
stagedHealthCheckCount: number;
pies: {
libraryName: string;
libraryId: string;
totalFiles: number;
totalTranscodes: number;
savedSpace: number;
totalHealthChecks: number;
transcodeStatus: TdarrPieSegment[];
healthCheckStatus: TdarrPieSegment[];
videoCodecs: TdarrPieSegment[];
videoContainers: TdarrPieSegment[];
videoResolutions: TdarrPieSegment[];
audioCodecs: TdarrPieSegment[];
audioContainers: TdarrPieSegment[];
}[];
totalSavedSpace: number;
transcodeStatus: TdarrPieSegment[];
healthCheckStatus: TdarrPieSegment[];
videoCodecs: TdarrPieSegment[];
videoContainers: TdarrPieSegment[];
videoResolutions: TdarrPieSegment[];
audioCodecs: TdarrPieSegment[];
audioContainers: TdarrPieSegment[];
}
@@ -1,5 +1,3 @@
import { z } from "zod";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { Integration } from "../base/integration";
@@ -10,25 +8,34 @@ import { getNodesResponseSchema, getStatisticsSchema, getStatusTableSchema } fro
export class TdarrIntegration extends Integration {
public async testConnectionAsync(): Promise<void> {
const url = this.url("/api/v2/status");
const response = await fetchWithTrustedCertificatesAsync(url);
if (response.status !== 200) {
throw new Error(`Unexpected status code: ${response.status}`);
}
await z.object({ status: z.string() }).parseAsync(await response.json());
await super.handleTestConnectionResponseAsync({
queryFunctionAsync: async () => {
return await fetchWithTrustedCertificatesAsync(this.url("/api/v2/is-server-alive"), {
method: "POST",
headers: {
accept: "application/json",
"X-Api-Key": super.hasSecretValue("apiKey") ? super.getSecretValue("apiKey") : "",
},
});
},
});
}
public async getStatisticsAsync(): Promise<TdarrStatistics> {
const url = this.url("/api/v2/cruddb");
const url = this.url("/api/v2/stats/get-pies");
const headerParams = {
accept: "application/json",
"Content-Type": "application/json",
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
};
const response = await fetchWithTrustedCertificatesAsync(url, {
method: "POST",
headers: { "content-type": "application/json" },
headers: headerParams,
body: JSON.stringify({
data: {
collection: "StatisticsJSONDB",
mode: "getById",
docID: "statistics",
libraryId: "", // empty string to get all libraries
},
}),
});
@@ -36,36 +43,41 @@ export class TdarrIntegration extends Integration {
const statisticsData = await getStatisticsSchema.parseAsync(await response.json());
return {
totalFileCount: statisticsData.totalFileCount,
totalTranscodeCount: statisticsData.totalTranscodeCount,
totalHealthCheckCount: statisticsData.totalHealthCheckCount,
failedTranscodeCount: statisticsData.table3Count,
failedHealthCheckCount: statisticsData.table6Count,
stagedTranscodeCount: statisticsData.table1Count,
stagedHealthCheckCount: statisticsData.table4Count,
pies: statisticsData.pies.map((pie) => ({
libraryName: pie[0],
libraryId: pie[1],
totalFiles: pie[2],
totalTranscodes: pie[3],
savedSpace: pie[4] * 1_000_000_000, // file_size is in GB, convert to bytes,
totalHealthChecks: pie[5],
transcodeStatus: pie[6],
healthCheckStatus: pie[7],
videoCodecs: pie[8],
videoContainers: pie[9],
videoResolutions: pie[10],
audioCodecs: pie[11],
audioContainers: pie[12],
})),
libraryName: "All",
totalFileCount: statisticsData.pieStats.totalFiles,
totalTranscodeCount: statisticsData.pieStats.totalTranscodeCount,
totalHealthCheckCount: statisticsData.pieStats.totalHealthCheckCount,
// The Tdarr API only returns a category if there is at least one item in it
failedTranscodeCount:
statisticsData.pieStats.status.transcode.find((transcode) => transcode.name === "Transcode error")?.value ?? 0,
failedHealthCheckCount:
statisticsData.pieStats.status.healthcheck.find((healthcheck) => healthcheck.name === "Error")?.value ?? 0,
stagedTranscodeCount:
statisticsData.pieStats.status.transcode.find((transcode) => transcode.name === "Transcode success")?.value ??
0,
stagedHealthCheckCount:
statisticsData.pieStats.status.healthcheck.find((healthcheck) => healthcheck.name === "Queued")?.value ?? 0,
totalSavedSpace: statisticsData.pieStats.sizeDiff * 1_000_000_000, // sizeDiff is in GB, convert to bytes
transcodeStatus: statisticsData.pieStats.status.transcode,
healthCheckStatus: statisticsData.pieStats.status.healthcheck,
videoCodecs: statisticsData.pieStats.video.codecs,
videoContainers: statisticsData.pieStats.video.containers,
videoResolutions: statisticsData.pieStats.video.resolutions,
audioCodecs: statisticsData.pieStats.audio.codecs,
audioContainers: statisticsData.pieStats.audio.containers,
};
}
public async getWorkersAsync(): Promise<TdarrWorker[]> {
const url = this.url("/api/v2/get-nodes");
const headerParams = {
"Content-Type": "application/json",
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
};
const response = await fetchWithTrustedCertificatesAsync(url, {
method: "GET",
headers: { "content-type": "application/json" },
headers: headerParams,
});
const nodesData = await getNodesResponseSchema.parseAsync(await response.json());
@@ -103,9 +115,13 @@ export class TdarrIntegration extends Integration {
private async getTranscodingQueueAsync(firstItemIndex: number, pageSize: number) {
const url = this.url("/api/v2/client/status-tables");
const headerParams = {
"Content-Type": "application/json",
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
};
const response = await fetchWithTrustedCertificatesAsync(url, {
method: "POST",
headers: { "content-type": "application/json" },
headers: headerParams,
body: JSON.stringify({
data: {
start: firstItemIndex,
@@ -124,7 +140,7 @@ export class TdarrIntegration extends Integration {
healthCheck: item.HealthCheck,
transcode: item.TranscodeDecisionMaker,
filePath: item.file,
fileSize: item.file_size * 1_000_000, // file_size is in MB, convert to bytes
fileSize: Math.floor(item.file_size * 1_000_000), // file_size is in MB, convert to bytes, floor because it returns as float
container: item.container,
codec: item.video_codec_name,
resolution: item.video_resolution,
@@ -138,9 +154,13 @@ export class TdarrIntegration extends Integration {
private async getHealthCheckDataAsync(firstItemIndex: number, pageSize: number, totalQueueCount: number) {
const url = this.url("/api/v2/client/status-tables");
const headerParams = {
"Content-Type": "application/json",
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
};
const response = await fetchWithTrustedCertificatesAsync(url, {
method: "POST",
headers: { "content-type": "application/json" },
headers: headerParams,
body: JSON.stringify({
data: {
start: Math.max(firstItemIndex - totalQueueCount, 0),
@@ -162,7 +182,7 @@ export class TdarrIntegration extends Integration {
healthCheck: item.HealthCheck,
transcode: item.TranscodeDecisionMaker,
filePath: item.file,
fileSize: item.file_size * 1_000_000, // file_size is in MB, convert to bytes
fileSize: Math.floor(item.file_size * 1_000_000), // file_size is in MB, convert to bytes, floor because it returns as float
container: item.container,
codec: item.video_codec_name,
resolution: item.video_resolution,
@@ -1,72 +1,60 @@
import { z } from "zod";
export const getStatisticsSchema = z.object({
totalFileCount: z.number(),
totalTranscodeCount: z.number(),
totalHealthCheckCount: z.number(),
table3Count: z.number(),
table6Count: z.number(),
table1Count: z.number(),
table4Count: z.number(),
pies: z.array(
z.tuple([
z.string(), // Library Name
z.string(), // Library ID
z.number(), // File count
z.number(), // Number of transcodes
z.number(), // Space saved (in GB)
z.number(), // Number of health checks
z.array(
pieStats: z.object({
totalFiles: z.number(),
totalTranscodeCount: z.number(),
sizeDiff: z.number(),
totalHealthCheckCount: z.number(),
status: z.object({
transcode: z.array(
z.object({
// Transcode Status (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
healthcheck: z.array(
z.object({
// Health Status (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
}),
video: z.object({
codecs: z.array(
z.object({
// Video files - Codecs (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
containers: z.array(
z.object({
// Video files - Containers (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
resolutions: z.array(
z.object({
// Video files - Resolutions (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
}),
audio: z.object({
codecs: z.array(
z.object({
// Audio files - Codecs (Pie segments)
name: z.string(),
value: z.number(),
}),
),
z.array(
containers: z.array(
z.object({
// Audio files - Containers (Pie segments)
name: z.string(),
value: z.number(),
}),
),
]),
),
}),
}),
});
export const getNodesResponseSchema = z.record(
+153
View File
@@ -0,0 +1,153 @@
import type { StartedTestContainer } from "testcontainers";
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
import { beforeAll, describe, expect, test } from "vitest";
import { Aria2Integration } from "../src";
const API_KEY = "ARIA2_API_KEY";
const IMAGE_NAME = "hurlenko/aria2-ariang:latest";
describe("Aria2 integration", () => {
beforeAll(async () => {
const containerRuntimeClient = await getContainerRuntimeClient();
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
}, 100_000);
test("Test connection should work", async () => {
// Arrange
const startedContainer = await createAria2Container().start();
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
// Act
const actAsync = async () => await aria2Integration.testConnectionAsync();
// Assert
await expect(actAsync()).resolves.not.toThrow();
// Cleanup
await startedContainer.stop();
}, 30_000);
test("pauseQueueAsync should work", async () => {
// Arrange
const startedContainer = await createAria2Container().start();
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
// Acts
const actAsync = async () => await aria2Integration.pauseQueueAsync();
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync();
// Assert
await expect(actAsync()).resolves.not.toThrow();
await expect(getAsync()).resolves.toMatchObject({ status: { paused: true } });
// Cleanup
await startedContainer.stop();
}, 30_000); // Timeout of 30 seconds
test("Items should be empty", async () => {
// Arrange
const startedContainer = await createAria2Container().start();
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
// Act
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync();
// Assert
await expect(getAsync()).resolves.not.toThrow();
await expect(getAsync()).resolves.toMatchObject({
items: [],
});
// Cleanup
await startedContainer.stop();
}, 30_000); // Timeout of 30 seconds
test("1 Items should exist after adding one", async () => {
// Arrange
const startedContainer = await createAria2Container().start();
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
await aria2AddItemAsync(startedContainer, API_KEY, aria2Integration);
// Act
const getAsync = async () => await aria2Integration.getClientJobsAndStatusAsync();
// Assert
await expect(getAsync()).resolves.not.toThrow();
expect((await getAsync()).items).toHaveLength(1);
// Cleanup
await startedContainer.stop();
}, 30_000); // Timeout of 30 seconds
test("Delete item should result in empty items", async () => {
// Arrange
const startedContainer = await createAria2Container().start();
const aria2Integration = createAria2Intergration(startedContainer, API_KEY);
const item = await aria2AddItemAsync(startedContainer, API_KEY, aria2Integration);
// Act
const actAsync = async () => await aria2Integration.deleteItemAsync(item, true);
// Assert
await expect(actAsync()).resolves.not.toThrow();
// NzbGet is slow and we wait for a second before querying the items. Test was flaky without this.
await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await aria2Integration.getClientJobsAndStatusAsync();
expect(result.items).toHaveLength(0);
// Cleanup
await startedContainer.stop();
}, 30_000); // Timeout of 30 seconds
});
const createAria2Container = () => {
return new GenericContainer(IMAGE_NAME)
.withExposedPorts(8080)
.withEnvironment({
PUID: "1000",
PGID: "1000",
ARIA2RPCPORT: "443",
RPC_SECRET: API_KEY,
})
.withWaitStrategy(Wait.forLogMessage("listening on TCP port"));
};
const createAria2Intergration = (container: StartedTestContainer, apikey: string) => {
return new Aria2Integration({
id: "1",
decryptedSecrets: [
{
kind: "apiKey",
value: apikey,
},
],
name: "Aria2",
url: `http://${container.getHost()}:${container.getMappedPort(8080)}`,
});
};
const aria2AddItemAsync = async (container: StartedTestContainer, apiKey: string, integration: Aria2Integration) => {
await fetch(`http://${container.getHost()}:${container.getMappedPort(8080)}/jsonrpc`, {
method: "POST",
body: JSON.stringify({
jsonrpc: "2.0",
id: btoa(["Homarr", Date.now().toString(), Math.random()].join(".")), // unique id per request
method: "aria2.addUri",
params: [`token:${apiKey}`, ["https://google.com"]],
}),
});
await delay(3000);
const {
items: [item],
} = await integration.getClientJobsAndStatusAsync();
if (!item) {
throw new Error("No item found");
}
return item;
};
const delay = (microseconds: number) => new Promise((resolve) => setTimeout(resolve, microseconds));
+3 -3
View File
@@ -24,16 +24,16 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/env": "workspace:^0.1.0",
"ioredis": "5.6.0",
"ioredis": "5.6.1",
"superjson": "2.2.2",
"winston": "3.17.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+3 -3
View File
@@ -36,16 +36,16 @@
"@mantine/core": "^7.17.4",
"@tabler/icons-react": "^3.31.0",
"dayjs": "^1.11.13",
"next": "15.2.5",
"next": "15.3.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
@@ -0,0 +1,121 @@
import { useMemo, useState } from "react";
import Image from "next/image";
import { Button, Card, Center, Grid, Input, Stack, Text } from "@mantine/core";
import { IconPlus, IconSearch } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { createModal, useModalAction } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { QuickAddAppModal } from "./quick-add-app/quick-add-app-modal";
interface AppSelectModalProps {
onSelect?: (appId: string) => void;
}
export const AppSelectModal = createModal<AppSelectModalProps>(({ actions, innerProps }) => {
const [search, setSearch] = useState("");
const t = useI18n();
const { data: apps = [], isPending } = clientApi.app.selectable.useQuery();
const { openModal: openQuickAddAppModal } = useModalAction(QuickAddAppModal);
const filteredApps = useMemo(
() =>
apps
.filter((app) => app.name.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => a.name.localeCompare(b.name)),
[apps, search],
);
const handleSelect = (appId: string) => {
if (innerProps.onSelect) {
innerProps.onSelect(appId);
}
actions.closeModal();
};
const handleAddNewApp = () => {
openQuickAddAppModal({
onClose(createdAppId) {
if (innerProps.onSelect) {
innerProps.onSelect(createdAppId);
}
actions.closeModal();
},
});
};
return (
<Stack>
<Input
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
leftSection={<IconSearch />}
placeholder={`${t("app.action.select.search")}...`}
data-autofocus
onKeyDown={(event) => {
if (event.key === "Enter" && filteredApps.length === 1 && filteredApps[0]) {
handleSelect(filteredApps[0].id);
}
}}
/>
<Grid>
<Grid.Col span={{ xs: 12, sm: 4, md: 3 }}>
<Card h="100%">
<Stack justify="space-between" h="100%">
<Stack gap="xs">
<Center>
<IconPlus size={24} />
</Center>
<Text lh={1.2} style={{ whiteSpace: "normal" }} ta="center">
{t("app.action.create.title")}
</Text>
<Text lh={1.2} style={{ whiteSpace: "normal" }} size="xs" ta="center" c="dimmed">
{t("app.action.create.description")}
</Text>
</Stack>
<Button onClick={handleAddNewApp} variant="light" size="xs" mt="auto" radius="md" fullWidth>
{t("app.action.create.action")}
</Button>
</Stack>
</Card>
</Grid.Col>
{filteredApps.map((app) => (
<Grid.Col key={app.id} span={{ xs: 12, sm: 4, md: 3 }}>
<Card h="100%">
<Stack justify="space-between" h="100%">
<Stack gap="xs">
<Center>
<Image src={app.iconUrl || ""} alt={app.name} width={24} height={24} />
</Center>
<Text lh={1.2} style={{ whiteSpace: "normal" }} ta="center">
{app.name}
</Text>
<Text lh={1.2} style={{ whiteSpace: "normal" }} size="xs" ta="center" c="dimmed">
{app.description ?? ""}
</Text>
</Stack>
<Button onClick={() => handleSelect(app.id)} variant="light" size="xs" mt="auto" radius="md" fullWidth>
{t("app.action.select.action", { app: app.name })}
</Button>
</Stack>
</Card>
</Grid.Col>
))}
{filteredApps.length === 0 && !isPending && (
<Grid.Col span={12}>
<Center p="xl">
<Text c="dimmed">{t("app.action.select.noResults")}</Text>
</Center>
</Grid.Col>
)}
</Grid>
</Stack>
);
}).withOptions({
defaultTitle: (t) => t("app.action.select.title"),
size: "xl",
});
@@ -1 +1,2 @@
export { AppSelectModal } from "./app-select-modal";
export { QuickAddAppModal } from "./quick-add-app/quick-add-app-modal";
@@ -1,6 +1,7 @@
import type { z } from "zod";
import { clientApi } from "@homarr/api/client";
import type { MaybePromise } from "@homarr/common/types";
import { AppForm } from "@homarr/forms-collection";
import { createModal } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
@@ -8,7 +9,7 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
import type { appManageSchema } from "@homarr/validation/app";
interface QuickAddAppModalProps {
onClose: (createdAppId: string) => Promise<void>;
onClose: (createdAppId: string) => MaybePromise<void>;
}
export const QuickAddAppModal = createModal<QuickAddAppModalProps>(({ actions, innerProps }) => {
@@ -82,7 +82,7 @@ export const AddBoardModal = createModal(({ actions }) => {
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.create")}
</Button>
</Group>
@@ -44,7 +44,7 @@ export const AddGroupModal = createModal<void>(({ actions }) => {
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button loading={isPending} type="submit" color="teal">
<Button loading={isPending} type="submit">
{t("common.action.create")}
</Button>
</Group>
@@ -1,4 +1,3 @@
import React from "react";
import { Button, Group, Stack, Text } from "@mantine/core";
import { DateTimePicker } from "@mantine/dates";
import dayjs from "dayjs";
@@ -63,7 +62,7 @@ export const InviteCreateModal = createModal<void>(({ actions }) => {
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.create")}
</Button>
</Group>
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+3 -3
View File
@@ -40,11 +40,11 @@
"@mantine/core": "^7.17.4",
"@mantine/hooks": "^7.17.4",
"adm-zip": "0.5.16",
"next": "15.2.5",
"next": "15.3.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"superjson": "2.2.2",
"zod": "^3.24.2",
"zod": "^3.24.3",
"zod-form-data": "^2.0.7"
},
"devDependencies": {
@@ -52,7 +52,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/adm-zip": "0.5.7",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
@@ -76,6 +76,7 @@ const optionMapping: OptionMapping = {
defaultSort: () => "type",
descendingDefaultSort: () => false,
showCompletedUsenet: () => true,
showCompletedHttp: () => true,
},
weather: {
forecastDayCount: (oldOptions) => oldOptions.forecastDays,
+2 -2
View File
@@ -23,13 +23,13 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"zod": "^3.24.2"
"zod": "^3.24.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -26,14 +26,14 @@
"@homarr/db": "workspace:^",
"@homarr/definitions": "workspace:^",
"@homarr/log": "workspace:^",
"ioredis": "5.6.0",
"ioredis": "5.6.1",
"superjson": "2.2.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -30,7 +30,7 @@
"@homarr/log": "workspace:^0.1.0",
"@homarr/redis": "workspace:^0.1.0",
"dayjs": "^1.11.13",
"octokit": "^4.1.2",
"octokit": "^4.1.3",
"pretty-print-error": "^1.1.2",
"superjson": "2.2.2"
},
@@ -38,7 +38,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -26,7 +26,7 @@
"@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^7.17.4",
"next": "15.2.5",
"next": "15.3.1",
"react": "19.1.0",
"react-dom": "19.1.0"
},
@@ -34,7 +34,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+3 -3
View File
@@ -37,8 +37,8 @@
"@mantine/hooks": "^7.17.4",
"@mantine/spotlight": "^7.17.4",
"@tabler/icons-react": "^3.31.0",
"jotai": "^2.12.2",
"next": "15.2.5",
"jotai": "^2.12.3",
"next": "15.3.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"use-deep-compare-effect": "^1.8.1"
@@ -47,7 +47,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+2 -2
View File
@@ -32,7 +32,7 @@
"dayjs": "^1.11.13",
"deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.2.5",
"next": "15.3.1",
"next-intl": "4.0.2",
"react": "19.1.0",
"react-dom": "19.1.0"
@@ -41,7 +41,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.25.0",
"typescript": "^5.8.3"
}
}
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "Seleccioneu l'aplicació",
"notFound": "No s'ha trobat cap aplicació"
}
"notFound": "No s'ha trobat cap aplicació",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
+17 -4
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "选择应用",
"notFound": "未找到应用"
}
"notFound": "未找到应用",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": "显示标记为已完成的 Torrent 条目"
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": "隐藏在此阈值下已完成的 Torrent (kiB/s)"
},
@@ -2209,7 +2222,7 @@
"label": "背景图片 URL",
"placeholder": "",
"group": {
"your": "",
"your": "你的图像",
"other": ""
}
},
@@ -2849,7 +2862,7 @@
"label": "媒体转码"
},
"networkController": {
"label": ""
"label": "网络控制器"
}
}
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "Vybrat aplikaci",
"notFound": ""
}
"notFound": "",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
+38 -25
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "Vælg app",
"notFound": "Ingen app fundet"
}
"notFound": "Ingen app fundet",
"search": "Søg efter en app",
"noResults": "Ingen resultater",
"action": "Vælg {app}",
"title": "Vælg en app, der skal tilføjes til denne tavle"
},
"create": {
"title": "Opret ny app",
"description": "Opret en ny app ",
"action": "Åbn app-oprettelse"
},
"add": "Tilføj en app"
}
},
"integration": {
@@ -759,8 +769,8 @@
"message": "Stien er sandsynligvis ikke korrekt"
},
"tooManyRequests": {
"title": "",
"message": ""
"title": "For mange anmodninger i et givet tidsrum",
"message": "Der var for mange anmodninger. Du bliver sandsynligvis rate-limited eller afvist af målsystemet"
}
}
},
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": "Vis torrent poster markeret som fuldførte"
},
"showCompletedHttp": {
"label": "Vis Diverse poster markeret som færdige"
},
"activeTorrentThreshold": {
"label": "Skjul afsluttet torrent under denne tærskel (i kiB/s)"
},
@@ -2035,56 +2048,56 @@
"option": {},
"card": {
"vpn": {
"countConnected": ""
"countConnected": "{count} tilsluttet"
}
},
"error": {
"integrationsDisconnected": "",
"unknownContentOption": ""
"integrationsDisconnected": "Ingen tilgængelige data, alle integrationer afbrudt",
"unknownContentOption": "Ukendt indhold indstilling for netværkscontroller resumé widget: "
},
"name": "",
"description": ""
"name": "Netværkskontroller Oversigt",
"description": "Viser resuméet af en netværkskontroller (som UniFi Controller)"
},
"networkControllerStatus": {
"card": {
"variants": {
"wired": {
"name": ""
"name": "Kablet"
},
"wifi": {
"name": ""
"name": "WiFi"
}
},
"users": {
"label": ""
"label": "Brugere"
},
"guests": {
"label": ""
"label": "Gæster"
}
},
"option": {
"content": {
"option": {
"wifi": {
"label": ""
"label": "WiFi"
},
"wired": {
"label": ""
"label": "Kablet"
}
},
"label": ""
"label": "Widget indhold"
}
},
"error": {
"integrationsDisconnected": "",
"unknownContentOption": ""
"integrationsDisconnected": "Ingen tilgængelige data, alle integrationer afbrudt",
"unknownContentOption": "Ukendt indhold indstilling for netværksstatuswidget: "
},
"name": "",
"description": ""
"name": "Status for netværk",
"description": "Vis tilsluttede enheder på et netværk"
},
"networkController": {
"error": {
"internalServerError": ""
"internalServerError": "Kunne ikke hente Netværkskontroloversigt"
}
}
},
@@ -2207,10 +2220,10 @@
},
"backgroundImageUrl": {
"label": "Baggrundsbillede URL",
"placeholder": "",
"placeholder": "Begynd at skrive for at søge efter lokale billeder",
"group": {
"your": "",
"other": ""
"your": "Dine billeder",
"other": "Andre billeder"
}
},
"backgroundImageAttachment": {
@@ -2849,7 +2862,7 @@
"label": "Omkodning af medier"
},
"networkController": {
"label": ""
"label": "Netværkskontroller"
}
}
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "App wähle",
"notFound": "Keni app gfunge"
}
"notFound": "Keni app gfunge",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": "Torrent Einträge anzeigen die abgeschlossen wurden"
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": "Abgeschlossene Torrents unter diesem Schwellenwert ausblenden (in kiB/s)"
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "App auswählen",
"notFound": "Keine App gefunden"
}
"notFound": "Keine App gefunden",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": "Torrent Einträge anzeigen die abgeschlossen wurden"
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": "Abgeschlossene Torrents unter diesem Schwellenwert ausblenden (in kiB/s)"
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "",
"notFound": ""
}
"notFound": "",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "Select an application",
"notFound": "No application found"
}
"notFound": "No application found",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "Select app",
"notFound": "No app found"
}
"notFound": "No app found",
"search": "Search for an app",
"noResults": "No results",
"action": "Select {app}",
"title": "Select an app to add to this board"
},
"create": {
"title": "Create new app",
"description": "Create a new app ",
"action": "Open app creation"
},
"add": "Add an app"
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": "Show torrent entries marked as completed"
},
"showCompletedHttp": {
"label": "Show Miscellaneous entries marked as completed"
},
"activeTorrentThreshold": {
"label": "Hide completed torrent under this threshold (in kiB/s)"
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "",
"notFound": ""
}
"notFound": "",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
+15 -2
View File
@@ -611,8 +611,18 @@
"action": {
"select": {
"label": "",
"notFound": ""
}
"notFound": "",
"search": "",
"noResults": "",
"action": "",
"title": ""
},
"create": {
"title": "",
"description": "",
"action": ""
},
"add": ""
}
},
"integration": {
@@ -1786,6 +1796,9 @@
"showCompletedTorrent": {
"label": ""
},
"showCompletedHttp": {
"label": ""
},
"activeTorrentThreshold": {
"label": ""
},
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff. Show more