Compare commits

...
Author SHA1 Message Date
Releases Homarr def87f36fd chore(release): 1.43.2 [skip ci] 2025-11-07 19:16:38 +00:00
homarr-releases[bot] 5adc2049cc chore(release): automatic release v1.43.2 2025-11-07 19:15:38 +00:00
homarr-renovate[bot] 5f9054b162 fix(deps): update dependency @tanstack/react-query to ^5.90.6 (#4427)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-07 03:23:11 +00:00
homarr-renovate[bot] b450ff6877 chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.25.12 (#4431)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-07 02:04:23 +00:00
homarr-renovate[bot] 97052b8bff chore(deps): update dependency esbuild to ^0.25.12 (#4430)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-07 02:03:50 +00:00
homarr-renovate[bot] 07af9d8673 fix(deps): update dependency jotai to ^2.15.1 (#4426)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-06 06:44:55 +00:00
homarr-renovate[bot] 4516f27fa6 fix(deps): update dependency @gitbeaker/rest to ^43.8.0 (#4425)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-06 06:44:37 +00:00
homarr-renovate[bot] f2164272a5 fix(deps): update dependency sass to ^1.93.3 (#4421)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-06 02:05:33 +00:00
homarr-renovate[bot] 50caa1fcf9 chore(deps): update vitest monorepo to ^4.0.6 (#4420)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 23:30:24 +00:00
homarr-renovate[bot] 742ef66e3c fix(deps): update turbo monorepo to ^2.6.0 (#4422)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 22:34:14 +00:00
homarr-renovate[bot] 0d10b2b99a chore(deps): update dependency @semantic-release/github to ^12.0.1 (#4423)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 22:31:09 +00:00
homarr-renovate[bot] 2ff32627f4 chore(deps): update dependency eslint to ^9.39.0 (#4424)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 21:32:12 +00:00
homarr-renovate[bot] d9b14db6a1 fix(deps): update dependency swagger-ui-react to ^5.30.1 (#4418)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 09:36:37 +00:00
homarr-renovate[bot] 186e3e445d fix(deps): update dependency octokit to ^5.0.5 (#4413)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 09:35:58 +00:00
homarr-renovate[bot] 160c20a9a6 fix(deps): update dependency tsdav to ^2.1.6 (#4417)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 08:43:04 +00:00
homarr-renovate[bot] 97cbade713 fix(deps): update dependency dayjs to ^1.11.19 (#4414)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 07:32:10 +00:00
homarr-renovate[bot] 816c6a0329 chore(deps): update dependency jsdom to ^27.1.0 (#4416)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 06:44:28 +00:00
homarr-renovate[bot] 018a33b078 fix(deps): update dependency @octokit/auth-app to ^8.1.2 (#4412)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-05 02:04:46 +00:00
homarr-renovate[bot] 168458e9e1 chore(deps): update actions/setup-node action to v6 (#4320)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-04 22:15:15 +01:00
homarr-renovate[bot] ebc64f3090 chore(deps): update dependency dotenv-cli to v11 (#4402)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-04 22:13:26 +01:00
homarr-renovate[bot]andMeier Lukas d255492bb7 chore(deps): update vitest monorepo to v4 (major) (#4367)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-11-04 22:03:43 +01:00
homarr-renovate[bot]andMeier Lukas dcf960fa97 fix(deps): update dependency @gitbeaker/rest to ^43.7.0 (#4386)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-11-04 21:28:11 +01:00
homarr-renovate[bot]andMeier Lukas 6ce23a6e97 fix(deps): update nextjs monorepo to v16 (major) (#4363)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-11-04 21:26:44 +01:00
homarr-renovate[bot] a43ac18bef chore(deps): update node.js to v24 (#4395)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-04 21:14:13 +01:00
homarr-renovate[bot] 881d0d1219 chore(deps): update dependency hono@&lt;4.6.5 to >=4.10.4 (#4410)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-04 19:03:28 +00:00
homarr-crowdin[bot] 3fff8b88b3 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-11-04 00:50:14 +00:00
homarr-renovate[bot] 57d1a91866 fix(deps): update dependency @ctrl/deluge to ^7.5.0 (#4407)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-03 21:18:41 +00:00
homarr-renovate[bot] d34f9e5b3e fix(deps): update tiptap monorepo to v2.27.1 (#4405)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-03 16:37:13 +00:00
homarr-renovate[bot] cf89a03f0c chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.13.1 (#4401)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-03 10:36:04 +00:00
homarr-renovate[bot] f4caddfd56 fix(deps): update tiptap monorepo to v2.27.0 (#4404)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-03 09:38:19 +00:00
homarr-renovate[bot] 9e2bbb8a3f fix(deps): update mantine monorepo to ^8.3.6 (#4403)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-03 06:45:35 +00:00
homarr-renovate[bot] 6dbd2802c9 chore(deps): update pnpm to v10.20.0 (#4400)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 18:37:28 +00:00
homarr-renovate[bot] 19e25a095f fix(deps): update dependency superjson to v2.2.5 (#4398)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 17:26:14 +00:00
homarr-renovate[bot] 4c4e901e7d fix(deps): update dependency @jellyfin/sdk to ^0.13.0 (#4399)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 16:34:38 +00:00
homarr-renovate[bot] 5aca5d1b0a fix(deps): update trpc monorepo to ^11.7.1 (#4396)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 14:27:23 +00:00
homarr-renovate[bot] 59434ee33a fix(deps): update dependency drizzle-kit to ^0.31.6 (#4397)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 13:34:04 +00:00
homarr-renovate[bot] b59ee77c28 chore(deps): update node.js to v22.21.1 (#4355)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 02:09:11 +00:00
homarr-renovate[bot] 5effee8148 chore(deps): update dependency @types/pg to ^8.15.6 (#4394)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-02 02:08:59 +00:00
homarr-renovate[bot] ba82853f5c chore(deps): update dependency @types/chroma-js to v3.1.2 (#4393)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-01 21:26:47 +00:00
homarr-renovate[bot] 1491b0fe0e chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.13.0 (#4392)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-01 16:33:43 +00:00
homarr-renovate[bot] b51e285c92 fix(deps): update dependency swagger-ui-react to ^5.30.0 (#4391)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-11-01 12:51:55 +00:00
Meier Lukas 6d9726e19e chore: update bug report template 2025-10-31 23:35:49 +01:00
Releases Homarr 145bea727f chore(release): 1.43.1 [skip ci] 2025-10-31 19:14:59 +00:00
homarr-releases[bot] 5b7de36642 chore(release): automatic release v1.43.1 2025-10-31 19:13:48 +00:00
Meier Lukas 6f3b7ec491 fix(media-releases): support more media type mappings for jellyfin and emby (#4382) 2025-10-31 16:56:15 +01:00
homarr-update-contributors[bot] bbfc6e48fe chore: update contributors (#4389)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-10-31 12:36:00 +00:00
homarr-renovate[bot] 2fe36564c2 fix(deps): update dependency @auth/drizzle-adapter to ^1.11.1 (#4388)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-31 04:38:56 +00:00
homarr-renovate[bot] 747bb922cc fix(deps): update dependency @auth/core to ^0.41.1 (#4387)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-31 03:22:51 +00:00
homarr-renovate[bot] 24def99827 fix(deps): update trpc monorepo to ^11.7.0 (#4385)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-30 17:29:32 +00:00
castielwaverly e0526c5ea7 fix(releases-widget): Search multiple docker hub pages for releases (#4158) 2025-10-29 19:33:50 +01:00
homarr-renovate[bot] 9e4b93abcf chore(deps): update dependency node to v24 (#4368)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-29 18:30:02 +01:00
homarr-renovate[bot] c544d6ec2e fix(deps): update dependency next-auth to v5.0.0-beta.30 [security] (#4379)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-29 12:55:30 +00:00
112 changed files with 2715 additions and 2839 deletions

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ body:
options: options:
# The below comment is used to insert a new version with on-release.yml # The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION# #NEXT_VERSION#
- 1.43.1
- 1.43.0 - 1.43.0
- 1.42.1 - 1.42.1
- 1.42.0 - 1.42.0
@@ -61,10 +61,10 @@ jobs:
persist-credentials: false persist-credentials: false
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
with: with:
node-version: 22.21.0 node-version: 24.11.0
cache: "pnpm" cache: "pnpm"
- run: npm i -g pnpm - run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: "pnpm" cache: "pnpm"
+1 -1
View File
@@ -1 +1 @@
22.21.0 24.11.0
+48
View File
@@ -1,3 +1,51 @@
## [1.43.2](https://github.com/homarr-labs/homarr/compare/v1.43.1...v1.43.2) (2025-11-07)
### Bug Fixes
* **deps:** update dependency @ctrl/deluge to ^7.5.0 ([#4407](https://github.com/homarr-labs/homarr/issues/4407)) ([57d1a91](https://github.com/homarr-labs/homarr/commit/57d1a9186653dd4c327b360a3bdaa78fd8e1187b))
* **deps:** update dependency @gitbeaker/rest to ^43.7.0 ([#4386](https://github.com/homarr-labs/homarr/issues/4386)) ([dcf960f](https://github.com/homarr-labs/homarr/commit/dcf960fa97011c24475f303d587152b42eabec45))
* **deps:** update dependency @gitbeaker/rest to ^43.8.0 ([#4425](https://github.com/homarr-labs/homarr/issues/4425)) ([4516f27](https://github.com/homarr-labs/homarr/commit/4516f27fa69445e5aec16760e1f6ed8080bc8fff))
* **deps:** update dependency @jellyfin/sdk to ^0.13.0 ([#4399](https://github.com/homarr-labs/homarr/issues/4399)) ([4c4e901](https://github.com/homarr-labs/homarr/commit/4c4e901e7d38faf83a4725b7cee5526b0b223dc3))
* **deps:** update dependency @octokit/auth-app to ^8.1.2 ([#4412](https://github.com/homarr-labs/homarr/issues/4412)) ([018a33b](https://github.com/homarr-labs/homarr/commit/018a33b07812fa59f1469ce8b4baa7b1d55c6103))
* **deps:** update dependency @tanstack/react-query to ^5.90.6 ([#4427](https://github.com/homarr-labs/homarr/issues/4427)) ([5f9054b](https://github.com/homarr-labs/homarr/commit/5f9054b162daf974baafdbee9f844b3a860cbdd0))
* **deps:** update dependency dayjs to ^1.11.19 ([#4414](https://github.com/homarr-labs/homarr/issues/4414)) ([97cbade](https://github.com/homarr-labs/homarr/commit/97cbade7135cb06e0780dd1b448c78c8802794ea))
* **deps:** update dependency drizzle-kit to ^0.31.6 ([#4397](https://github.com/homarr-labs/homarr/issues/4397)) ([59434ee](https://github.com/homarr-labs/homarr/commit/59434ee33ae16e06f96e2f07cf85337c313c73f6))
* **deps:** update dependency jotai to ^2.15.1 ([#4426](https://github.com/homarr-labs/homarr/issues/4426)) ([07af9d8](https://github.com/homarr-labs/homarr/commit/07af9d8673ea9573bc436626cd84d11980d023bc))
* **deps:** update dependency octokit to ^5.0.5 ([#4413](https://github.com/homarr-labs/homarr/issues/4413)) ([186e3e4](https://github.com/homarr-labs/homarr/commit/186e3e445dfe6b4fb1aeda3830ae1f260b605882))
* **deps:** update dependency sass to ^1.93.3 ([#4421](https://github.com/homarr-labs/homarr/issues/4421)) ([f216427](https://github.com/homarr-labs/homarr/commit/f2164272a5ca9b51bdf9110664ed5a40b87b60f6))
* **deps:** update dependency superjson to v2.2.5 ([#4398](https://github.com/homarr-labs/homarr/issues/4398)) ([19e25a0](https://github.com/homarr-labs/homarr/commit/19e25a095fb0252e8f2a459e0a97d44a37d2188a))
* **deps:** update dependency swagger-ui-react to ^5.30.0 ([#4391](https://github.com/homarr-labs/homarr/issues/4391)) ([b51e285](https://github.com/homarr-labs/homarr/commit/b51e285c92ef707076f321f0ce1a15d5cbd5de7d))
* **deps:** update dependency swagger-ui-react to ^5.30.1 ([#4418](https://github.com/homarr-labs/homarr/issues/4418)) ([d9b14db](https://github.com/homarr-labs/homarr/commit/d9b14db6a1af515796951a1db670431d10f81eaf))
* **deps:** update dependency tsdav to ^2.1.6 ([#4417](https://github.com/homarr-labs/homarr/issues/4417)) ([160c20a](https://github.com/homarr-labs/homarr/commit/160c20a9a6f850f75537e246f42e93f9a2ed79a0))
* **deps:** update mantine monorepo to ^8.3.6 ([#4403](https://github.com/homarr-labs/homarr/issues/4403)) ([9e2bbb8](https://github.com/homarr-labs/homarr/commit/9e2bbb8a3f2db95c5598e5dca993bf6cd4ebc127))
* **deps:** update nextjs monorepo to v16 (major) ([#4363](https://github.com/homarr-labs/homarr/issues/4363)) ([6ce23a6](https://github.com/homarr-labs/homarr/commit/6ce23a6e971b064292970fc4df90cc69cdb59a95))
* **deps:** update tiptap monorepo to v2.27.0 ([#4404](https://github.com/homarr-labs/homarr/issues/4404)) ([f4caddf](https://github.com/homarr-labs/homarr/commit/f4caddfd564553014854347eb6dd14cc800eb027))
* **deps:** update tiptap monorepo to v2.27.1 ([#4405](https://github.com/homarr-labs/homarr/issues/4405)) ([d34f9e5](https://github.com/homarr-labs/homarr/commit/d34f9e5b3ef999559a4c5f1165488fe4f8828a83))
* **deps:** update trpc monorepo to ^11.7.1 ([#4396](https://github.com/homarr-labs/homarr/issues/4396)) ([5aca5d1](https://github.com/homarr-labs/homarr/commit/5aca5d1b0a76ab4cf0ed27dac62183c766ad398a))
* **deps:** update turbo monorepo to ^2.6.0 ([#4422](https://github.com/homarr-labs/homarr/issues/4422)) ([742ef66](https://github.com/homarr-labs/homarr/commit/742ef66e3cf56fd351c8dee2d3dd05cd639e51fa))
## [1.43.1](https://github.com/homarr-labs/homarr/compare/v1.43.0...v1.43.1) (2025-10-31)
### Bug Fixes
* **deps:** update dependency @auth/core to ^0.41.1 ([#4387](https://github.com/homarr-labs/homarr/issues/4387)) ([747bb92](https://github.com/homarr-labs/homarr/commit/747bb922ccf0508bd00c37faeab29e28876577b1))
* **deps:** update dependency @auth/drizzle-adapter to ^1.11.1 ([#4388](https://github.com/homarr-labs/homarr/issues/4388)) ([2fe3656](https://github.com/homarr-labs/homarr/commit/2fe36564c26c33ae2625eb22ef4503c6dd8ebcf9))
* **deps:** update dependency @jellyfin/sdk to ^0.12.0 ([#4359](https://github.com/homarr-labs/homarr/issues/4359)) ([554d19a](https://github.com/homarr-labs/homarr/commit/554d19a458e856141f90e795ecebabf205e945e9))
* **deps:** update dependency @paralleldrive/cuid2 to v3 ([#4343](https://github.com/homarr-labs/homarr/issues/4343)) ([074d27e](https://github.com/homarr-labs/homarr/commit/074d27e37e3430e7e98cf28e2876afe009811027))
* **deps:** update dependency drizzle-orm to ^0.44.7 ([#4372](https://github.com/homarr-labs/homarr/issues/4372)) ([8242bfc](https://github.com/homarr-labs/homarr/commit/8242bfc95cd0e67397b1bfc46d65aec3f568c304))
* **deps:** update dependency ioredis to v5.8.2 ([#4357](https://github.com/homarr-labs/homarr/issues/4357)) ([3c4c26e](https://github.com/homarr-labs/homarr/commit/3c4c26ee214a37193aea31b69ce75322862fc534))
* **deps:** update dependency mysql2 to v3.15.3 ([#4360](https://github.com/homarr-labs/homarr/issues/4360)) ([6e6c912](https://github.com/homarr-labs/homarr/commit/6e6c9122257934402848d6fdc51e6ccdfc2dcca0))
* **deps:** update dependency next-auth to v5.0.0-beta.30 [security] ([#4379](https://github.com/homarr-labs/homarr/issues/4379)) ([c544d6e](https://github.com/homarr-labs/homarr/commit/c544d6ec2e426bc4c82db547d7c4a5dc4d6e2a27))
* **deps:** update dependency next-intl to v4.4.0 ([#4365](https://github.com/homarr-labs/homarr/issues/4365)) ([ce63388](https://github.com/homarr-labs/homarr/commit/ce6338840c20fc46048687ed5566ff12fa4b2502))
* **deps:** update dependency node-ical to ^0.22.1 ([#4346](https://github.com/homarr-labs/homarr/issues/4346)) ([ef18c58](https://github.com/homarr-labs/homarr/commit/ef18c58f5b09bafd697365a0182ab7bc8719db2b))
* **deps:** update dependency superjson to v2.2.3 ([#4364](https://github.com/homarr-labs/homarr/issues/4364)) ([18cc04e](https://github.com/homarr-labs/homarr/commit/18cc04e7f2740226f7bc7c75c0b0cdaec7cda6f8))
* **deps:** update dependency typescript-eslint to ^8.46.2 ([#4352](https://github.com/homarr-labs/homarr/issues/4352)) ([f96cbe0](https://github.com/homarr-labs/homarr/commit/f96cbe06525515112fad466cb8a5f33d74517aba))
* **deps:** update testcontainers-node monorepo to ^11.7.2 ([#4366](https://github.com/homarr-labs/homarr/issues/4366)) ([7329135](https://github.com/homarr-labs/homarr/commit/7329135fd85906602a16a9bdc16a1ef1cc4607c5))
* **deps:** update tiptap monorepo to v2.26.4 ([#4371](https://github.com/homarr-labs/homarr/issues/4371)) ([136ed8e](https://github.com/homarr-labs/homarr/commit/136ed8e8b401b801ba16f2079bdfb5aeebe9f57f))
* **deps:** update trpc monorepo to ^11.7.0 ([#4385](https://github.com/homarr-labs/homarr/issues/4385)) ([24def99](https://github.com/homarr-labs/homarr/commit/24def9982709cd6de1765a7235169fe1ceb3cbea))
* **media-releases:** support more media type mappings for jellyfin and emby ([#4382](https://github.com/homarr-labs/homarr/issues/4382)) ([6f3b7ec](https://github.com/homarr-labs/homarr/commit/6f3b7ec4915754e183300a915baebc11f264df62))
* **releases-widget:** Search multiple docker hub pages for releases ([#4158](https://github.com/homarr-labs/homarr/issues/4158)) ([e0526c5](https://github.com/homarr-labs/homarr/commit/e0526c5ea7ee83adcd22d78fbf63334cb59bcf79))
## [1.43.0](https://github.com/homarr-labs/homarr/compare/v1.42.1...v1.43.0) (2025-10-24) ## [1.43.0](https://github.com/homarr-labs/homarr/compare/v1.42.1...v1.43.0) (2025-10-24)
### Features ### Features
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:22.20.0-alpine AS base FROM node:24.11.0-alpine AS base
FROM base AS builder FROM base AS builder
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
+9 -3
View File
@@ -20,8 +20,8 @@ const withNextIntl = createNextIntlPlugin({
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
reactStrictMode: true, reactStrictMode: true,
/** We already do linting and typechecking as separate tasks in CI */ reactCompiler: true,
eslint: { ignoreDuringBuilds: true }, /** We already do typechecking as separate tasks in CI */
typescript: { ignoreBuildErrors: true }, typescript: { ignoreBuildErrors: true },
/** /**
* dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612 * dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612
@@ -29,10 +29,16 @@ const nextConfig: NextConfig = {
serverExternalPackages: ["dockerode"], serverExternalPackages: ["dockerode"],
experimental: { experimental: {
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"], optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
turbopackFileSystemCacheForDev: true,
}, },
transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"], transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"],
images: { images: {
domains: ["cdn.jsdelivr.net"], localPatterns: [
{
pathname: "/**",
search: "",
},
],
}, },
// eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax // eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax
async headers() { async headers() {
+21 -20
View File
@@ -50,42 +50,43 @@
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.3.5", "@mantine/colors-generator": "^8.3.6",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@mantine/dropzone": "^8.3.5", "@mantine/dropzone": "^8.3.6",
"@mantine/hooks": "^8.3.5", "@mantine/hooks": "^8.3.6",
"@mantine/modals": "^8.3.5", "@mantine/modals": "^8.3.6",
"@mantine/tiptap": "^8.3.5", "@mantine/tiptap": "^8.3.6",
"@million/lint": "1.0.14", "@million/lint": "1.0.14",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.5", "@tanstack/react-query": "^5.90.6",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-query-next-experimental": "^5.90.2", "@tanstack/react-query-next-experimental": "^5.90.2",
"@trpc/client": "^11.6.0", "@trpc/client": "^11.7.1",
"@trpc/next": "^11.6.0", "@trpc/next": "^11.7.1",
"@trpc/react-query": "^11.6.0", "@trpc/react-query": "^11.7.1",
"@trpc/server": "^11.6.0", "@trpc/server": "^11.7.1",
"@xterm/addon-canvas": "^0.7.0", "@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"babel-plugin-react-compiler": "^1.0.0",
"chroma-js": "^3.1.2", "chroma-js": "^3.1.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"flag-icons": "^7.5.0", "flag-icons": "^7.5.0",
"glob": "^11.0.3", "glob": "^11.0.3",
"jotai": "^2.15.0", "jotai": "^2.15.1",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "15.5.6", "next": "16.0.1",
"postcss-preset-mantine": "^1.18.0", "postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"react-error-boundary": "^6.0.0", "react-error-boundary": "^6.0.0",
"react-simple-code-editor": "^0.14.1", "react-simple-code-editor": "^0.14.1",
"sass": "^1.93.2", "sass": "^1.93.3",
"superjson": "2.2.3", "superjson": "2.2.5",
"swagger-ui-react": "^5.29.5", "swagger-ui-react": "^5.30.1",
"use-deep-compare-effect": "^1.8.1", "use-deep-compare-effect": "^1.8.1",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -93,14 +94,14 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1", "@types/chroma-js": "3.1.2",
"@types/node": "^22.18.12", "@types/node": "^24.9.2",
"@types/prismjs": "^1.26.5", "@types/prismjs": "^1.26.5",
"@types/react": "19.2.2", "@types/react": "19.2.2",
"@types/react-dom": "19.2.2", "@types/react-dom": "19.2.2",
"@types/swagger-ui-react": "^5.18.0", "@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"node-loader": "^2.1.0", "node-loader": "^2.1.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Group, Menu, ScrollArea } from "@mantine/core"; import { Group, Menu, ScrollArea } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks"; import { useHotkeys } from "@mantine/hooks";
@@ -29,6 +28,7 @@ import { useConfirmModal, useModalAction } from "@homarr/modals";
import { AppSelectModal } from "@homarr/modals-collection"; import { AppSelectModal } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { useItemActions } from "~/components/board/items/item-actions"; import { useItemActions } from "~/components/board/items/item-actions";
import { ItemSelectModal } from "~/components/board/items/item-select-modal"; import { ItemSelectModal } from "~/components/board/items/item-select-modal";
@@ -1,4 +1,3 @@
import Link from "next/link";
import type { MantineColor } from "@mantine/core"; import type { MantineColor } from "@mantine/core";
import { Button, Card, Stack, Text } from "@mantine/core"; import { Button, Card, Stack, Text } from "@mantine/core";
import { IconBook2, IconCategoryPlus, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react"; import { IconBook2, IconCategoryPlus, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react";
@@ -8,6 +7,7 @@ import { getMantineColor } from "@homarr/common";
import { db } from "@homarr/db"; import { db } from "@homarr/db";
import { createDocumentationLink } from "@homarr/definitions"; import { createDocumentationLink } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import type { TablerIcon } from "@homarr/ui"; import type { TablerIcon } from "@homarr/ui";
export const InitFinish = async () => { export const InitFinish = async () => {
@@ -1,5 +1,4 @@
import { Fragment } from "react"; import { Fragment } from "react";
import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core"; import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
import { IconBox, IconPencil } from "@tabler/icons-react"; import { IconBox, IconPencil } from "@tabler/icons-react";
@@ -10,7 +9,7 @@ import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import type { inferSearchParamsFromSchema } from "@homarr/common/types"; import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { SearchInput, TablePagination } from "@homarr/ui"; import { Link, SearchInput, TablePagination } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useCallback } from "react"; import { useCallback } from "react";
import Link from "next/link";
import { Menu } from "@mantine/core"; import { Menu } from "@mantine/core";
import { IconCopy, IconDeviceMobile, IconHome, IconSettings, IconTrash } from "@tabler/icons-react"; import { IconCopy, IconDeviceMobile, IconHome, IconSettings, IconTrash } from "@tabler/icons-react";
@@ -12,6 +11,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
import { useConfirmModal, useModalAction } from "@homarr/modals"; import { useConfirmModal, useModalAction } from "@homarr/modals";
import { DuplicateBoardModal } from "@homarr/modals-collection"; import { DuplicateBoardModal } from "@homarr/modals-collection";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { useBoardPermissions } from "~/components/board/permissions/client"; import { useBoardPermissions } from "~/components/board/permissions/client";
@@ -1,4 +1,3 @@
import Link from "next/link";
import { import {
ActionIcon, ActionIcon,
Badge, Badge,
@@ -21,7 +20,7 @@ import type { RouterOutputs } from "@homarr/api";
import { api } from "@homarr/api/server"; import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
import { UserAvatar } from "@homarr/ui"; import { Link, UserAvatar } from "@homarr/ui";
import { getBoardPermissionsAsync } from "~/components/board/permissions/server"; import { getBoardPermissionsAsync } from "~/components/board/permissions/server";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Alert, Anchor, Button, ButtonGroup, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core"; import { Alert, Anchor, Button, ButtonGroup, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core";
import { IconInfoCircle, IconPencil, IconPlus, IconUnlink } from "@tabler/icons-react"; import { IconInfoCircle, IconPencil, IconPlus, IconUnlink } from "@tabler/icons-react";
@@ -17,6 +16,7 @@ import { useConfirmModal, useModalAction } from "@homarr/modals";
import { AppSelectModal } from "@homarr/modals-collection"; import { AppSelectModal } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { integrationUpdateSchema } from "@homarr/validation/integration"; import { integrationUpdateSchema } from "@homarr/validation/integration";
import { SecretCard } from "../../_components/secrets/integration-secret-card"; import { SecretCard } from "../../_components/secrets/integration-secret-card";
@@ -2,13 +2,12 @@
import type { ChangeEvent } from "react"; import type { ChangeEvent } from "react";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import Link from "next/link";
import { Flex, Group, Menu, ScrollArea, Text, TextInput } from "@mantine/core"; import { Flex, Group, Menu, ScrollArea, Text, TextInput } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react"; import { IconSearch } from "@tabler/icons-react";
import { getIntegrationName, integrationKinds } from "@homarr/definitions"; import { getIntegrationName, integrationKinds } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { IntegrationAvatar } from "@homarr/ui"; import { IntegrationAvatar, Link } from "@homarr/ui";
interface IntegrationCreateDropdownContentProps { interface IntegrationCreateDropdownContentProps {
enableMockIntegration: boolean; enableMockIntegration: boolean;
@@ -1,7 +1,6 @@
"use client"; "use client";
import { startTransition, useState } from "react"; import { startTransition, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
Alert, Alert,
@@ -36,6 +35,7 @@ import type { GetInputPropsReturnType, UseFormReturnType } from "@homarr/form";
import { useZodForm } from "@homarr/form"; import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { appHrefSchema } from "@homarr/validation/app"; import { appHrefSchema } from "@homarr/validation/app";
import { integrationCreateSchema } from "@homarr/validation/integration"; import { integrationCreateSchema } from "@homarr/validation/integration";
@@ -1,6 +1,5 @@
import { Fragment } from "react"; import { Fragment } from "react";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { import {
AccordionControl, AccordionControl,
@@ -36,7 +35,7 @@ import { objectEntries } from "@homarr/common";
import type { IntegrationKind } from "@homarr/definitions"; import type { IntegrationKind } from "@homarr/definitions";
import { getIntegrationName } from "@homarr/definitions"; import { getIntegrationName } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
import { CountBadge, IntegrationAvatar } from "@homarr/ui"; import { CountBadge, IntegrationAvatar, Link } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
@@ -1,4 +1,3 @@
import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { import {
ActionIcon, ActionIcon,
@@ -25,7 +24,7 @@ import { humanFileSize } from "@homarr/common";
import type { inferSearchParamsFromSchema } from "@homarr/common/types"; import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { createLocalImageUrl } from "@homarr/icons/local"; import { createLocalImageUrl } from "@homarr/icons/local";
import { getI18n } from "@homarr/translation/server"; import { getI18n } from "@homarr/translation/server";
import { SearchInput, TablePagination, UserAvatar } from "@homarr/ui"; import { Link, SearchInput, TablePagination, UserAvatar } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
+1 -1
View File
@@ -1,10 +1,10 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link";
import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core"; import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react"; import { IconArrowRight } from "@tabler/icons-react";
import { api } from "@homarr/api/server"; import { api } from "@homarr/api/server";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { createMetaTitle } from "~/metadata"; import { createMetaTitle } from "~/metadata";
@@ -1,6 +1,5 @@
"use client"; "use client";
import Link from "next/link";
import type { SegmentedControlItem } from "@mantine/core"; import type { SegmentedControlItem } from "@mantine/core";
import { Button, Fieldset, Grid, Group, SegmentedControl, Stack, Textarea, TextInput } from "@mantine/core"; import { Button, Fieldset, Grid, Group, SegmentedControl, Stack, Textarea, TextInput } from "@mantine/core";
import { WidgetIntegrationSelect } from "node_modules/@homarr/widgets/src/widget-integration-select"; import { WidgetIntegrationSelect } from "node_modules/@homarr/widgets/src/widget-integration-select";
@@ -12,6 +11,7 @@ import { useZodForm } from "@homarr/form";
import { IconPicker } from "@homarr/forms-collection"; import { IconPicker } from "@homarr/forms-collection";
import type { TranslationFunction } from "@homarr/translation"; import type { TranslationFunction } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { searchEngineManageSchema } from "@homarr/validation/search-engine"; import { searchEngineManageSchema } from "@homarr/validation/search-engine";
type FormType = z.infer<typeof searchEngineManageSchema>; type FormType = z.infer<typeof searchEngineManageSchema>;
@@ -1,4 +1,3 @@
import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core"; import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
import { IconPencil, IconSearch } from "@tabler/icons-react"; import { IconPencil, IconSearch } from "@tabler/icons-react";
@@ -9,7 +8,7 @@ import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import type { inferSearchParamsFromSchema } from "@homarr/common/types"; import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { SearchInput, TablePagination } from "@homarr/ui"; import { Link, SearchInput, TablePagination } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button"; import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
@@ -1,5 +1,4 @@
import { X509Certificate } from "node:crypto"; import { X509Certificate } from "node:crypto";
import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { import {
Button, Button,
@@ -19,6 +18,7 @@ import { IconCertificateOff } from "@tabler/icons-react";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server"; import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server";
import { getI18n } from "@homarr/translation/server"; import { getI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results"; import { NoResults } from "~/components/no-results";
@@ -1,5 +1,4 @@
import { X509Certificate } from "node:crypto"; import { X509Certificate } from "node:crypto";
import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Button, Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core"; import { Button, Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core";
import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/icons-react"; import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/icons-react";
@@ -10,6 +9,7 @@ import { loadCustomRootCertificatesAsync } from "@homarr/certificates/server";
import { getMantineColor } from "@homarr/common"; import { getMantineColor } from "@homarr/common";
import type { SupportedLanguage } from "@homarr/translation"; import type { SupportedLanguage } from "@homarr/translation";
import { getI18n } from "@homarr/translation/server"; import { getI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results"; import { NoResults } from "~/components/no-results";
@@ -1,10 +1,10 @@
"use client"; "use client";
import Link from "next/link";
import { Anchor, Center, Stack, Text } from "@mantine/core"; import { Anchor, Center, Stack, Text } from "@mantine/core";
import { IconShipOff } from "@tabler/icons-react"; import { IconShipOff } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
export default function DockerErrorPage() { export default function DockerErrorPage() {
const t = useI18n(); const t = useI18n();
@@ -1,10 +1,10 @@
"use client"; "use client";
import Link from "next/link";
import { Anchor, Center, Stack, Text } from "@mantine/core"; import { Anchor, Center, Stack, Text } from "@mantine/core";
import { IconCubeOff } from "@tabler/icons-react"; import { IconCubeOff } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
export default function KubernetesErrorPage() { export default function KubernetesErrorPage() {
const t = useI18n(); const t = useI18n();
@@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import { Card, Group, Text } from "@mantine/core"; import { Card, Group, Text } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react"; import { IconArrowRight } from "@tabler/icons-react";
import type { KubernetesLabelResourceType } from "@homarr/definitions"; import type { KubernetesLabelResourceType } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import classes from "./resource-tile.module.css"; import classes from "./resource-tile.module.css";
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useMemo } from "react"; import { useMemo } from "react";
import Link from "next/link";
import { Anchor, Button, Group, Text, Title, Tooltip } from "@mantine/core"; import { Anchor, Button, Group, Text, Title, Tooltip } from "@mantine/core";
import { IconCheck } from "@tabler/icons-react"; import { IconCheck } from "@tabler/icons-react";
import type { MRT_ColumnDef } from "mantine-react-table"; import type { MRT_ColumnDef } from "mantine-react-table";
@@ -10,7 +9,7 @@ import { MantineReactTable } from "mantine-react-table";
import type { RouterOutputs } from "@homarr/api"; import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { UserAvatar } from "@homarr/ui"; import { Link, UserAvatar } from "@homarr/ui";
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks"; import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
interface UserListComponentProps { interface UserListComponentProps {
@@ -1,8 +1,8 @@
import Link from "next/link";
import { Button, Card, Group } from "@mantine/core"; import { Button, Card, Group } from "@mantine/core";
import { IconArrowBackUp, IconArrowLeft, IconArrowRight, IconRotate } from "@tabler/icons-react"; import { IconArrowBackUp, IconArrowLeft, IconArrowRight, IconRotate } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
interface StepperNavigationComponentProps { interface StepperNavigationComponentProps {
hasPrevious: boolean; hasPrevious: boolean;
@@ -1,10 +1,11 @@
"use client"; "use client";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { NavLink } from "@mantine/core"; import { NavLink } from "@mantine/core";
import { Link } from "@homarr/ui";
interface NavigationLinkProps { interface NavigationLinkProps {
href: string; href: string;
label: string; label: string;
@@ -1,11 +1,11 @@
"use client"; "use client";
import Link from "next/link";
import { Alert, Anchor } from "@mantine/core"; import { Alert, Anchor } from "@mantine/core";
import { IconExclamationCircle } from "@tabler/icons-react"; import { IconExclamationCircle } from "@tabler/icons-react";
import { createDocumentationLink } from "@homarr/definitions"; import { createDocumentationLink } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
export const ReservedGroupAlert = () => { export const ReservedGroupAlert = () => {
const t = useI18n(); const t = useI18n();
@@ -1,10 +1,10 @@
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
import Link from "next/link";
import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core"; import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core";
import { IconId, IconLock, IconSettings, IconUsersGroup } from "@tabler/icons-react"; import { IconId, IconLock, IconSettings, IconUsersGroup } from "@tabler/icons-react";
import { api } from "@homarr/api/server"; import { api } from "@homarr/api/server";
import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { NavigationLink } from "./_navigation"; import { NavigationLink } from "./_navigation";
@@ -1,4 +1,3 @@
import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Alert, Anchor, Center, Group, Stack, Table, TableTbody, TableTd, TableTr, Text, Title } from "@mantine/core"; import { Alert, Anchor, Center, Group, Stack, Table, TableTbody, TableTd, TableTr, Text, Title } from "@mantine/core";
import { IconExclamationCircle } from "@tabler/icons-react"; import { IconExclamationCircle } from "@tabler/icons-react";
@@ -10,7 +9,7 @@ import { auth } from "@homarr/auth/next";
import { isProviderEnabled } from "@homarr/auth/server"; import { isProviderEnabled } from "@homarr/auth/server";
import { everyoneGroup } from "@homarr/definitions"; import { everyoneGroup } from "@homarr/definitions";
import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { SearchInput, UserAvatar } from "@homarr/ui"; import { Link, SearchInput, UserAvatar } from "@homarr/ui";
import { ReservedGroupAlert } from "../_reserved-group-alert"; import { ReservedGroupAlert } from "../_reserved-group-alert";
import { AddGroupMember } from "./_add-group-member"; import { AddGroupMember } from "./_add-group-member";
@@ -2,7 +2,6 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import Link from "next/link";
import type { DragEndEvent, DraggableAttributes, DragStartEvent } from "@dnd-kit/core"; import type { DragEndEvent, DraggableAttributes, DragStartEvent } from "@dnd-kit/core";
import { import {
closestCenter, closestCenter,
@@ -41,7 +40,7 @@ import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client"; import { revalidatePathActionAsync } from "@homarr/common/client";
import { showSuccessNotification } from "@homarr/notifications"; import { showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { UserAvatarGroup } from "@homarr/ui"; import { Link, UserAvatarGroup } from "@homarr/ui";
interface GroupsTableProps { interface GroupsTableProps {
initialGroupIds: string[]; initialGroupIds: string[];
@@ -1,4 +1,3 @@
import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Card, Group, Stack, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core"; import { Card, Group, Stack, Text, ThemeIcon, Title, UnstyledButton } from "@mantine/core";
import { IconChevronRight, IconUsersGroup } from "@tabler/icons-react"; import { IconChevronRight, IconUsersGroup } from "@tabler/icons-react";
@@ -7,6 +6,7 @@ import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import { everyoneGroup } from "@homarr/definitions"; import { everyoneGroup } from "@homarr/definitions";
import { getI18n } from "@homarr/translation/server"; import { getI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container"; import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
@@ -1,10 +1,10 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link";
import { Anchor, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core"; import { Anchor, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react"; import { IconPlus } from "@tabler/icons-react";
import { useModalAction } from "@homarr/modals"; import { useModalAction } from "@homarr/modals";
import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import type { AccessQueryData } from "./access-settings"; import type { AccessQueryData } from "./access-settings";
import { AccessSelectRow } from "./access-table-rows"; import { AccessSelectRow } from "./access-table-rows";
@@ -1,11 +1,10 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link";
import { Anchor, Box, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core"; import { Anchor, Box, Button, Group, Stack, Table, TableTbody, TableTh, TableThead, TableTr } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react"; import { IconPlus } from "@tabler/icons-react";
import { useModalAction } from "@homarr/modals"; import { useModalAction } from "@homarr/modals";
import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { UserAvatar } from "@homarr/ui"; import { Link, UserAvatar } from "@homarr/ui";
import type { AccessQueryData } from "./access-settings"; import type { AccessQueryData } from "./access-settings";
import { AccessDisplayRow, AccessSelectRow } from "./access-table-rows"; import { AccessDisplayRow, AccessSelectRow } from "./access-table-rows";
+1 -1
View File
@@ -1,8 +1,8 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import Link from "next/link";
import { AppShellHeader, Group, UnstyledButton } from "@mantine/core"; import { AppShellHeader, Group, UnstyledButton } from "@mantine/core";
import { Spotlight } from "@homarr/spotlight"; import { Spotlight } from "@homarr/spotlight";
import { Link } from "@homarr/ui";
import { ClientBurger } from "./header/burger"; import { ClientBurger } from "./header/burger";
import { DesktopSearchInput, MobileSearchButton } from "./header/search"; import { DesktopSearchInput, MobileSearchButton } from "./header/search";
@@ -1,9 +1,10 @@
import type { ForwardedRef, ReactNode } from "react"; import type { ForwardedRef, ReactNode } from "react";
import { forwardRef } from "react"; import { forwardRef } from "react";
import Link from "next/link";
import type { ActionIconProps } from "@mantine/core"; import type { ActionIconProps } from "@mantine/core";
import { ActionIcon } from "@mantine/core"; import { ActionIcon } from "@mantine/core";
import { Link } from "@homarr/ui";
type HeaderButtonProps = ( type HeaderButtonProps = (
| { | {
onClick?: () => void; onClick?: () => void;
@@ -1,10 +1,11 @@
"use client"; "use client";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { NavLink } from "@mantine/core"; import { NavLink } from "@mantine/core";
import { Link } from "@homarr/ui";
export const CommonNavLink = (props: ClientNavigationLink) => export const CommonNavLink = (props: ClientNavigationLink) =>
"href" in props ? <NavLinkHref {...props} /> : <NavLinkWithItems {...props} />; "href" in props ? <NavLinkHref {...props} /> : <NavLinkWithItems {...props} />;
@@ -2,7 +2,6 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core"; import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
import { useHotkeys, useTimeout } from "@mantine/hooks"; import { useHotkeys, useTimeout } from "@mantine/hooks";
@@ -22,6 +21,7 @@ import { signOut, useSession } from "@homarr/auth/client";
import { hotkeys } from "@homarr/definitions"; import { hotkeys } from "@homarr/definitions";
import { createModal, useModalAction } from "@homarr/modals"; import { createModal, useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { useAuthContext } from "~/app/[locale]/_client-providers/session"; import { useAuthContext } from "~/app/[locale]/_client-providers/session";
import { CurrentLanguageCombobox } from "./language/current-language-combobox"; import { CurrentLanguageCombobox } from "./language/current-language-combobox";
@@ -1,10 +1,7 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { createTRPCClient, httpLink } from "@trpc/client"; import { NextResponse } from "next/server";
import SuperJSON from "superjson";
import type { AppRouter } from "@homarr/api"; import { api } from "@homarr/api/server";
import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared";
import { localeCookieKey } from "@homarr/definitions"; import { localeCookieKey } from "@homarr/definitions";
import type { SupportedLanguage } from "@homarr/translation"; import type { SupportedLanguage } from "@homarr/translation";
import { supportedLanguages } from "@homarr/translation"; import { supportedLanguages } from "@homarr/translation";
@@ -12,12 +9,12 @@ import { createI18nMiddleware } from "@homarr/translation/middleware";
let isOnboardingFinished = false; let isOnboardingFinished = false;
export async function middleware(request: NextRequest) { export async function proxy(request: NextRequest) {
// Redirect to onboarding if it's not finished yet // Redirect to onboarding if it's not finished yet
const pathname = request.nextUrl.pathname; const pathname = request.nextUrl.pathname;
if (!isOnboardingFinished && !pathname.endsWith("/init")) { if (!isOnboardingFinished && !pathname.endsWith("/init")) {
const currentOnboardingStep = await serverFetchApi.onboard.currentStep.query(); const currentOnboardingStep = await api.onboard.currentStep();
if (currentOnboardingStep.current !== "finish") { if (currentOnboardingStep.current !== "finish") {
return NextResponse.redirect(new URL("/init", request.url)); return NextResponse.redirect(new URL("/init", request.url));
} }
@@ -29,11 +26,12 @@ export async function middleware(request: NextRequest) {
const currentLocale = request.cookies.get(localeCookieKey)?.value; const currentLocale = request.cookies.get(localeCookieKey)?.value;
let defaultLocale: SupportedLanguage = "en"; let defaultLocale: SupportedLanguage = "en";
if (!currentLocale || !supportedLanguages.includes(currentLocale as SupportedLanguage)) { if (!currentLocale || !supportedLanguages.includes(currentLocale as SupportedLanguage)) {
defaultLocale = await serverFetchApi.serverSettings.getCulture.query().then((culture) => culture.defaultLocale); defaultLocale = await api.serverSettings.getCulture().then((culture) => culture.defaultLocale);
} }
// We don't want to fallback to accept-language header so we clear it // We don't want to fallback to accept-language header so we clear it
request.headers.set("accept-language", ""); request.headers.set("accept-language", "");
const next = createI18nMiddleware(defaultLocale); const next = createI18nMiddleware(defaultLocale);
return next(request); return next(request);
} }
@@ -41,13 +39,3 @@ export async function middleware(request: NextRequest) {
export const config = { export const config = {
matcher: ["/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)"], matcher: ["/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)"],
}; };
export const serverFetchApi = createTRPCClient<AppRouter>({
links: [
httpLink({
url: getTrpcUrl(),
transformer: SuperJSON,
headers: createHeadersCallbackForSource("server-fetch"),
}),
],
});
+6 -6
View File
@@ -37,20 +37,20 @@
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"fastify": "^5.6.1", "fastify": "^5.6.1",
"superjson": "2.2.3", "superjson": "2.2.5",
"undici": "7.16.0" "undici": "7.16.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.18.12", "@types/node": "^24.9.2",
"dotenv-cli": "^10.0.0", "dotenv-cli": "^11.0.0",
"esbuild": "^0.25.11", "esbuild": "^0.25.12",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"tsx": "4.20.4", "tsx": "4.20.4",
"typescript": "^5.9.3" "typescript": "^5.9.3"
+2 -2
View File
@@ -34,8 +34,8 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"esbuild": "^0.25.11", "esbuild": "^0.25.12",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "homarr", "name": "homarr",
"version": "1.43.0", "version": "1.43.2",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "cross-env CI=true turbo build", "build": "cross-env CI=true turbo build",
@@ -39,28 +39,28 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.0", "@semantic-release/github": "^12.0.1",
"@semantic-release/npm": "^13.1.1", "@semantic-release/npm": "^13.1.1",
"@semantic-release/release-notes-generator": "^14.1.0", "@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.7.2", "@testcontainers/redis": "^11.7.2",
"@turbo/gen": "^2.5.8", "@turbo/gen": "^2.6.0",
"@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react": "^5.1.0",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^4.0.6",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^4.0.6",
"conventional-changelog-conventionalcommits": "^9.1.0", "conventional-changelog-conventionalcommits": "^9.1.0",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"jsdom": "^27.0.1", "jsdom": "^27.1.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"semantic-release": "^25.0.1", "semantic-release": "^25.0.1",
"testcontainers": "^11.7.2", "testcontainers": "^11.7.2",
"turbo": "^2.5.8", "turbo": "^2.6.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4" "vitest": "^4.0.6"
}, },
"packageManager": "pnpm@10.19.0", "packageManager": "pnpm@10.20.0",
"engines": { "engines": {
"node": ">=22.21.0" "node": ">=24.11.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
@@ -77,12 +77,12 @@
"overrides": { "overrides": {
"@babel/helpers@<7.26.10": ">=7.28.4", "@babel/helpers@<7.26.10": ">=7.28.4",
"@babel/runtime@<7.26.10": ">=7.28.4", "@babel/runtime@<7.26.10": ">=7.28.4",
"axios@>=1.0.0 <1.8.2": ">=1.12.2", "axios@>=1.0.0 <1.8.2": ">=1.13.1",
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1", "brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1", "brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.25.11", "esbuild@<=0.24.2": ">=0.25.12",
"form-data@>=4.0.0 <4.0.4": ">=4.0.4", "form-data@>=4.0.0 <4.0.4": ">=4.0.4",
"hono@<4.6.5": ">=4.10.3", "hono@<4.6.5": ">=4.10.4",
"linkifyjs@<4.3.2": ">=4.3.2", "linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6", "nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0", "prismjs@<1.30.0": ">=1.30.0",
+2 -2
View File
@@ -26,13 +26,13 @@
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@umami/node": "^0.4.0", "@umami/node": "^0.4.0",
"superjson": "2.2.3" "superjson": "2.2.5"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+8 -8
View File
@@ -43,16 +43,16 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.4.0", "@kubernetes/client-node": "^1.4.0",
"@tanstack/react-query": "^5.90.5", "@tanstack/react-query": "^5.90.6",
"@trpc/client": "^11.6.0", "@trpc/client": "^11.7.1",
"@trpc/react-query": "^11.6.0", "@trpc/react-query": "^11.7.1",
"@trpc/server": "^11.6.0", "@trpc/server": "^11.7.1",
"@trpc/tanstack-react-query": "^11.6.0", "@trpc/tanstack-react-query": "^11.7.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"superjson": "2.2.3", "superjson": "2.2.5",
"trpc-to-openapi": "^3.1.0", "trpc-to-openapi": "^3.1.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -60,7 +60,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
+15 -8
View File
@@ -40,15 +40,22 @@ export const releasesRouter = createTRPCRouter({
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
return await Promise.all( return await Promise.all(
input.repositories.map(async (repository) => { input.repositories.map(async (repository) => {
const innerHandler = releasesRequestHandler.handler(ctx.integration, { const response = await releasesRequestHandler
id: repository.id, .handler(ctx.integration, {
identifier: repository.identifier, id: repository.id,
versionRegex: formatVersionFilterRegex(repository.versionFilter), identifier: repository.identifier,
}); versionRegex: formatVersionFilterRegex(repository.versionFilter),
})
.getCachedOrUpdatedDataAsync({
forceUpdate: false,
});
return await innerHandler.getCachedOrUpdatedDataAsync({ return {
forceUpdate: false, id: repository.id,
}); integration: { name: ctx.integration.name, kind: ctx.integration.kind },
timestamp: response.timestamp,
...response.data,
};
}), }),
); );
}), }),
+5 -5
View File
@@ -23,8 +23,8 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@auth/core": "^0.41.0", "@auth/core": "^0.41.1",
"@auth/drizzle-adapter": "^1.11.0", "@auth/drizzle-adapter": "^1.11.1",
"@homarr/certificates": "workspace:^0.1.0", "@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
@@ -35,8 +35,8 @@
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"cookies": "^0.9.1", "cookies": "^0.9.1",
"ldapts": "8.0.9", "ldapts": "8.0.9",
"next": "15.5.6", "next": "16.0.1",
"next-auth": "5.0.0-beta.29", "next-auth": "5.0.0-beta.30",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"zod": "^4.1.12" "zod": "^4.1.12"
@@ -47,7 +47,7 @@
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0", "@types/bcrypt": "6.0.0",
"@types/cookies": "0.9.2", "@types/cookies": "0.9.2",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
@@ -23,12 +23,11 @@ describe("authorizeWithLdapCredentials", () => {
test("should fail when wrong ldap base credentials", async () => { test("should fail when wrong ldap base credentials", async () => {
// Arrange // Arrange
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.reject(new Error("bindAsync"))),
bindAsync: vi.fn(() => Promise.reject(new Error("bindAsync"))), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
// Act // Act
const act = () => const act = () =>
@@ -44,13 +43,12 @@ describe("authorizeWithLdapCredentials", () => {
test("should fail when user not found", async () => { test("should fail when user not found", async () => {
// Arrange // Arrange
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn(() => Promise.resolve([])),
searchAsync: vi.fn(() => Promise.resolve([])), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
// Act // Act
const act = () => const act = () =>
@@ -66,20 +64,19 @@ describe("authorizeWithLdapCredentials", () => {
test("should fail when user has invalid email", async () => { test("should fail when user has invalid email", async () => {
// Arrange // Arrange
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn(() =>
searchAsync: vi.fn(() => Promise.resolve([
Promise.resolve([ {
{ dn: "test",
dn: "test", mail: "test",
mail: "test", },
}, ]),
]), ),
), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
// Act // Act
const act = () => const act = () =>
@@ -103,15 +100,14 @@ describe("authorizeWithLdapCredentials", () => {
]), ]),
); );
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn((props: ldapClient.BindOptions) =>
bindAsync: vi.fn((props: ldapClient.BindOptions) => props.distinguishedName === "test" ? Promise.reject(new Error("bindAsync")) : Promise.resolve(),
props.distinguishedName === "test" ? Promise.reject(new Error("bindAsync")) : Promise.resolve(), ),
), searchAsync: searchSpy,
searchAsync: searchSpy, } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
// Act // Act
const act = () => const act = () =>
@@ -129,21 +125,20 @@ describe("authorizeWithLdapCredentials", () => {
// Arrange // Arrange
const db = createDb(); const db = createDb();
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn(() =>
searchAsync: vi.fn(() => Promise.resolve([
Promise.resolve([ {
{ dn: "test",
dn: "test", mail: "test@gmail.com",
mail: "test@gmail.com", },
}, ]),
]), ),
), disconnectAsync: vi.fn(),
disconnectAsync: vi.fn(), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
// Act // Act
const result = await authorizeWithLdapCredentialsAsync(db, { const result = await authorizeWithLdapCredentialsAsync(db, {
@@ -168,21 +163,20 @@ describe("authorizeWithLdapCredentials", () => {
// Arrange // Arrange
const db = createDb(); const db = createDb();
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn(() =>
searchAsync: vi.fn(() => Promise.resolve([
Promise.resolve([ {
{ dn: "test",
dn: "test", mail: "test@gmail.com",
mail: "test@gmail.com", },
}, ]),
]), ),
), disconnectAsync: vi.fn(),
disconnectAsync: vi.fn(), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
await db.insert(users).values({ await db.insert(users).values({
id: createId(), id: createId(),
name: "test", name: "test",
@@ -220,21 +214,20 @@ describe("authorizeWithLdapCredentials", () => {
test("should authorize user with correct credentials and return updated name", async () => { test("should authorize user with correct credentials and return updated name", async () => {
// Arrange // Arrange
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn(() =>
searchAsync: vi.fn(() => Promise.resolve([
Promise.resolve([ {
{ dn: "test55",
dn: "test55", mail: "test@gmail.com",
mail: "test@gmail.com", },
}, ]),
]), ),
), disconnectAsync: vi.fn(),
disconnectAsync: vi.fn(), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
const userId = createId(); const userId = createId();
const db = createDb(); const db = createDb();
@@ -268,27 +261,26 @@ describe("authorizeWithLdapCredentials", () => {
test("should authorize user with correct credentials and return his groups", async () => { test("should authorize user with correct credentials and return his groups", async () => {
// Arrange // Arrange
const spy = vi.spyOn(ldapClient, "LdapClient"); const spy = vi.spyOn(ldapClient, "LdapClient");
spy.mockImplementation( spy.mockImplementation(function () {
() => return {
({ bindAsync: vi.fn(() => Promise.resolve()),
bindAsync: vi.fn(() => Promise.resolve()), searchAsync: vi.fn((argument: { options: { filter: string } }) =>
searchAsync: vi.fn((argument: { options: { filter: string } }) => argument.options.filter.includes("group")
argument.options.filter.includes("group") ? Promise.resolve([
? Promise.resolve([ {
{ cn: "homarr_example",
cn: "homarr_example", },
}, ])
]) : Promise.resolve([
: Promise.resolve([ {
{ dn: "test55",
dn: "test55", mail: "test@gmail.com",
mail: "test@gmail.com", },
}, ]),
]), ),
), disconnectAsync: vi.fn(),
disconnectAsync: vi.fn(), } as unknown as ldapClient.LdapClient;
}) as unknown as ldapClient.LdapClient, });
);
const db = createDb(); const db = createDb();
const userId = createId(); const userId = createId();
await db.insert(users).values({ await db.insert(users).values({
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+2 -2
View File
@@ -34,8 +34,8 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"esbuild": "^0.25.11", "esbuild": "^0.25.12",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+4 -4
View File
@@ -30,10 +30,10 @@
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^3.1.0", "@paralleldrive/cuid2": "^3.1.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"dns-caching": "^0.2.7", "dns-caching": "^0.2.7",
"next": "15.5.6", "next": "16.0.1",
"octokit": "^5.0.4", "octokit": "^5.0.5",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"undici": "7.16.0", "undici": "7.16.0",
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+5 -5
View File
@@ -29,10 +29,10 @@
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@tanstack/react-query": "^5.90.5", "@tanstack/react-query": "^5.90.6",
"@trpc/client": "^11.6.0", "@trpc/client": "^11.7.1",
"@trpc/server": "^11.6.0", "@trpc/server": "^11.7.1",
"@trpc/tanstack-react-query": "^11.6.0", "@trpc/tanstack-react-query": "^11.7.1",
"node-cron": "^4.2.1", "node-cron": "^4.2.1",
"react": "19.2.0", "react": "19.2.0",
"zod": "^4.1.12" "zod": "^4.1.12"
@@ -43,7 +43,7 @@
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"@types/react": "19.2.2", "@types/react": "19.2.2",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+8 -8
View File
@@ -43,34 +43,34 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@auth/core": "^0.41.0", "@auth/core": "^0.41.1",
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@paralleldrive/cuid2": "^3.1.0", "@paralleldrive/cuid2": "^3.1.0",
"@testcontainers/mysql": "^11.7.2", "@testcontainers/mysql": "^11.7.2",
"@testcontainers/postgresql": "^11.7.2", "@testcontainers/postgresql": "^11.7.2",
"better-sqlite3": "^12.4.1", "better-sqlite3": "^12.4.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-kit": "^0.31.5", "drizzle-kit": "^0.31.6",
"drizzle-orm": "^0.44.7", "drizzle-orm": "^0.44.7",
"drizzle-zod": "^0.8.3", "drizzle-zod": "^0.8.3",
"mysql2": "3.15.3", "mysql2": "3.15.3",
"pg": "^8.16.3", "pg": "^8.16.3",
"superjson": "2.2.3" "superjson": "2.2.5"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13", "@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.15.5", "@types/pg": "^8.15.6",
"dotenv-cli": "^10.0.0", "dotenv-cli": "^11.0.0",
"esbuild": "^0.25.11", "esbuild": "^0.25.12",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"tsx": "4.20.4", "tsx": "4.20.4",
"typescript": "^5.9.3" "typescript": "^5.9.3"
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"tsx": "4.20.4", "tsx": "4.20.4",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/dockerode": "^3.3.45", "@types/dockerode": "^3.3.45",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+2 -2
View File
@@ -26,7 +26,7 @@
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.5", "@mantine/form": "^8.3.6",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -34,7 +34,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+3 -2
View File
@@ -28,8 +28,9 @@
"@homarr/form": "workspace:^0.1.0", "@homarr/form": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"react": "19.2.0", "react": "19.2.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -37,7 +38,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
@@ -2,7 +2,6 @@
import type { ChangeEventHandler } from "react"; import type { ChangeEventHandler } from "react";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import Link from "next/link";
import { Button, Checkbox, Collapse, Group, Stack, Textarea, TextInput } from "@mantine/core"; import { Button, Checkbox, Collapse, Group, Stack, Textarea, TextInput } from "@mantine/core";
import { useDebouncedValue, useDisclosure } from "@mantine/hooks"; import { useDebouncedValue, useDisclosure } from "@mantine/hooks";
import type { z } from "zod/v4"; import type { z } from "zod/v4";
@@ -10,6 +9,7 @@ import type { z } from "zod/v4";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import { useZodForm } from "@homarr/form"; import { useZodForm } from "@homarr/form";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { appManageSchema } from "@homarr/validation/app"; import { appManageSchema } from "@homarr/validation/app";
import { IconPicker } from "../icon-picker/icon-picker"; import { IconPicker } from "../icon-picker/icon-picker";
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0", "@types/bcrypt": "6.0.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+7 -7
View File
@@ -25,10 +25,10 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@ctrl/deluge": "^7.4.0", "@ctrl/deluge": "^7.5.0",
"@ctrl/qbittorrent": "^9.10.0", "@ctrl/qbittorrent": "^9.10.0",
"@ctrl/transmission": "^7.4.0", "@ctrl/transmission": "^7.4.0",
"@gitbeaker/rest": "^43.5.0", "@gitbeaker/rest": "^43.8.0",
"@homarr/certificates": "workspace:^0.1.0", "@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
@@ -39,14 +39,14 @@
"@homarr/redis": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@jellyfin/sdk": "^0.12.0", "@jellyfin/sdk": "^0.13.0",
"@octokit/auth-app": "^8.1.1", "@octokit/auth-app": "^8.1.2",
"ical.js": "^2.2.1", "ical.js": "^2.2.1",
"maria2": "^0.4.1", "maria2": "^0.4.1",
"node-ical": "^0.22.1", "node-ical": "^0.22.1",
"octokit": "^5.0.4", "octokit": "^5.0.5",
"proxmox-api": "1.1.1", "proxmox-api": "1.1.1",
"tsdav": "^2.1.5", "tsdav": "^2.1.6",
"undici": "7.16.0", "undici": "7.16.0",
"xml2js": "^0.6.2", "xml2js": "^0.6.2",
"zod": "^4.1.12" "zod": "^4.1.12"
@@ -57,7 +57,7 @@
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-unifi": "^2.5.1", "@types/node-unifi": "^2.5.1",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
import type { import type {
DetailsProviderResponse, DetailsProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas"; import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas";
@@ -43,22 +42,23 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const [owner, name] = repository.identifier.split("/"); const [owner, name] = identifier.split("/");
if (!owner || !name) { if (!owner || !name) {
localLogger.warn( localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Codeberg integration`, `Invalid identifier format. Expected 'owner/name', for ${identifier} with Codeberg integration`,
{ { identifier },
identifier: repository.identifier,
},
); );
return { return null;
id: repository.id,
error: { code: "invalidIdentifier" },
};
} }
return { owner, name };
}
const details = await this.getDetailsAsync(owner, name); public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const parsedIdentifier = this.parseIdentifier(identifier);
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
const { owner, name } = parsedIdentifier;
const releasesResponse = await this.withHeadersAsync(async (headers) => { const releasesResponse = await this.withHeadersAsync(async (headers) => {
return await fetchWithTrustedCertificatesAsync( return await fetchWithTrustedCertificatesAsync(
@@ -66,34 +66,36 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
{ headers }, { headers },
); );
}); });
if (!releasesResponse.ok) { if (!releasesResponse.ok) {
return { return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
id: repository.id,
error: { message: releasesResponse.statusText },
};
} }
const releasesResponseJson: unknown = await releasesResponse.json(); const releasesResponseJson: unknown = await releasesResponse.json();
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
if (!success) { if (!success) {
return { return {
id: repository.id, success: false,
error: { error: {
code: "unexpected",
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message, message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
}, },
}; };
} else {
const formattedReleases = data.map((tag) => ({
latestRelease: tag.tag_name,
latestReleaseAt: tag.published_at,
releaseUrl: tag.url,
releaseDescription: tag.body,
isPreRelease: tag.prerelease,
}));
return getLatestRelease(formattedReleases, repository, details);
} }
const formattedReleases = data.map((tag) => ({
latestRelease: tag.tag_name,
latestReleaseAt: tag.published_at,
releaseUrl: tag.url,
releaseDescription: tag.body,
isPreRelease: tag.prerelease,
}));
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
const details = await this.getDetailsAsync(owner, name);
return { success: true, data: { ...details, ...latestRelease } };
} }
protected async getDetailsAsync(owner: string, name: string): Promise<DetailsProviderResponse | undefined> { protected async getDetailsAsync(owner: string, name: string): Promise<DetailsProviderResponse | undefined> {
@@ -14,8 +14,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
import type { import type {
DetailsProviderResponse, DetailsProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas"; import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas";
@@ -73,49 +72,61 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const relativeUrl = this.getRelativeUrl(repository.identifier); if (!identifier.includes("/")) return { owner: "", name: identifier };
if (relativeUrl === "/") { const [owner, name] = identifier.split("/");
localLogger.warn( if (!owner || !name) {
`Invalid identifier format. Expected 'owner/name' or 'name', for ${repository.identifier} on DockerHub`, localLogger.warn(`Invalid identifier format. Expected 'owner/name' or 'name', for ${identifier} on DockerHub`, {
{ identifier,
identifier: repository.identifier,
},
);
return {
id: repository.id,
error: { code: "invalidIdentifier" },
};
}
const details = await this.getDetailsAsync(relativeUrl);
const releasesResponse = await this.withHeadersAsync(async (headers) => {
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100`), {
headers,
}); });
}); return null;
}
return { owner, name };
}
if (!releasesResponse.ok) { public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
return { const parsedIdentifier = this.parseIdentifier(identifier);
id: repository.id, if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
error: { message: releasesResponse.statusText },
}; const { owner, name } = parsedIdentifier;
const relativeUrl: `/${string}` = owner
? `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`
: `/v2/repositories/library/${encodeURIComponent(name)}`;
for (let page = 0; page <= 5; page++) {
const releasesResponse = await this.withHeadersAsync(async (headers) => {
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100&page=${page}`), {
headers,
});
});
if (!releasesResponse.ok) {
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
}
const releasesResponseJson: unknown = await releasesResponse.json();
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
if (!releasesResult.success) {
return {
success: false,
error: {
code: "unexpected",
message: releasesResponseJson
? JSON.stringify(releasesResponseJson, null, 2)
: releasesResult.error.message,
},
};
}
const latestRelease = getLatestRelease(releasesResult.data.results, versionRegex);
if (!latestRelease) continue;
const details = await this.getDetailsAsync(relativeUrl);
return { success: true, data: { ...details, ...latestRelease } };
} }
const releasesResponseJson: unknown = await releasesResponse.json(); return { success: false, error: { code: "noMatchingVersion" } };
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
if (!releasesResult.success) {
return {
id: repository.id,
error: {
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : releasesResult.error.message,
},
};
} else {
return getLatestRelease(releasesResult.data.results, repository, details);
}
} }
private async getDetailsAsync(relativeUrl: `/${string}`): Promise<DetailsProviderResponse | undefined> { private async getDetailsAsync(relativeUrl: `/${string}`): Promise<DetailsProviderResponse | undefined> {
@@ -154,18 +165,6 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
}; };
} }
private getRelativeUrl(identifier: string): `/${string}` {
if (identifier.indexOf("/") > 0) {
const [owner, name] = identifier.split("/");
if (!owner || !name) {
return "/";
}
return `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`;
} else {
return `/v2/repositories/library/${encodeURIComponent(identifier)}`;
}
}
private async getSessionAsync(fetchAsync: typeof fetch = fetchWithTrustedCertificatesAsync): Promise<string> { private async getSessionAsync(fetchAsync: typeof fetch = fetchWithTrustedCertificatesAsync): Promise<string> {
const response = await fetchAsync(this.url("/v2/auth/token"), { const response = await fetchAsync(this.url("/v2/auth/token"), {
method: "POST", method: "POST",
@@ -11,7 +11,7 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration"; import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types"; import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
import { convertJellyfinType } from "../jellyfin/jellyfin-integration"; import { convertJellyfinType } from "../jellyfin/jellyfin-integration";
import type { IMediaReleasesIntegration, MediaRelease } from "../types"; import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
const sessionSchema = z.object({ const sessionSchema = z.object({
NowPlayingItem: z NowPlayingItem: z
@@ -163,7 +163,7 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
return items.map((item) => ({ return items.map((item) => ({
id: item.Id, id: item.Id,
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown", type: this.mapMediaReleaseType(item.Type),
title: item.Name, title: item.Name,
subtitle: item.Taglines.at(0), subtitle: item.Taglines.at(0),
description: item.Overview, description: item.Overview,
@@ -179,6 +179,27 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
})); }));
} }
private mapMediaReleaseType(type: string | undefined): MediaType {
switch (type) {
case "Audio":
case "AudioBook":
case "MusicAlbum":
return "music";
case "Book":
return "book";
case "Episode":
case "Series":
case "Season":
return "tv";
case "Movie":
return "movie";
case "Video":
return "video";
default:
return "unknown";
}
}
// https://dev.emby.media/reference/RestAPI/UserService/getUsersPublic.html // https://dev.emby.media/reference/RestAPI/UserService/getUsersPublic.html
private async fetchUsersPublicAsync(): Promise<{ id: string; name: string }[]> { private async fetchUsersPublicAsync(): Promise<{ id: string; name: string }[]> {
const apiKey = super.getSecretValue("apiKey"); const apiKey = super.getSecretValue("apiKey");
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
import type { import type {
DetailsProviderResponse, DetailsProviderResponse,
ReleaseProviderResponse, ReleaseProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" }); const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
@@ -43,23 +42,24 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const [owner, name] = repository.identifier.split("/"); const [owner, name] = identifier.split("/");
if (!owner || !name) { if (!owner || !name) {
localLogger.warn( localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with GitHub Container Registry integration`, `Invalid identifier format. Expected 'owner/name', for ${identifier} with GitHub Container Registry integration`,
{ { identifier },
identifier: repository.identifier,
},
); );
return { return null;
id: repository.id,
error: { code: "invalidIdentifier" },
};
} }
return { owner, name };
}
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const parsedIdentifier = this.parseIdentifier(identifier);
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
const { owner, name } = parsedIdentifier;
const api = this.getApi(); const api = this.getApi();
const details = await this.getDetailsAsync(api, owner, name);
try { try {
const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({ const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
@@ -83,20 +83,20 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
return acc; return acc;
}, []); }, []);
return getLatestRelease(releasesProviderResponse, repository, details); const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
const details = await this.getDetailsAsync(api, owner, name);
return { success: true, data: { ...details, ...latestRelease } };
} catch (error) { } catch (error) {
const errorMessage = error instanceof RequestError ? error.message : String(error); const errorMessage = error instanceof RequestError ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, { localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, {
owner, owner,
name, name,
error: errorMessage, error: errorMessage,
}); });
return { success: false, error: { code: "unexpected", message: errorMessage } };
return {
id: repository.id,
error: { message: errorMessage },
};
} }
} }
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
import type { import type {
DetailsProviderResponse, DetailsProviderResponse,
ReleaseProviderResponse, ReleaseProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GithubIntegration" }); const localLogger = logger.child({ module: "GithubIntegration" });
@@ -43,38 +42,32 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const [owner, name] = repository.identifier.split("/"); const [owner, name] = identifier.split("/");
if (!owner || !name) { if (!owner || !name) {
localLogger.warn( localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Github integration`, {
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Github integration`, identifier,
{ });
identifier: repository.identifier, return null;
},
);
return {
id: repository.id,
error: { code: "invalidIdentifier" },
};
} }
return { owner, name };
}
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const parsedIdentifier = this.parseIdentifier(identifier);
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
const { owner, name } = parsedIdentifier;
const api = this.getApi(); const api = this.getApi();
const details = await this.getDetailsAsync(api, owner, name);
try { try {
const releasesResponse = await api.rest.repos.listReleases({ const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name });
owner,
repo: name,
});
if (releasesResponse.data.length === 0) { if (releasesResponse.data.length === 0) {
localLogger.warn(`No releases found, for ${repository.identifier} with Github integration`, { localLogger.warn(`No releases found, for ${owner}/${name} with Github integration`, {
identifier: repository.identifier, identifier: `${owner}/${name}`,
}); });
return { return { success: false, error: { code: "noMatchingVersion" } };
id: repository.id,
error: { code: "noReleasesFound" },
};
} }
const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => { const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => {
@@ -90,20 +83,20 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
return acc; return acc;
}, []); }, []);
return getLatestRelease(releasesProviderResponse, repository, details); const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
const details = await this.getDetailsAsync(api, owner, name);
return { success: true, data: { ...details, ...latestRelease } };
} catch (error) { } catch (error) {
const errorMessage = error instanceof OctokitRequestError ? error.message : String(error); const errorMessage = error instanceof OctokitRequestError ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, { localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, {
owner, owner,
name, name,
error: errorMessage, error: errorMessage,
}); });
return { success: false, error: { code: "unexpected", message: errorMessage } };
return {
id: repository.id,
error: { message: errorMessage },
};
} }
} }
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
import type { import type {
DetailsProviderResponse, DetailsProviderResponse,
ReleaseProviderResponse, ReleaseProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
const localLogger = logger.child({ module: "GitlabIntegration" }); const localLogger = logger.child({ module: "GitlabIntegration" });
@@ -40,25 +39,20 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const api = this.getApi(); const api = this.getApi();
const details = await this.getDetailsAsync(api, repository.identifier);
try { try {
const releasesResponse = await api.ProjectReleases.all(repository.identifier, { const releasesResponse = await api.ProjectReleases.all(identifier, {
perPage: 100, perPage: 100,
}); });
if (releasesResponse instanceof Error) { if (releasesResponse instanceof Error) {
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, { localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
identifier: repository.identifier, identifier,
error: releasesResponse.message, error: releasesResponse.message,
}); });
return { return { success: false, error: { code: "noReleasesFound" } };
id: repository.id,
error: { code: "noReleasesFound" },
};
} }
const releasesProviderResponse = releasesResponse.reduce<ReleaseProviderResponse[]>((acc, release) => { const releasesProviderResponse = releasesResponse.reduce<ReleaseProviderResponse[]>((acc, release) => {
@@ -76,17 +70,19 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
return acc; return acc;
}, []); }, []);
return getLatestRelease(releasesProviderResponse, repository, details); const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
} catch (error) { if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
identifier: repository.identifier,
error: error instanceof Error ? error.message : String(error),
});
return { const details = await this.getDetailsAsync(api, identifier);
id: repository.id,
error: { code: "noReleasesFound" }, return { success: true, data: { ...details, ...latestRelease } };
}; } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
identifier,
error: errorMessage,
});
return { success: false, error: { code: "unexpected", message: errorMessage } };
} }
} }
@@ -112,7 +108,7 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
return { return {
projectUrl: response.web_url, projectUrl: response.web_url,
projectDescription: response.description, projectDescription: response.description ?? undefined,
isFork: response.forked_from_project !== null, isFork: response.forked_from_project !== null,
isArchived: response.archived, isArchived: response.archived,
createdAt: new Date(response.created_at), createdAt: new Date(response.created_at),
+1 -1
View File
@@ -47,7 +47,7 @@ export type {
TdarrStatistics, TdarrStatistics,
TdarrWorker, TdarrWorker,
} from "./interfaces/media-transcoding/media-transcoding-types"; } from "./interfaces/media-transcoding/media-transcoding-types";
export type { ReleasesResponse } from "./interfaces/releases-providers/releases-providers-types"; export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
export type { Notification } from "./interfaces/notifications/notification-types"; export type { Notification } from "./interfaces/notifications/notification-types";
// Schemas // Schemas
@@ -1,47 +1,21 @@
import type { import type { ReleaseProviderResponse, ReleaseResponse } from "./releases-providers-types";
DetailsProviderResponse,
ReleaseProviderResponse,
ReleasesRepository,
ReleasesResponse,
} from "./releases-providers-types";
export const getLatestRelease = ( export const getLatestRelease = (
releases: ReleaseProviderResponse[], releases: ReleaseProviderResponse[],
repository: ReleasesRepository, versionRegex?: string,
details?: DetailsProviderResponse, ): ReleaseProviderResponse | null => {
): ReleasesResponse => {
const validReleases = releases.filter((result) => { const validReleases = releases.filter((result) => {
if (result.latestRelease) { if (result.latestRelease) {
return repository.versionRegex ? new RegExp(repository.versionRegex).test(result.latestRelease) : true; return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true;
} }
return true; return true;
}); });
const latest = return validReleases.length === 0
validReleases.length === 0 ? null
? ({ : validReleases.reduce((latest, current) => (current.latestReleaseAt > latest.latestReleaseAt ? current : latest));
id: repository.id,
error: { code: "noMatchingVersion" },
} as ReleasesResponse)
: validReleases.reduce(
(latest, result) => {
return {
...details,
...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest),
id: repository.id,
};
},
{
id: "",
latestRelease: "",
latestReleaseAt: new Date(0),
},
);
return latest;
}; };
export interface ReleasesProviderIntegration { export interface ReleasesProviderIntegration {
getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse>; getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse>;
} }
@@ -1,5 +1,11 @@
import type { TranslationObject } from "@homarr/translation"; import type { TranslationObject } from "@homarr/translation";
export interface ReleasesRepository extends Record<string, unknown> {
id: string;
identifier: string;
versionRegex?: string;
}
export interface DetailsProviderResponse { export interface DetailsProviderResponse {
projectUrl?: string; projectUrl?: string;
projectDescription?: string; projectDescription?: string;
@@ -19,35 +25,10 @@ export interface ReleaseProviderResponse {
isPreRelease?: boolean; isPreRelease?: boolean;
} }
export interface ReleasesRepository {
id: string;
identifier: string;
versionRegex?: string;
}
type ReleasesErrorKeys = keyof TranslationObject["widget"]["releases"]["error"]["messages"]; type ReleasesErrorKeys = keyof TranslationObject["widget"]["releases"]["error"]["messages"];
export interface ReleasesResponse { export type ReleaseData = DetailsProviderResponse & ReleaseProviderResponse;
id: string;
latestRelease?: string;
latestReleaseAt?: Date;
releaseUrl?: string;
releaseDescription?: string;
isPreRelease?: boolean;
projectUrl?: string;
projectDescription?: string;
isFork?: boolean;
isArchived?: boolean;
createdAt?: Date;
starsCount?: number;
openIssues?: number;
forksCount?: number;
error?: export type ReleaseError = { code: ReleasesErrorKeys } | { code: "unexpected"; message: string };
| {
code: ReleasesErrorKeys; export type ReleaseResponse = { success: true; data: ReleaseData } | { success: false; error: ReleaseError };
}
| {
message: string;
};
}
@@ -15,7 +15,7 @@ import { Integration } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service"; import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration"; import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types"; import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
import type { IMediaReleasesIntegration, MediaRelease } from "../types"; import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
@HandleIntegrationErrors([integrationAxiosHttpErrorHandler]) @HandleIntegrationErrors([integrationAxiosHttpErrorHandler])
export class JellyfinIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration { export class JellyfinIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
@@ -122,7 +122,7 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
return result.data.map((item) => ({ return result.data.map((item) => ({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: item.Id!, id: item.Id!,
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown", type: this.mapMediaReleaseType(item.Type),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
title: item.Name!, title: item.Name!,
subtitle: item.Taglines?.at(0), subtitle: item.Taglines?.at(0),
@@ -140,6 +140,27 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
})); }));
} }
private mapMediaReleaseType(type: BaseItemKind | undefined): MediaType {
switch (type) {
case "Audio":
case "AudioBook":
case "MusicAlbum":
return "music";
case "Book":
return "book";
case "Episode":
case "Series":
case "Season":
return "tv";
case "Movie":
return "movie";
case "Video":
return "video";
default:
return "unknown";
}
}
/** /**
* Constructs an ApiClient synchronously with an ApiKey or asynchronously * Constructs an ApiClient synchronously with an ApiKey or asynchronously
* with a username and password. * with a username and password.
@@ -6,7 +6,7 @@ import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error"; import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service"; import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types"; import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
import { releasesResponseSchema } from "./linuxserverio-schemas"; import { releasesResponseSchema } from "./linuxserverio-schemas";
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" }); const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
@@ -24,56 +24,44 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const [owner, name] = repository.identifier.split("/"); const [owner, name] = identifier.split("/");
if (!owner || !name) { if (!owner || !name) {
localLogger.warn( localLogger.warn(
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`, `Invalid identifier format. Expected 'owner/name', for ${identifier} with LinuxServerIO integration`,
{ { identifier },
identifier: repository.identifier,
},
); );
return { return null;
id: repository.id,
error: { code: "invalidIdentifier" },
};
} }
return { owner, name };
}
public async getLatestMatchingReleaseAsync(identifier: string): Promise<ReleaseResponse> {
const { name } = this.parseIdentifier(identifier) ?? {};
if (!name) return { success: false, error: { code: "invalidIdentifier" } };
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images")); const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images"));
if (!releasesResponse.ok) { if (!releasesResponse.ok) {
return { return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
id: repository.id,
error: { message: releasesResponse.statusText },
};
} }
const releasesResponseJson: unknown = await releasesResponse.json(); const releasesResponseJson: unknown = await releasesResponse.json();
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
if (!success) { if (!success) {
return { return { success: false, error: { code: "unexpected", message: error.message } };
id: repository.id, }
error: {
message: error.message,
},
};
} else {
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
if (!release) {
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
owner,
name,
});
return { const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
id: repository.id, if (!release) {
error: { code: "noReleasesFound" }, localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
}; name,
} });
return { success: false, error: { code: "noMatchingVersion" } };
}
return { return {
id: repository.id, success: true,
data: {
latestRelease: release.version, latestRelease: release.version,
latestReleaseAt: release.version_timestamp, latestReleaseAt: release.version_timestamp,
releaseDescription: release.changelog?.shift()?.desc, releaseDescription: release.changelog?.shift()?.desc,
@@ -82,7 +70,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
isArchived: release.deprecated, isArchived: release.deprecated,
createdAt: release.initial_date ? new Date(release.initial_date) : undefined, createdAt: release.initial_date ? new Date(release.initial_date) : undefined,
starsCount: release.stars, starsCount: release.stars,
}; },
} };
} }
} }
@@ -6,7 +6,7 @@ import { TestConnectionError } from "../base/test-connection/test-connection-err
import type { TestingResult } from "../base/test-connection/test-connection-service"; import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration"; import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types"; import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
import { releasesResponseSchema } from "./npm-schemas"; import { releasesResponseSchema } from "./npm-schemas";
export class NPMIntegration extends Integration implements ReleasesProviderIntegration { export class NPMIntegration extends Integration implements ReleasesProviderIntegration {
@@ -22,35 +22,35 @@ export class NPMIntegration extends Integration implements ReleasesProviderInteg
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const releasesResponse = await fetchWithTrustedCertificatesAsync( if (!identifier) return { success: false, error: { code: "invalidIdentifier" } };
this.url(`/${encodeURIComponent(repository.identifier)}`),
);
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url(`/${encodeURIComponent(identifier)}`));
if (!releasesResponse.ok) { if (!releasesResponse.ok) {
return { return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
id: repository.id,
error: { message: releasesResponse.statusText },
};
} }
const releasesResponseJson: unknown = await releasesResponse.json(); const releasesResponseJson: unknown = await releasesResponse.json();
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
if (!success) { if (!success) {
return { return {
id: repository.id, success: false,
error: { error: {
code: "unexpected",
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message, message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
}, },
}; };
} else {
const formattedReleases = data.time.map((tag) => ({
...tag,
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
}));
return getLatestRelease(formattedReleases, repository);
} }
const formattedReleases = data.time.map((tag) => ({
...tag,
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
}));
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
return { success: true, data: latestRelease };
} }
} }
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration"; import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
import type { import type {
ReleaseProviderResponse, ReleaseProviderResponse,
ReleasesRepository, ReleaseResponse,
ReleasesResponse,
} from "../interfaces/releases-providers/releases-providers-types"; } from "../interfaces/releases-providers/releases-providers-types";
import { releasesResponseSchema } from "./quay-schemas"; import { releasesResponseSchema } from "./quay-schemas";
@@ -43,20 +42,22 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
}; };
} }
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> { private parseIdentifier(identifier: string) {
const [owner, name] = repository.identifier.split("/"); const [owner, name] = identifier.split("/");
if (!owner || !name) { if (!owner || !name) {
localLogger.warn( localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Quay integration`, {
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`, identifier,
{ });
identifier: repository.identifier, return null;
},
);
return {
id: repository.id,
error: { code: "invalidIdentifier" },
};
} }
return { owner, name };
}
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
const parsedIdentifier = this.parseIdentifier(identifier);
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
const { owner, name } = parsedIdentifier;
const releasesResponse = await this.withHeadersAsync(async (headers) => { const releasesResponse = await this.withHeadersAsync(async (headers) => {
return await fetchWithTrustedCertificatesAsync( return await fetchWithTrustedCertificatesAsync(
@@ -68,42 +69,29 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
}, },
); );
}); });
if (!releasesResponse.ok) { if (!releasesResponse.ok) {
return { return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
id: repository.id,
error: { message: releasesResponse.statusText },
};
} }
const releasesResponseJson: unknown = await releasesResponse.json(); const releasesResponseJson: unknown = await releasesResponse.json();
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson); const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
if (!success) { if (!success) {
return { return { success: false, error: { code: "unexpected", message: error.message } };
id: repository.id,
error: {
message: error.message,
},
};
} else {
const details = {
projectDescription: data.description,
};
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
if (!tag.name || !tag.last_modified) return acc;
acc.push({
latestRelease: tag.name,
latestReleaseAt: new Date(tag.last_modified),
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
});
return acc;
}, []);
return getLatestRelease(releasesProviderResponse, repository, details);
} }
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
if (!tag.name || !tag.last_modified) return acc;
acc.push({
latestRelease: tag.name,
latestReleaseAt: new Date(tag.last_modified),
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
});
return acc;
}, []);
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
return { success: true, data: { projectDescription: data.description, ...latestRelease } };
} }
} }
+2 -2
View File
@@ -25,7 +25,7 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"superjson": "2.2.3", "superjson": "2.2.5",
"winston": "3.18.3", "winston": "3.18.3",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -33,7 +33,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+4 -4
View File
@@ -33,10 +33,10 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"zod": "^4.1.12" "zod": "^4.1.12"
@@ -45,7 +45,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
@@ -1,10 +1,10 @@
import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Button, CopyButton, Mark, Stack, Text } from "@mantine/core"; import { Button, CopyButton, Mark, Stack, Text } from "@mantine/core";
import type { RouterOutputs } from "@homarr/api"; import type { RouterOutputs } from "@homarr/api";
import { createModal } from "@homarr/modals"; import { createModal } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
export const InviteCopyModal = createModal<RouterOutputs["invite"]["createInvite"]>(({ actions, innerProps }) => { export const InviteCopyModal = createModal<RouterOutputs["invite"]["createInvite"]>(({ actions, innerProps }) => {
const t = useScopedI18n("management.page.user.invite"); const t = useScopedI18n("management.page.user.invite");
+3 -3
View File
@@ -24,15 +24,15 @@
"dependencies": { "dependencies": {
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@mantine/hooks": "^8.3.5", "@mantine/hooks": "^8.3.6",
"react": "19.2.0" "react": "19.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+2 -2
View File
@@ -24,14 +24,14 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/notifications": "^8.3.5", "@mantine/notifications": "^8.3.6",
"@tabler/icons-react": "^3.35.0" "@tabler/icons-react": "^3.35.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+5 -5
View File
@@ -37,13 +37,13 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@mantine/hooks": "^8.3.5", "@mantine/hooks": "^8.3.6",
"adm-zip": "0.5.16", "adm-zip": "0.5.16",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"superjson": "2.2.3", "superjson": "2.2.5",
"zod": "^4.1.12", "zod": "^4.1.12",
"zod-form-data": "^3.0.1" "zod-form-data": "^3.0.1"
}, },
@@ -52,7 +52,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/adm-zip": "0.5.7", "@types/adm-zip": "0.5.7",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+2 -2
View File
@@ -28,13 +28,13 @@
"@homarr/definitions": "workspace:^", "@homarr/definitions": "workspace:^",
"@homarr/log": "workspace:^", "@homarr/log": "workspace:^",
"ioredis": "5.8.2", "ioredis": "5.8.2",
"superjson": "2.2.3" "superjson": "2.2.5"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+4 -4
View File
@@ -29,9 +29,9 @@
"@homarr/integrations": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/redis": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"octokit": "^5.0.4", "octokit": "^5.0.5",
"superjson": "2.2.3", "superjson": "2.2.5",
"undici": "7.16.0", "undici": "7.16.0",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
@@ -39,7 +39,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+6 -23
View File
@@ -1,36 +1,19 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { IntegrationKindByCategory } from "@homarr/definitions"; import type { IntegrationKindByCategory } from "@homarr/definitions";
import { getIconUrl } from "@homarr/definitions"; import type { ReleaseResponse, ReleasesRepository } from "@homarr/integrations";
import type { ReleasesResponse } from "@homarr/integrations";
import { createIntegrationAsync } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations";
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
export const releasesRequestHandler = createCachedIntegrationRequestHandler< export const releasesRequestHandler = createCachedIntegrationRequestHandler<
ReleasesResponse, ReleaseResponse,
IntegrationKindByCategory<"releasesProvider">, IntegrationKindByCategory<"releasesProvider">,
{ ReleasesRepository
id: string;
identifier: string;
versionRegex?: string;
}
>({ >({
async requestAsync(integration, input) { requestAsync: async (integration, input) => {
const integrationInstance = await createIntegrationAsync(integration); const instance = await createIntegrationAsync(integration);
const response = await integrationInstance.getLatestMatchingReleaseAsync({ return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex);
id: input.id,
identifier: input.identifier,
versionRegex: input.versionRegex,
});
return {
...response,
integration: {
name: integration.name,
iconUrl: getIconUrl(integration.kind),
},
};
}, },
cacheDuration: dayjs.duration(5, "minutes"), cacheDuration: dayjs.duration(5, "minutes"),
queryKey: "repositoriesReleases", queryKey: "repositoriesReleases",
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+3 -3
View File
@@ -26,8 +26,8 @@
"@homarr/api": "workspace:^0.1.0", "@homarr/api": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.3.5", "@mantine/dates": "^8.3.6",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0" "react-dom": "19.2.0"
}, },
@@ -35,7 +35,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+6 -6
View File
@@ -33,12 +33,12 @@
"@homarr/settings": "workspace:^0.1.0", "@homarr/settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@mantine/hooks": "^8.3.5", "@mantine/hooks": "^8.3.6",
"@mantine/spotlight": "^8.3.5", "@mantine/spotlight": "^8.3.6",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"jotai": "^2.15.0", "jotai": "^2.15.1",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"use-deep-compare-effect": "^1.8.1" "use-deep-compare-effect": "^1.8.1"
@@ -47,7 +47,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
@@ -1,6 +1,7 @@
import Link from "next/link";
import { Spotlight } from "@mantine/spotlight"; import { Spotlight } from "@mantine/spotlight";
import { Link } from "@homarr/ui";
import type { inferSearchInteractionOptions } from "../../../lib/interaction"; import type { inferSearchInteractionOptions } from "../../../lib/interaction";
import classes from "./action-item.module.css"; import classes from "./action-item.module.css";
@@ -1,7 +1,7 @@
import Link from "next/link";
import { Spotlight } from "@mantine/spotlight"; import { Spotlight } from "@mantine/spotlight";
import type { TranslationObject } from "@homarr/translation"; import type { TranslationObject } from "@homarr/translation";
import { Link } from "@homarr/ui";
import type { SearchGroup } from "../../../lib/group"; import type { SearchGroup } from "../../../lib/group";
import type { inferSearchInteractionOptions } from "../../../lib/interaction"; import type { inferSearchInteractionOptions } from "../../../lib/interaction";
+3 -3
View File
@@ -29,10 +29,10 @@
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0",
"dayjs": "^1.11.18", "dayjs": "^1.11.19",
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "15.5.6", "next": "16.0.1",
"next-intl": "4.4.0", "next-intl": "4.4.0",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0" "react-dom": "19.2.0"
@@ -41,7 +41,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -30,12 +30,12 @@
"@homarr/log": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.5", "@mantine/core": "^8.3.6",
"@mantine/dates": "^8.3.5", "@mantine/dates": "^8.3.6",
"@mantine/hooks": "^8.3.5", "@mantine/hooks": "^8.3.6",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "15.5.6", "next": "16.0.1",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"svgson": "^5.3.1" "svgson": "^5.3.1"
@@ -45,7 +45,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/css-modules": "^1.0.5", "@types/css-modules": "^1.0.5",
"eslint": "^9.38.0", "eslint": "^9.39.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }
+1
View File
@@ -15,3 +15,4 @@ export { BetaBadge } from "./beta-badge";
export { MaskedImage } from "./masked-image"; export { MaskedImage } from "./masked-image";
export { MaskedOrNormalImage } from "./masked-or-normal-image"; export { MaskedOrNormalImage } from "./masked-or-normal-image";
export { LanguageIcon } from "./language-icon"; export { LanguageIcon } from "./language-icon";
export { Link } from "./link";
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more