Compare commits

...
164 Commits
Author SHA1 Message Date
Releases Homarr d8a2ab74d2 chore(release): 1.42.0 [skip ci] 2025-10-17 19:15:33 +00:00
homarr-releases[bot] 4dabc98bd4 chore(release): automatic release v1.42.0 2025-10-17 19:14:09 +00:00
homarr-update-contributors[bot] 84909b2535 chore: update contributors (#4300)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-10-17 12:35:12 +00:00
homarr-renovate[bot] f2540c7d8e chore(deps): update dependency @types/node to ^22.18.10 (#4289)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-16 14:26:31 +00:00
homarr-renovate[bot] 0e63ffe611 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.11 (#4298)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-16 13:00:11 +00:00
homarr-crowdin[bot] 0b120455fd chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-16 00:50:00 +00:00
homarr-renovate[bot] fde68f8bcc fix(deps): update dependency swagger-ui-react to ^5.29.4 (#4293)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-15 13:00:10 +00:00
homarr-renovate[bot] 38e368fdbc fix(deps): update tiptap monorepo to v2.26.3 (#4292)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-14 23:30:27 +00:00
homarr-renovate[bot] 94533c667c fix(deps): update mantine monorepo to ^8.3.4 (#4290)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-14 19:27:59 +00:00
homarr-renovate[bot] cd25bdfc0e chore(deps): update dependency @types/node to ^22.18.9 (#4288)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-14 18:42:56 +00:00
homarr-renovate[bot] 3a7c8d5e19 fix(deps): update dependency next-intl to v4.3.12 (#4274)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-14 17:27:31 +00:00
homarr-renovate[bot] 1afbdd0511 chore(deps): update pnpm to v10.18.2 (#4262)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-14 16:37:03 +00:00
homarr-crowdin[bot] 1fa60cbea6 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-14 00:48:41 +00:00
homarr-crowdin[bot] 3cbb664bc0 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-13 22:01:32 +00:00
Meier Lukas a80c5d40e0 fix(plex): tv show links not working (#4272) 2025-10-13 23:52:52 +02:00
Meier Lukas cccec5e195 fix(docker-widget): memory usage wrong (#4266) 2025-10-13 23:52:40 +02:00
Meier Lukas afca8007f1 feat(nextcloud): reuse color from event for indicator (#4270) 2025-10-13 23:52:27 +02:00
Meier Lukas 5c86930220 feat(users): allow profile picture change for external providers (#4275) 2025-10-13 23:52:17 +02:00
Meier Lukas 35a8ac820f fix(users): profile picture upload limit wrong (#4276) 2025-10-13 23:52:05 +02:00
homarr-renovate[bot] cd5d9c66ca fix(deps): update dependency @auth/drizzle-adapter to ^1.11.0 (#4283)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-13 18:41:36 +00:00
homarr-renovate[bot] 60370a7e33 fix(deps): update dependency @auth/core to ^0.41.0 (#4282)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-13 17:28:20 +00:00
homarr-renovate[bot] 65ccfdfb13 fix(deps): update dependency mysql2 to v3.15.2 (#4280)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-13 08:42:02 +00:00
homarr-renovate[bot] f792b2a553 fix(deps): update dependency next-intl to v4.3.11 (#4273)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-12 10:51:20 +00:00
homarr-renovate[bot] 5d402f88d2 fix(deps): update dependency next-intl to v4.3.10 (#4271)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-12 09:28:56 +00:00
homarr-renovate[bot] 7a7b3a32f8 chore(deps): update react monorepo (#4269)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-12 06:33:55 +00:00
homarr-renovate[bot] d80f070ba3 fix(deps): update dependency trpc-to-openapi to ^3.1.0 (#4268)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-12 02:01:30 +00:00
homarr-renovate[bot] d7182b9cf5 chore(deps): update dependency @types/react to v19.2.1 (#4236)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-12 02:00:11 +00:00
homarr-crowdin[bot] 6a199561b6 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-12 00:50:43 +00:00
Meier Lukas 7e5f4e59a9 fix(management): improve homepage (#4260) 2025-10-11 22:55:56 +02:00
Meier Lukas 1b7e67dd97 fix(calendar): recurring events not working (#4265) 2025-10-11 22:54:51 +02:00
homarr-renovate[bot] 0aede0dec3 fix(deps): update dependency typescript-eslint to ^8.46.0 (#4264)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-11 17:26:36 +00:00
homarr-renovate[bot] e8877169f8 fix(deps): update dependency zod to ^4.1.12 (#4263)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-11 17:26:06 +00:00
homarr-renovate[bot] 4ffbea3b52 fix(deps): update dependency ioredis to v5.8.1 (#4259)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-11 16:11:48 +00:00
homarr-renovate[bot] fab57e6fbd chore(deps): update pnpm to v10.18.1 (#4240)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-11 12:48:47 +00:00
homarr-renovate[bot] c2b2b098a1 fix(deps): update dependency @ctrl/qbittorrent to ^9.9.1 (#4229)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-11 01:52:48 +00:00
homarr-crowdin[bot] 4f74395000 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-11 00:46:35 +00:00
Meier Lukas 073ede3aad chore: update bug report template 2025-10-10 23:09:36 +02:00
Releases Homarr 40f9ce0530 chore(release): 1.41.0 [skip ci] 2025-10-10 19:15:36 +00:00
homarr-releases[bot] 92802a37db chore(release): automatic release v1.41.0 2025-10-10 19:14:15 +00:00
Meier Lukas 4c2e352aca feat(dynamic-sections): add custom css classes option (#4256) 2025-10-10 19:59:41 +02:00
Meier Lukas 3b708c5ebd feat(integrations): allow changing secret kinds of existing integration (#4254) 2025-10-10 19:59:23 +02:00
Meier Lukas 50a23d76e3 feat(weather-widget): add imperial windspeed option (#4253) 2025-10-10 19:59:05 +02:00
homarr-renovate[bot] 6d0a1452a6 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.10 (#4255)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-10 14:30:52 +00:00
homarr-update-contributors[bot] a9f73bc279 chore: update contributors (#4252)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-10-10 12:35:36 +00:00
homarr-renovate[bot] 76bc24b06a fix(deps): update dependency eslint-plugin-react-hooks to v6 (#4231)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-10 13:43:07 +02:00
homarr-crowdin[bot] 196a9b4b6d chore(lang): update translations from crowdin (#4250)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-10 13:41:03 +02:00
Meier Lukas eb39eea87f chore: update bug report template 2025-10-10 13:40:09 +02:00
homarr-renovate[bot] e0fe6d5b81 chore(deps): update dependency eslint to ^9.37.0 (#4249)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-09 12:24:54 +00:00
homarr-renovate[bot] 530335eecc chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.52.4 (#4244)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 18:42:31 +00:00
homarr-renovate[bot] c35c15c635 fix(deps): update dependency fast-xml-parser to ^5.3.0 (#4245)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 17:33:46 +00:00
homarr-renovate[bot] e7267548fe fix(deps): update dependency swagger-ui-react to ^5.29.3 (#4238)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 17:31:57 +00:00
homarr-renovate[bot] ec09fb70a9 fix(deps): update dependency @kubernetes/client-node to ^1.4.0 (#4247)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 16:38:39 +00:00
homarr-renovate[bot] 7e500f772d fix(deps): update testcontainers-node monorepo to ^11.7.1 (#4213)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 11:21:30 +00:00
homarr-renovate[bot] c9841dfb47 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.9 (#4235)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-08 01:55:12 +00:00
homarr-crowdin[bot] 6aa127e57a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-08 00:48:02 +00:00
homarr-crowdin[bot] bd6761cb75 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-07 20:57:52 +00:00
homarr-renovate[bot] 982a0c96a5 fix(deps): update mantine monorepo to ^8.3.3 (#4242)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 19:28:00 +00:00
homarr-renovate[bot] 0ff0b17c28 chore(deps): update pnpm to v10.18.0 (#4239)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 15:33:42 +00:00
homarr-renovate[bot] 055770c5e8 fix(deps): update dependency swagger-ui-react to ^5.29.2 (#4212)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 10:32:59 +00:00
homarr-renovate[bot] 3934252053 fix(deps): update dependency drizzle-orm to ^0.44.6 (#4237)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 10:32:37 +00:00
homarr-renovate[bot] 35172fa910 fix(deps): update react monorepo to v19.2.0 (#4230)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 07:35:22 +00:00
homarr-renovate[bot] 9e059a9632 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.8 (#4234)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-07 06:43:09 +00:00
homarr-renovate[bot] 82cd072753 chore(deps): update react monorepo (#4221)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-06 17:29:00 +00:00
homarr-renovate[bot] ee05137351 fix(deps): update dependency @ctrl/qbittorrent to ^9.9.0 (#4228)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-06 08:40:41 +00:00
homarr-renovate[bot] d9de9694c0 fix(deps): update dependency winston to v3.18.3 (#4222)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-06 07:33:09 +00:00
homarr-renovate[bot] 915a759d7e chore(deps): update dependency @types/node to ^22.18.8 (#4225)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-06 07:32:12 +00:00
homarr-renovate[bot] cf2977f821 fix(deps): update dependency @ctrl/deluge to ^7.4.0 (#4227)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-06 06:43:46 +00:00
homarr-crowdin[bot] c072e19150 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-06 00:49:37 +00:00
homarr-renovate[bot] 4da24060c9 chore(deps): update dependency typescript to ^5.9.3 (#4223)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 21:27:02 +00:00
homarr-renovate[bot] 84ace42aa5 fix(deps): update dependency winston to v3.18.1 (#4220)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 07:28:09 +00:00
homarr-renovate[bot] 040786353d chore(deps): update dependency @types/react to v19.1.16 (#4207)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 06:39:12 +00:00
homarr-renovate[bot] e1162c7907 fix(deps): update dependency winston to v3.18.0 (#4219)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 04:37:00 +00:00
homarr-renovate[bot] e827f6653f fix(deps): update dependency typescript-eslint to ^8.45.0 (#4215)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 04:36:38 +00:00
homarr-renovate[bot] 67184890ec chore(deps): update dependency cross-env to ^10.1.0 (#4216)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 03:24:19 +00:00
homarr-renovate[bot] 340f8f98e4 fix(deps): update dependency dockerode to ^4.0.9 (#4214)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 03:20:13 +00:00
homarr-renovate[bot] 5955ae6f8b fix(deps): update dependency @octokit/auth-app to ^8.1.1 (#4217)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 03:19:25 +00:00
homarr-renovate[bot] af448f0085 fix(deps): update dependency dotenv to ^17.2.3 (#4218)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-05 02:05:41 +00:00
homarr-crowdin[bot] b4cfc3bf8b chore(lang): update translations from crowdin (#4203)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-04 15:13:35 +02:00
homarr-renovate[bot] a21fbd26da fix(deps): update testcontainers-node monorepo to ^11.7.0 (#4211)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-04 08:36:05 +00:00
homarr-renovate[bot] eb7a3a31f0 fix(deps): update dependency swagger-ui-react to ^5.29.1 (#4210)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-04 08:34:49 +00:00
Releases Homarr 6ce712794a chore(release): 1.40.0 [skip ci] 2025-10-03 19:14:43 +00:00
homarr-releases[bot] aa67e109dd chore(release): automatic release v1.40.0 2025-10-03 19:13:40 +00:00
homarr-renovate[bot] 24c8dde969 chore(deps): update dependency @types/react to v19.1.15 (#4197)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-03 12:54:48 +00:00
homarr-update-contributors[bot] fdf48d6184 chore: update contributors (#4205)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-10-03 12:34:22 +00:00
Meier Lukas 6e93c3b608 feat(media-server): add stats for nerds (#4170) 2025-10-02 19:54:53 +02:00
Meier Lukas dcb845b609 feat(medias): support upload of multiple items (#4169) 2025-10-02 19:54:40 +02:00
Meier Lukas f82f343631 feat(about): add hotkeys list (#4165) 2025-10-02 19:54:30 +02:00
Meier Lukas b76003de9b feat(media-transcoding): add periodic live updates (#4166) 2025-10-02 19:54:20 +02:00
Meier Lukas 514034d184 feat(translations): add crowdin live support (#4177) 2025-10-02 19:54:08 +02:00
homarr-renovate[bot] 878f8f3d60 chore(deps): update dependency @vitejs/plugin-react to ^5.0.4 (#4202)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-02 14:31:47 +00:00
homarr-renovate[bot] 2540c94697 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.52.3 (#4200)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-02 07:29:42 +00:00
homarr-renovate[bot] 8a552b81d8 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.9 (#4199)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-02 01:54:52 +00:00
homarr-crowdin[bot] 0dd076e6c9 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-10-02 00:47:34 +00:00
homarr-renovate[bot] b81a62127e chore(deps): update dependency @types/react to v19.1.14 (#4196)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-01 18:41:19 +00:00
homarr-renovate[bot] 2ec0ffec5e fix(deps): update dependency drizzle-kit to ^0.31.5 (#4195)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-10-01 12:59:49 +00:00
homarr-renovate[bot] 82c31efc02 fix(deps): update dependency jotai to ^2.15.0 (#4192)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-30 16:37:46 +00:00
homarr-renovate[bot] b7b79d8722 fix(deps): update trpc monorepo to ^11.6.0 (#4188)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-30 01:54:38 +00:00
homarr-renovate[bot] 88651171ad chore(deps): update dependency node to v22.20.0 (#4183)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-29 22:21:10 +00:00
homarr-renovate[bot] 026d9b8418 fix(deps): update dependency mysql2 to v3.15.1 (#4185)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-29 18:42:03 +00:00
homarr-renovate[bot] 89a1cc84bc fix(deps): update turbo monorepo to ^2.5.8 (#4180)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-29 03:15:31 +00:00
homarr-renovate[bot] bd6655b585 chore(deps): update node.js to v22.20.0 (#4182)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-29 02:01:38 +00:00
homarr-crowdin[bot] faacdc03d4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-29 00:50:12 +00:00
homarr-renovate[bot] 6d3005fb70 fix(deps): update dependency sass to ^1.93.2 (#4171)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 22:21:55 +00:00
homarr-renovate[bot] 82ce369c04 fix(deps): update turbo monorepo to ^2.5.7 (#4176)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 18:39:39 +00:00
homarr-renovate[bot] 6932f3198a fix(deps): update mantine monorepo to ^8.3.2 (#4179)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 18:37:57 +00:00
homarr-renovate[bot] 333ef48351 fix(deps): update nextjs monorepo to v15.5.4 (#4178)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 17:26:25 +00:00
homarr-crowdin[bot]andMeier Lukas ca1b3383e8 chore(lang): updated translations from crowdin
* ci: add write permission to download translations

* chore(lang): update translations from crowdin

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-28 12:05:13 +00:00
homarr-renovate[bot] bb47f163d3 fix(deps): update dependency ioredis to v5.8.0 (#4174)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 08:57:22 +00:00
homarr-renovate[bot] 5f4734ef6b fix(deps): update tanstack-query monorepo to ^5.90.2 (#4163)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 08:34:23 +00:00
homarr-renovate[bot] 1346370ce3 fix(deps): update tiptap monorepo to v2.26.2 (#4173)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 07:27:23 +00:00
homarr-renovate[bot] 4da3aa43f3 fix(deps): update dependency better-sqlite3 to ^12.4.1 (#4162)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 02:06:07 +00:00
homarr-renovate[bot] 319aafe35d fix(deps): update dependency fastify to ^5.6.1 (#4167)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-28 02:05:49 +00:00
homarr-renovate[bot] 16dff3f982 fix(deps): update dependency typescript-eslint to ^8.44.1 (#4168)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 23:31:06 +00:00
homarr-renovate[bot] 2049b90c5b fix(deps): update dependency sass to ^1.93.1 (#4149)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 22:27:11 +00:00
homarr-renovate[bot] 4ebfb33f45 chore(deps): update pnpm to v10.17.1 (#4136)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 14:20:50 +00:00
homarr-renovate[bot] d42d81d03c chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.6 (#4164)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 10:45:33 +00:00
homarr-renovate[bot] e5dddc8444 fix(deps): update tanstack-query monorepo to ^5.90.1 (#4161)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 08:36:29 +00:00
homarr-renovate[bot] 0b797c1fd0 fix(deps): update dependency better-sqlite3 to ^12.3.0 (#4160)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-27 07:26:22 +00:00
Meier Lukas 2e56d9a6c7 chore: update bug report template 2025-09-26 23:40:02 +02:00
Releases Homarr 499bdf110a chore(release): 1.39.0 [skip ci] 2025-09-26 19:15:07 +00:00
homarr-releases[bot] 993a0d78ef chore(release): automatic release v1.39.0 2025-09-26 19:13:45 +00:00
Meier Lukas 5b32f1eb79 feat(weather): add periodic live updates (#4155) 2025-09-26 20:42:13 +02:00
castielwaverly 0b15561f20 fix: Recalculate time ago on re-render (#4107) 2025-09-26 20:30:56 +02:00
Meier Lukas 2443d7300b feat(calendar): add periodic live updates (#4154) 2025-09-26 20:03:34 +02:00
Meier Lukas 0a908de1e7 feat(calendar): add homeassistant support (#4152) 2025-09-26 17:34:33 +02:00
homarr-update-contributors[bot] e1a232b37a chore: update contributors (#4153)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-26 12:39:02 +00:00
Meier Lukas c8a6cdae81 fix(calendar): support multi-day events (#4151) 2025-09-25 21:55:20 +02:00
homarr-renovate[bot] f0a0b9bca1 fix(deps): update dependency zod to ^4.1.11 (#4128)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-25 19:27:40 +00:00
Meier Lukas 3aea77a64f chore(docs): update sitemap 2025-09-25 21:08:37 +02:00
Meier Lukas b7d1d4ceef fix(calendar): missing badge for sonarr series (#4150) 2025-09-25 20:19:21 +02:00
homarr-renovate[bot] aced8c77bb fix(deps): update dependency sass to ^1.93.0 (#4147)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-25 15:23:12 +00:00
homarr-renovate[bot] 717d4ccff2 chore(deps): update dependency semantic-release to ^24.2.9 (#4146)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 20:34:42 +00:00
homarr-renovate[bot] 441e1ceba2 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.52.2 (#4144)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 21:50:50 +02:00
homarr-renovate[bot] 9ad8c6dbf1 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.7 (#4140)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 21:49:15 +02:00
homarr-renovate[bot] c17e5e8774 chore(deps): update dependency eslint to ^9.36.0 (#4145)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 16:39:21 +00:00
homarr-renovate[bot] 0c577a6f11 fix(deps): update dependency zod-validation-error to ^4.0.2 (#4142)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 10:35:41 +00:00
homarr-renovate[bot] a5ec293dda chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.51.0 (#4112)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 09:33:50 +00:00
homarr-renovate[bot] 484f0bdca4 fix(deps): update dependency dns-caching to ^0.2.7 (#4137)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 11:21:20 +00:00
homarr-renovate[bot] 7e4bde03e4 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.8 (#4141)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 09:33:29 +00:00
homarr-renovate[bot] 2c3f7c02d6 chore(deps): update dependency @types/node to ^22.18.6 (#4127)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 05:30:14 +00:00
homarr-renovate[bot] 1892987976 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.6 (#4139)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 04:36:03 +00:00
homarr-renovate[bot] bd5065ba88 chore(deps): update dependency esbuild to ^0.25.10 (#4134)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 17:28:44 +00:00
homarr-renovate[bot] 9b63fb6df6 chore(deps): update dependency @vitejs/plugin-react to ^5.0.3 (#4129)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 17:25:57 +00:00
homarr-renovate[bot] 588f8a4faa chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.25.10 (#4135)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:42:37 +00:00
homarr-renovate[bot] cf892f517f fix(deps): update dependency mysql2 to v3.15.0 (#4125)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:25:37 +00:00
homarr-renovate[bot] 3eb2b3795b chore(deps): update pnpm to v10.17.0 (#4133)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:19:21 +00:00
homarr-renovate[bot] 2c3856a2a9 fix(deps): update dependency dns-caching to ^0.2.6 (#4131)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:14:26 +00:00
homarr-renovate[bot] 83df2ec58b fix(deps): update testcontainers-node monorepo to ^11.6.0 (#4130)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 10:34:51 +00:00
homarr-renovate[bot] 303067b0c8 fix(deps): update dependency zod to ^4.1.9 (#4094)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:29:44 +00:00
homarr-renovate[bot] 9b8ec975e4 chore(deps): update dependency @types/dockerode to ^3.3.44 (#4126)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:29:15 +00:00
homarr-renovate[bot] 92811db400 chore(deps): update dependency @types/node to ^22.18.5 (#4117)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:28:12 +00:00
homarr-renovate[bot] eb85804cff fix(deps): update dependency @ctrl/deluge to ^7.3.0 (#4121)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 16:38:14 +00:00
homarr-renovate[bot] 25e32e0010 chore(deps): update dependency tar-fs@&gt;&#x3d;3.0.0 &lt;3.0.9 to >=3.1.1 (#4124)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 16:34:31 +00:00
homarr-renovate[bot] 4af13a8d32 chore(deps): update dependency tar-fs@&gt;&#x3d;2.0.0 &lt;2.1.3 to >=3.1.1 (#4123)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 15:28:08 +00:00
homarr-renovate[bot] 11f29d8def fix(deps): update tanstack-query monorepo to ^5.89.0 (#4122)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 09:28:40 +00:00
homarr-renovate[bot] c0ed041bdf fix(deps): update dependency @ctrl/qbittorrent to ^9.8.0 (#4118)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 07:30:25 +00:00
homarr-renovate[bot] 2eb82108a8 fix(deps): update dependency @ctrl/transmission to ^7.4.0 (#4120)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 06:41:05 +00:00
homarr-renovate[bot] 034dcacb08 fix(deps): update dependency next-intl to v4.3.9 (#4096)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 21:26:27 +00:00
homarr-renovate[bot] 966e1ac81e chore(deps): update dependency @types/node to ^22.18.4 (#4116)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 21:25:25 +00:00
homarr-renovate[bot] bcc71e6eaa fix(deps): update dependency node-ical to ^0.21.0 (#4111)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 20:33:50 +00:00
homarr-renovate[bot] 61ae703014 fix(deps): update dependency @tabler/icons-react to ^3.35.0 (#4115)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 19:24:29 +00:00
homarr-renovate[bot] 7ea746bed1 fix(deps): update dependency typescript-eslint to ^8.44.0 (#4114)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 17:25:55 +00:00
homarr-renovate[bot] c0197d0e96 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.50.2 (#4110)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 07:27:51 +00:00
Meier Lukas 75f9ead209 chore: update bug report template 2025-09-19 21:34:27 +02:00
154 changed files with 11079 additions and 5464 deletions

No files matched your search

+4
View File
@@ -33,6 +33,10 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.41.0
- 1.40.0
- 1.39.0
- 1.38.0
- 1.37.0
- 1.36.1
- 1.36.0
@@ -6,7 +6,7 @@ on:
- cron: "0 0 * * *" # every day at midnight
permissions:
contents: read # required for code checkout
contents: write # required to push changes
jobs:
download-crowdin-translations:
@@ -64,7 +64,7 @@ jobs:
- uses: actions/setup-node@v5
if: env.SKIP_RELEASE == 'false'
with:
node-version: 22.19.0
node-version: 22.20.0
cache: "pnpm"
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
+1 -1
View File
@@ -1 +1 @@
22.19.0
22.20.0
+121
View File
@@ -1,3 +1,124 @@
## [1.42.0](https://github.com/homarr-labs/homarr/compare/v1.41.0...v1.42.0) (2025-10-17)
### Features
* **nextcloud:** reuse color from event for indicator ([#4270](https://github.com/homarr-labs/homarr/issues/4270)) ([afca800](https://github.com/homarr-labs/homarr/commit/afca8007f1919e0bb8cb17eefb9c41e3f28291cc))
* **users:** allow profile picture change for external providers ([#4275](https://github.com/homarr-labs/homarr/issues/4275)) ([5c86930](https://github.com/homarr-labs/homarr/commit/5c86930220e97888eeae1c5440572ba7ef2434be))
### Bug Fixes
* **calendar:** recurring events not working ([#4265](https://github.com/homarr-labs/homarr/issues/4265)) ([1b7e67d](https://github.com/homarr-labs/homarr/commit/1b7e67dd9760c159e525cd88face00e28a260d13))
* **deps:** update dependency @auth/core to ^0.41.0 ([#4282](https://github.com/homarr-labs/homarr/issues/4282)) ([60370a7](https://github.com/homarr-labs/homarr/commit/60370a7e33dc1957aa433cbd6a11ab69661606cc))
* **deps:** update dependency @auth/drizzle-adapter to ^1.11.0 ([#4283](https://github.com/homarr-labs/homarr/issues/4283)) ([cd5d9c6](https://github.com/homarr-labs/homarr/commit/cd5d9c66ca42687faa9aeeff3d4d74d21a472a92))
* **deps:** update dependency @ctrl/qbittorrent to ^9.9.1 ([#4229](https://github.com/homarr-labs/homarr/issues/4229)) ([c2b2b09](https://github.com/homarr-labs/homarr/commit/c2b2b098a1ae7df027265dfcd184c01be1831ffc))
* **deps:** update dependency ioredis to v5.8.1 ([#4259](https://github.com/homarr-labs/homarr/issues/4259)) ([4ffbea3](https://github.com/homarr-labs/homarr/commit/4ffbea3b521b45c3dd52595d4f6824d25a6b4a05))
* **deps:** update dependency mysql2 to v3.15.2 ([#4280](https://github.com/homarr-labs/homarr/issues/4280)) ([65ccfdf](https://github.com/homarr-labs/homarr/commit/65ccfdfb13e83e78e65585aaf15a07b9d9755697))
* **deps:** update dependency next-intl to v4.3.10 ([#4271](https://github.com/homarr-labs/homarr/issues/4271)) ([5d402f8](https://github.com/homarr-labs/homarr/commit/5d402f88d29892ddebfc7e9efc902be4c3bbeba1))
* **deps:** update dependency next-intl to v4.3.11 ([#4273](https://github.com/homarr-labs/homarr/issues/4273)) ([f792b2a](https://github.com/homarr-labs/homarr/commit/f792b2a553f7a5851db36fc6a7ec495e2662dbc0))
* **deps:** update dependency next-intl to v4.3.12 ([#4274](https://github.com/homarr-labs/homarr/issues/4274)) ([3a7c8d5](https://github.com/homarr-labs/homarr/commit/3a7c8d5e19c28a1e3b084e80eb52a3193a419d7f))
* **deps:** update dependency swagger-ui-react to ^5.29.4 ([#4293](https://github.com/homarr-labs/homarr/issues/4293)) ([fde68f8](https://github.com/homarr-labs/homarr/commit/fde68f8bcc75aefb9cdb15c0b3b5d12c18a54d35))
* **deps:** update dependency trpc-to-openapi to ^3.1.0 ([#4268](https://github.com/homarr-labs/homarr/issues/4268)) ([d80f070](https://github.com/homarr-labs/homarr/commit/d80f070ba3dca6bd8af10ee771f7cbd3d24dafa3))
* **deps:** update dependency typescript-eslint to ^8.46.0 ([#4264](https://github.com/homarr-labs/homarr/issues/4264)) ([0aede0d](https://github.com/homarr-labs/homarr/commit/0aede0dec38016f5df08ce42f4f2e9237aaecaa6))
* **deps:** update dependency zod to ^4.1.12 ([#4263](https://github.com/homarr-labs/homarr/issues/4263)) ([e887716](https://github.com/homarr-labs/homarr/commit/e8877169f88179fc9a23947c3f34478f851cb66a))
* **deps:** update mantine monorepo to ^8.3.4 ([#4290](https://github.com/homarr-labs/homarr/issues/4290)) ([94533c6](https://github.com/homarr-labs/homarr/commit/94533c667cd234fdfe8f064b3d4892ad902e74c7))
* **deps:** update tiptap monorepo to v2.26.3 ([#4292](https://github.com/homarr-labs/homarr/issues/4292)) ([38e368f](https://github.com/homarr-labs/homarr/commit/38e368fdbcf9aba54fbc960031742cb162a72b72))
* **docker-widget:** memory usage wrong ([#4266](https://github.com/homarr-labs/homarr/issues/4266)) ([cccec5e](https://github.com/homarr-labs/homarr/commit/cccec5e19538aa8df154281f14fa939414bc8190))
* **management:** improve homepage ([#4260](https://github.com/homarr-labs/homarr/issues/4260)) ([7e5f4e5](https://github.com/homarr-labs/homarr/commit/7e5f4e59a9edcb05dfa8a09919408e93e8d0313f))
* **plex:** tv show links not working ([#4272](https://github.com/homarr-labs/homarr/issues/4272)) ([a80c5d4](https://github.com/homarr-labs/homarr/commit/a80c5d40e0e944d40dfdfe8e902ee10872e26bc0))
* **users:** profile picture upload limit wrong ([#4276](https://github.com/homarr-labs/homarr/issues/4276)) ([35a8ac8](https://github.com/homarr-labs/homarr/commit/35a8ac820f084271bc7591834096226639aec0d4))
## [1.41.0](https://github.com/homarr-labs/homarr/compare/v1.40.0...v1.41.0) (2025-10-10)
### Features
* **dynamic-sections:** add custom css classes option ([#4256](https://github.com/homarr-labs/homarr/issues/4256)) ([4c2e352](https://github.com/homarr-labs/homarr/commit/4c2e352acac62434c840e407887f8fddfc8c426a))
* **integrations:** allow changing secret kinds of existing integration ([#4254](https://github.com/homarr-labs/homarr/issues/4254)) ([3b708c5](https://github.com/homarr-labs/homarr/commit/3b708c5ebdcfb3425cb0b777f72ff2aaea23386b))
* **weather-widget:** add imperial windspeed option ([#4253](https://github.com/homarr-labs/homarr/issues/4253)) ([50a23d7](https://github.com/homarr-labs/homarr/commit/50a23d76e3dc14f23a27125297b8901a6724d18a))
### Bug Fixes
* **deps:** update dependency @ctrl/deluge to ^7.4.0 ([#4227](https://github.com/homarr-labs/homarr/issues/4227)) ([cf2977f](https://github.com/homarr-labs/homarr/commit/cf2977f82189d9fa8b896012dd7730a641cc252b))
* **deps:** update dependency @ctrl/qbittorrent to ^9.9.0 ([#4228](https://github.com/homarr-labs/homarr/issues/4228)) ([ee05137](https://github.com/homarr-labs/homarr/commit/ee051373512fbfb5fb70d78ea45755b2cf370f6a))
* **deps:** update dependency @kubernetes/client-node to ^1.4.0 ([#4247](https://github.com/homarr-labs/homarr/issues/4247)) ([ec09fb7](https://github.com/homarr-labs/homarr/commit/ec09fb70a950cbef131352f60fdf45553c7fe99f))
* **deps:** update dependency @octokit/auth-app to ^8.1.1 ([#4217](https://github.com/homarr-labs/homarr/issues/4217)) ([5955ae6](https://github.com/homarr-labs/homarr/commit/5955ae6f8b3c90a7c9b17760650095676303192e))
* **deps:** update dependency dockerode to ^4.0.9 ([#4214](https://github.com/homarr-labs/homarr/issues/4214)) ([340f8f9](https://github.com/homarr-labs/homarr/commit/340f8f98e452c068c5d26973195ea676d2b3ee6e))
* **deps:** update dependency dotenv to ^17.2.3 ([#4218](https://github.com/homarr-labs/homarr/issues/4218)) ([af448f0](https://github.com/homarr-labs/homarr/commit/af448f008571562492748986bd7cb2651623130e))
* **deps:** update dependency drizzle-orm to ^0.44.6 ([#4237](https://github.com/homarr-labs/homarr/issues/4237)) ([3934252](https://github.com/homarr-labs/homarr/commit/3934252053792874c41a6bd545b20acd7ccef4e8))
* **deps:** update dependency eslint-plugin-react-hooks to v6 ([#4231](https://github.com/homarr-labs/homarr/issues/4231)) ([76bc24b](https://github.com/homarr-labs/homarr/commit/76bc24b06aeec71b53b9f3351b98c076621d69d9))
* **deps:** update dependency fast-xml-parser to ^5.3.0 ([#4245](https://github.com/homarr-labs/homarr/issues/4245)) ([c35c15c](https://github.com/homarr-labs/homarr/commit/c35c15c635f37d09071b9ccafcd532e1e2e9ccd4))
* **deps:** update dependency swagger-ui-react to ^5.29.1 ([#4210](https://github.com/homarr-labs/homarr/issues/4210)) ([eb7a3a3](https://github.com/homarr-labs/homarr/commit/eb7a3a31f0ddf135664f5609c39b9d8ee72ef432))
* **deps:** update dependency swagger-ui-react to ^5.29.2 ([#4212](https://github.com/homarr-labs/homarr/issues/4212)) ([055770c](https://github.com/homarr-labs/homarr/commit/055770c5e8e43dfc035b2a25d650383df865c118))
* **deps:** update dependency swagger-ui-react to ^5.29.3 ([#4238](https://github.com/homarr-labs/homarr/issues/4238)) ([e726754](https://github.com/homarr-labs/homarr/commit/e7267548fe132d6b32e3ba8379ebe31fa8f16635))
* **deps:** update dependency typescript-eslint to ^8.45.0 ([#4215](https://github.com/homarr-labs/homarr/issues/4215)) ([e827f66](https://github.com/homarr-labs/homarr/commit/e827f6653f1caabaf4da7d477aa59849e9d023b9))
* **deps:** update dependency winston to v3.18.0 ([#4219](https://github.com/homarr-labs/homarr/issues/4219)) ([e1162c7](https://github.com/homarr-labs/homarr/commit/e1162c7907b3285f1e8bb9980340ca758786715d))
* **deps:** update dependency winston to v3.18.1 ([#4220](https://github.com/homarr-labs/homarr/issues/4220)) ([84ace42](https://github.com/homarr-labs/homarr/commit/84ace42aa5eb5f3f8e3351e64b1e1ba6fe747544))
* **deps:** update dependency winston to v3.18.3 ([#4222](https://github.com/homarr-labs/homarr/issues/4222)) ([d9de969](https://github.com/homarr-labs/homarr/commit/d9de9694c0d7bbb630d40e64c01dee082856ce0f))
* **deps:** update mantine monorepo to ^8.3.3 ([#4242](https://github.com/homarr-labs/homarr/issues/4242)) ([982a0c9](https://github.com/homarr-labs/homarr/commit/982a0c96a5d40a532d4fa06640f0d35e8125e1f2))
* **deps:** update react monorepo to v19.2.0 ([#4230](https://github.com/homarr-labs/homarr/issues/4230)) ([35172fa](https://github.com/homarr-labs/homarr/commit/35172fa91007487c6da3f6e0709dadeab20621c6))
* **deps:** update testcontainers-node monorepo to ^11.7.0 ([#4211](https://github.com/homarr-labs/homarr/issues/4211)) ([a21fbd2](https://github.com/homarr-labs/homarr/commit/a21fbd26dad82427e62a5a6b18f72dc32c00e608))
* **deps:** update testcontainers-node monorepo to ^11.7.1 ([#4213](https://github.com/homarr-labs/homarr/issues/4213)) ([7e500f7](https://github.com/homarr-labs/homarr/commit/7e500f772d769b30354a73d73e2fee6382fb8811))
## [1.40.0](https://github.com/homarr-labs/homarr/compare/v1.39.0...v1.40.0) (2025-10-03)
### Features
* **about:** add hotkeys list ([#4165](https://github.com/homarr-labs/homarr/issues/4165)) ([f82f343](https://github.com/homarr-labs/homarr/commit/f82f3436313fa2e2ccf04ff0e7886bf4efffd78e))
* **media-server:** add stats for nerds ([#4170](https://github.com/homarr-labs/homarr/issues/4170)) ([6e93c3b](https://github.com/homarr-labs/homarr/commit/6e93c3b60813671f027062d55b2f16d2c8445475))
* **media-transcoding:** add periodic live updates ([#4166](https://github.com/homarr-labs/homarr/issues/4166)) ([b76003d](https://github.com/homarr-labs/homarr/commit/b76003de9bfe9c06d829e8da0faf77c688c31e2d))
* **medias:** support upload of multiple items ([#4169](https://github.com/homarr-labs/homarr/issues/4169)) ([dcb845b](https://github.com/homarr-labs/homarr/commit/dcb845b609354b0423f5b7a9d08862b88fd90fd9))
* **translations:** add crowdin live support ([#4177](https://github.com/homarr-labs/homarr/issues/4177)) ([514034d](https://github.com/homarr-labs/homarr/commit/514034d184d1654518295e847da3183cc7da2c91))
### Bug Fixes
* **deps:** update dependency better-sqlite3 to ^12.3.0 ([#4160](https://github.com/homarr-labs/homarr/issues/4160)) ([0b797c1](https://github.com/homarr-labs/homarr/commit/0b797c1fd0d338c85553e800ca11699d3e3a4c7d))
* **deps:** update dependency better-sqlite3 to ^12.4.1 ([#4162](https://github.com/homarr-labs/homarr/issues/4162)) ([4da3aa4](https://github.com/homarr-labs/homarr/commit/4da3aa43f35cc51a117b51e4bc3827837696cc21))
* **deps:** update dependency drizzle-kit to ^0.31.5 ([#4195](https://github.com/homarr-labs/homarr/issues/4195)) ([2ec0ffe](https://github.com/homarr-labs/homarr/commit/2ec0ffec5e50d2b931d9b65cf81fd141ebfd531f))
* **deps:** update dependency fastify to ^5.6.1 ([#4167](https://github.com/homarr-labs/homarr/issues/4167)) ([319aafe](https://github.com/homarr-labs/homarr/commit/319aafe35dd60ce44aa6aa65008caf1ede17af5e))
* **deps:** update dependency ioredis to v5.8.0 ([#4174](https://github.com/homarr-labs/homarr/issues/4174)) ([bb47f16](https://github.com/homarr-labs/homarr/commit/bb47f163d3ef0b1082d5af72a0906787e52ee154))
* **deps:** update dependency jotai to ^2.15.0 ([#4192](https://github.com/homarr-labs/homarr/issues/4192)) ([82c31ef](https://github.com/homarr-labs/homarr/commit/82c31efc02e8f7b3793c2ef115656f85ef97c9ba))
* **deps:** update dependency mysql2 to v3.15.1 ([#4185](https://github.com/homarr-labs/homarr/issues/4185)) ([026d9b8](https://github.com/homarr-labs/homarr/commit/026d9b841853657e4284c6c703b340a5dc0a9eab))
* **deps:** update dependency sass to ^1.93.1 ([#4149](https://github.com/homarr-labs/homarr/issues/4149)) ([2049b90](https://github.com/homarr-labs/homarr/commit/2049b90c5bc4043c73f681c9c805629d2ddb570e))
* **deps:** update dependency sass to ^1.93.2 ([#4171](https://github.com/homarr-labs/homarr/issues/4171)) ([6d3005f](https://github.com/homarr-labs/homarr/commit/6d3005fb70cdfe882cf36ee77ef75492ec902c1c))
* **deps:** update dependency typescript-eslint to ^8.44.1 ([#4168](https://github.com/homarr-labs/homarr/issues/4168)) ([16dff3f](https://github.com/homarr-labs/homarr/commit/16dff3f982dc758e9f698a4ec4472180032366a2))
* **deps:** update mantine monorepo to ^8.3.2 ([#4179](https://github.com/homarr-labs/homarr/issues/4179)) ([6932f31](https://github.com/homarr-labs/homarr/commit/6932f3198a3534a92a9b56efd0f9e59f9fb68a14))
* **deps:** update nextjs monorepo to v15.5.4 ([#4178](https://github.com/homarr-labs/homarr/issues/4178)) ([333ef48](https://github.com/homarr-labs/homarr/commit/333ef4835100e7017d909f2ce3323ac82b47f4e2))
* **deps:** update tanstack-query monorepo to ^5.90.1 ([#4161](https://github.com/homarr-labs/homarr/issues/4161)) ([e5dddc8](https://github.com/homarr-labs/homarr/commit/e5dddc84446dcbc7831d74a2285dce946e1d5524))
* **deps:** update tanstack-query monorepo to ^5.90.2 ([#4163](https://github.com/homarr-labs/homarr/issues/4163)) ([5f4734e](https://github.com/homarr-labs/homarr/commit/5f4734ef6ba83cedf16031e2ac6c02dd3586c9a1))
* **deps:** update tiptap monorepo to v2.26.2 ([#4173](https://github.com/homarr-labs/homarr/issues/4173)) ([1346370](https://github.com/homarr-labs/homarr/commit/1346370ce39e23fa6d431b0d3f5d064eb9eb210f))
* **deps:** update trpc monorepo to ^11.6.0 ([#4188](https://github.com/homarr-labs/homarr/issues/4188)) ([b7b79d8](https://github.com/homarr-labs/homarr/commit/b7b79d8722f50af293c971fbc3547eb7487ecaf1))
* **deps:** update turbo monorepo to ^2.5.7 ([#4176](https://github.com/homarr-labs/homarr/issues/4176)) ([82ce369](https://github.com/homarr-labs/homarr/commit/82ce369c0413fbb20db814ee77dfb6e13184655b))
* **deps:** update turbo monorepo to ^2.5.8 ([#4180](https://github.com/homarr-labs/homarr/issues/4180)) ([89a1cc8](https://github.com/homarr-labs/homarr/commit/89a1cc84bc9411c68100b6d947b6ae033237403e))
## [1.39.0](https://github.com/homarr-labs/homarr/compare/v1.38.0...v1.39.0) (2025-09-26)
### Features
* **calendar:** add homeassistant support ([#4152](https://github.com/homarr-labs/homarr/issues/4152)) ([0a908de](https://github.com/homarr-labs/homarr/commit/0a908de1e7768d4524170297e5d20bb1e786d824))
* **calendar:** add periodic live updates ([#4154](https://github.com/homarr-labs/homarr/issues/4154)) ([2443d73](https://github.com/homarr-labs/homarr/commit/2443d7300b958edabceb95ab8663b9bebb30e4d0))
* **weather:** add periodic live updates ([#4155](https://github.com/homarr-labs/homarr/issues/4155)) ([5b32f1e](https://github.com/homarr-labs/homarr/commit/5b32f1eb79d132291992e102f1f2fc72805742a9))
### Bug Fixes
* **calendar:** missing badge for sonarr series ([#4150](https://github.com/homarr-labs/homarr/issues/4150)) ([b7d1d4c](https://github.com/homarr-labs/homarr/commit/b7d1d4ceef110761ede7e2c7e54e86945d6b4fb0))
* **calendar:** support multi-day events ([#4151](https://github.com/homarr-labs/homarr/issues/4151)) ([c8a6cda](https://github.com/homarr-labs/homarr/commit/c8a6cdae8194bad1bfcadfcb06853ab56437b6d4))
* **deps:** update dependency @ctrl/deluge to ^7.3.0 ([#4121](https://github.com/homarr-labs/homarr/issues/4121)) ([eb85804](https://github.com/homarr-labs/homarr/commit/eb85804cff5c5b61697ee270411e75735fc0c32f))
* **deps:** update dependency @ctrl/qbittorrent to ^9.8.0 ([#4118](https://github.com/homarr-labs/homarr/issues/4118)) ([c0ed041](https://github.com/homarr-labs/homarr/commit/c0ed041bdfb3bbb246cdc048f7f4d27818e21348))
* **deps:** update dependency @ctrl/transmission to ^7.4.0 ([#4120](https://github.com/homarr-labs/homarr/issues/4120)) ([2eb8210](https://github.com/homarr-labs/homarr/commit/2eb82108a8d1447652eed840fab0757ff0168c77))
* **deps:** update dependency @tabler/icons-react to ^3.35.0 ([#4115](https://github.com/homarr-labs/homarr/issues/4115)) ([61ae703](https://github.com/homarr-labs/homarr/commit/61ae7030145bee4a4d20afdd176f3e8486668c5f))
* **deps:** update dependency dns-caching to ^0.2.6 ([#4131](https://github.com/homarr-labs/homarr/issues/4131)) ([2c3856a](https://github.com/homarr-labs/homarr/commit/2c3856a2a91c7566f951ec5238b6c4c5c0a26f40))
* **deps:** update dependency dns-caching to ^0.2.7 ([#4137](https://github.com/homarr-labs/homarr/issues/4137)) ([484f0bd](https://github.com/homarr-labs/homarr/commit/484f0bdca45f949999f974d4d119e656d6f7206e))
* **deps:** update dependency mysql2 to v3.15.0 ([#4125](https://github.com/homarr-labs/homarr/issues/4125)) ([cf892f5](https://github.com/homarr-labs/homarr/commit/cf892f517f9b5e06420541650599ec977942387f))
* **deps:** update dependency next-intl to v4.3.9 ([#4096](https://github.com/homarr-labs/homarr/issues/4096)) ([034dcac](https://github.com/homarr-labs/homarr/commit/034dcacb0818ed9daa1e29474ca0f6e39a5c8198))
* **deps:** update dependency node-ical to ^0.21.0 ([#4111](https://github.com/homarr-labs/homarr/issues/4111)) ([bcc71e6](https://github.com/homarr-labs/homarr/commit/bcc71e6eaa0dedd3a5609ef5384219afe01ea13a))
* **deps:** update dependency sass to ^1.93.0 ([#4147](https://github.com/homarr-labs/homarr/issues/4147)) ([aced8c7](https://github.com/homarr-labs/homarr/commit/aced8c77bb33cc3df2d9db3f5c3513632d8e51c2))
* **deps:** update dependency typescript-eslint to ^8.44.0 ([#4114](https://github.com/homarr-labs/homarr/issues/4114)) ([7ea746b](https://github.com/homarr-labs/homarr/commit/7ea746bed12ff66b84ffd993268e47db18c40a88))
* **deps:** update dependency zod to ^4.1.11 ([#4128](https://github.com/homarr-labs/homarr/issues/4128)) ([f0a0b9b](https://github.com/homarr-labs/homarr/commit/f0a0b9bca197cd970b5ad279e50f05806fe51809))
* **deps:** update dependency zod to ^4.1.9 ([#4094](https://github.com/homarr-labs/homarr/issues/4094)) ([303067b](https://github.com/homarr-labs/homarr/commit/303067b0c8fdc92647fbaa574f29a633c63c40d3))
* **deps:** update dependency zod-validation-error to ^4.0.2 ([#4142](https://github.com/homarr-labs/homarr/issues/4142)) ([0c577a6](https://github.com/homarr-labs/homarr/commit/0c577a6f11990b94dc524c6e41ab624e4fdf5c9b))
* **deps:** update tanstack-query monorepo to ^5.89.0 ([#4122](https://github.com/homarr-labs/homarr/issues/4122)) ([11f29d8](https://github.com/homarr-labs/homarr/commit/11f29d8def97286cc5f4b2aa45a345a79ab3630b))
* **deps:** update testcontainers-node monorepo to ^11.6.0 ([#4130](https://github.com/homarr-labs/homarr/issues/4130)) ([83df2ec](https://github.com/homarr-labs/homarr/commit/83df2ec58b3d6bb78660a3ec79690b51a1cd395c))
* Recalculate time ago on re-render ([#4107](https://github.com/homarr-labs/homarr/issues/4107)) ([0b15561](https://github.com/homarr-labs/homarr/commit/0b15561f20abe94908ee62f99a33ff3f030c49e2))
## [1.38.0](https://github.com/homarr-labs/homarr/compare/v1.37.0...v1.38.0) (2025-09-19)
### Features
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:22.19.0-alpine AS base
FROM node:22.20.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
+27 -27
View File
@@ -50,59 +50,59 @@
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.3.1",
"@mantine/core": "^8.3.1",
"@mantine/dropzone": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/modals": "^8.3.1",
"@mantine/tiptap": "^8.3.1",
"@mantine/colors-generator": "^8.3.4",
"@mantine/core": "^8.3.4",
"@mantine/dropzone": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/modals": "^8.3.4",
"@mantine/tiptap": "^8.3.4",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.34.1",
"@tanstack/react-query": "^5.87.4",
"@tanstack/react-query-devtools": "^5.87.4",
"@tanstack/react-query-next-experimental": "^5.87.4",
"@trpc/client": "^11.5.1",
"@trpc/next": "^11.5.1",
"@trpc/react-query": "^11.5.1",
"@trpc/server": "^11.5.1",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-query-next-experimental": "^5.90.2",
"@trpc/client": "^11.6.0",
"@trpc/next": "^11.6.0",
"@trpc/react-query": "^11.6.0",
"@trpc/server": "^11.6.0",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0",
"chroma-js": "^3.1.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"dotenv": "^17.2.2",
"dotenv": "^17.2.3",
"flag-icons": "^7.5.0",
"glob": "^11.0.3",
"jotai": "^2.14.0",
"jotai": "^2.15.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.5.3",
"next": "15.5.4",
"postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0",
"react": "19.1.1",
"react-dom": "19.1.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-error-boundary": "^6.0.0",
"react-simple-code-editor": "^0.14.1",
"sass": "^1.92.1",
"sass": "^1.93.2",
"superjson": "2.2.2",
"swagger-ui-react": "^5.29.0",
"swagger-ui-react": "^5.29.4",
"use-deep-compare-effect": "^1.8.1",
"zod": "^4.1.8"
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1",
"@types/node": "^22.18.3",
"@types/node": "^22.18.10",
"@types/prismjs": "^1.26.5",
"@types/react": "19.1.13",
"@types/react-dom": "19.1.9",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.1",
"@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.2.1",
"eslint": "^9.35.0",
"eslint": "^9.37.0",
"node-loader": "^2.1.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
@@ -23,6 +23,7 @@ import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { env } from "@homarr/common/env";
import { hotkeys } from "@homarr/definitions";
import { useConfirmModal, useModalAction } from "@homarr/modals";
import { AppSelectModal } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
@@ -165,7 +166,7 @@ const EditModeMenu = () => {
open();
}, [board, isEditMode, saveBoard, open]);
useHotkeys([["mod+e", toggle]]);
useHotkeys([[hotkeys.toggleBoardEdit, toggle]]);
usePreventLeaveWithDirty(isEditMode);
return (
@@ -120,11 +120,14 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
/>
{session?.user.permissions.includes("media-upload") && (
<UploadMedia
onSuccess={({ url }) =>
onSuccess={(medias) => {
const first = medias.at(0);
if (!first) return;
startTransition(() => {
form.setFieldValue("backgroundImageUrl", url);
})
}
form.setFieldValue("backgroundImageUrl", first.url);
});
}}
>
{({ onClick, loading }) => (
<ActionIcon onClick={onClick} loading={loading} mt={24} size={36} variant="default">
+5 -1
View File
@@ -23,6 +23,7 @@ import type { SupportedLanguage } from "@homarr/translation";
import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
import { Analytics } from "~/components/layout/analytics";
import { CrowdinLiveTranslation } from "~/components/layout/crowdin-live-translation";
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
import { DayJsLoader } from "./_client-providers/dayjs-loader";
@@ -118,10 +119,12 @@ export default async function Layout(props: {
(innerProps) => <SpotlightProvider {...innerProps} />,
]);
const { locale } = await props.params;
return (
// Instead of ColorSchemScript we use data-mantine-color-scheme to prevent flickering
<html
lang={(await props.params).locale}
lang={locale}
dir={direction}
data-mantine-color-scheme={colorScheme}
style={{
@@ -132,6 +135,7 @@ export default async function Layout(props: {
<head>
<Analytics />
<SearchEngineOptimization />
<CrowdinLiveTranslation locale={locale} />
</head>
<body className={["font-sans", fontSans.variable].join(" ")}>
<StackedProvider>
@@ -2,6 +2,7 @@ import { Box, Grid, GridCol, Group, Image, Stack, Title } from "@mantine/core";
import { splitToNChunks } from "@homarr/common";
import { integrationDefs } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import classes from "./hero-banner.module.css";
@@ -12,19 +13,20 @@ const icons = Object.values(integrationDefs)
const countIconGroups = 3;
const animationDurationInSeconds = icons.length;
const arrayInChunks = splitToNChunks(icons, countIconGroups);
const gridSpan = 12 / countIconGroups;
export const HeroBanner = () => {
const gridSpan = 12 / countIconGroups;
export const HeroBanner = async () => {
const t = await getScopedI18n("management.page.home");
return (
<Box className={classes.bannerContainer} p={{ base: "lg", md: "3rem" }} bg="dark.6" pos="relative">
<Stack gap={0}>
<Title fz={{ base: "h4", md: "h2" }} c="dimmed">
Welcome back to your
{t("heroBanner.title")}
</Title>
<Group gap="xs" wrap="nowrap">
<Image src="/logo/logo.png" w={{ base: 32, md: 40 }} h={{ base: 32, md: 40 }} />
<Title fz={{ base: "h3", md: "h1" }}>Homarr Board</Title>
<Title fz={{ base: "h3", md: "h1" }}>{t("heroBanner.subtitle", { app: "Homarr" })}</Title>
</Group>
</Stack>
<Box visibleFrom="md" className={classes.scrollContainer} w={"30%"} top={0} right={0} pos="absolute">
@@ -11,14 +11,23 @@ import {
Center,
Flex,
Group,
Kbd,
List,
ListItem,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from "@mantine/core";
import { IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react";
import { IconKeyboard, IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react";
import { capitalize, objectEntries } from "@homarr/common";
import { hotkeys } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
@@ -149,6 +158,45 @@ export default async function AboutPage() {
</List>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="hotkeys">
<AccordionControl icon={<IconKeyboard size="1rem" />}>
<Stack gap={0}>
<Text>{t("accordion.hotkeys.title")}</Text>
<Text size="sm" c="dimmed">
{t("accordion.hotkeys.subtitle")}
</Text>
</Stack>
</AccordionControl>
<AccordionPanel>
<Table>
<TableThead>
<TableTr>
<TableTh>{t("accordion.hotkeys.field.shortcut")}</TableTh>
<TableTh>{t("accordion.hotkeys.field.action")}</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{objectEntries(hotkeys).map(([key, shortcut]) => (
<TableTr key={key}>
<TableTd>
<Kbd size="md">
{shortcut
.split("+")
.map((key) => capitalize(key.trim()))
.join(" + ")}
</Kbd>
</TableTd>
<TableTd>{t(`accordion.hotkeys.action.${key}`)}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
<Text size="sm" c="dimmed">
{t("accordion.hotkeys.note")}
</Text>
</AccordionPanel>
</AccordionItem>
</Accordion>
</div>
);
@@ -0,0 +1,62 @@
import { useCallback } from "react";
import { SegmentedControl } from "@mantine/core";
import type { IntegrationSecretKind } from "@homarr/definitions";
import type { UseFormReturnType } from "@homarr/form";
import { useScopedI18n } from "@homarr/translation/client";
interface FormType {
secrets: { kind: IntegrationSecretKind; value: string | null }[];
}
interface SecretKindsSegmentedControlProps<TFormType extends FormType> {
defaultKinds?: IntegrationSecretKind[];
secretKinds: IntegrationSecretKind[][];
form: UseFormReturnType<TFormType, (values: TFormType) => TFormType>;
}
export const SecretKindsSegmentedControl = <TFormType extends FormType>({
defaultKinds,
secretKinds,
form,
}: SecretKindsSegmentedControlProps<TFormType>) => {
const t = useScopedI18n("integration.secrets");
const defaultValue = defaultKinds?.length === 0 ? "empty" : defaultKinds?.join("-");
const secretKindGroups = secretKinds.map((kinds) => ({
label:
kinds.length === 0
? t("noSecretsRequired.segmentTitle")
: kinds.map((kind) => t(`kind.${kind}.label`)).join(" & "),
value: kinds.length === 0 ? "empty" : kinds.join("-"),
}));
const onChange = useCallback(
(value: string) => {
if (value === "empty") {
const emptyValues = [] satisfies FormType["secrets"];
// @ts-expect-error somehow it is not able to understand that secrets is an array?
form.setFieldValue("secrets", emptyValues);
return;
}
const kinds = value.split("-") as IntegrationSecretKind[];
const secrets = kinds.map((kind) => ({
kind,
value: "",
})) satisfies FormType["secrets"];
// @ts-expect-error somehow it is not able to understand that secrets is an array?
form.setFieldValue("secrets", secrets);
},
[form],
);
return (
<SegmentedControl
fullWidth
data={secretKindGroups}
defaultValue={defaultValue}
onChange={onChange}
></SegmentedControl>
);
};
@@ -3,7 +3,8 @@
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
import { Alert, Button, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
import type { z } from "zod/v4";
import type { RouterOutputs } from "@homarr/api";
@@ -18,6 +19,7 @@ import { integrationUpdateSchema } from "@homarr/validation/integration";
import { SecretCard } from "../../_components/secrets/integration-secret-card";
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
import { SecretKindsSegmentedControl } from "../../_components/secrets/integration-secret-segmented-control";
import { IntegrationTestConnectionError } from "../../_components/test-connection/integration-test-connection-error";
import type { AnyMappedTestConnectionError } from "../../_components/test-connection/types";
@@ -28,19 +30,21 @@ interface EditIntegrationForm {
export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
const t = useI18n();
const { openConfirmModal } = useConfirmModal();
const secretsKinds =
const allSecretKinds = getAllSecretKindOptions(integration.kind);
const initialSecretsKinds =
getAllSecretKindOptions(integration.kind).find((secretKinds) =>
integration.secrets.every((secret) => secretKinds.includes(secret.kind)),
) ?? getDefaultSecretKinds(integration.kind);
const hasUrlSecret = secretsKinds.includes("url");
const hasUrlSecret = initialSecretsKinds.includes("url");
const router = useRouter();
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
initialValues: {
name: integration.name,
url: integration.url,
secrets: secretsKinds.map((kind) => ({
secrets: initialSecretsKinds.map((kind) => ({
kind,
value: integration.secrets.find((secret) => secret.kind === kind)?.value ?? "",
})),
@@ -93,6 +97,10 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
);
};
const isInitialSecretKinds =
initialSecretsKinds.every((kind) => form.values.secrets.some((secret) => secret.kind === kind)) &&
form.values.secrets.length === initialSecretsKinds.length;
return (
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
<Stack>
@@ -104,36 +112,60 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
<Fieldset legend={t("integration.secrets.title")}>
<Stack gap="sm">
{secretsKinds.map((kind, index) => (
<SecretCard
key={kind}
secret={secretsMap.get(kind) ?? { kind, value: null, updatedAt: null }}
onCancel={() =>
new Promise((resolve) => {
// When nothing changed, just close the secret card
if ((form.values.secrets[index]?.value ?? "") === (secretsMap.get(kind)?.value ?? "")) {
return resolve(true);
{allSecretKinds.length > 1 && (
<SecretKindsSegmentedControl
defaultKinds={initialSecretsKinds}
secretKinds={allSecretKinds}
form={form}
/>
)}
{!isInitialSecretKinds
? null
: form.values.secrets.map((secret, index) => (
<SecretCard
key={secret.kind}
secret={secretsMap.get(secret.kind) ?? { kind: secret.kind, value: null, updatedAt: null }}
onCancel={() =>
new Promise((resolve) => {
// When nothing changed, just close the secret card
if ((secret.value ?? "") === (secretsMap.get(secret.kind)?.value ?? "")) {
return resolve(true);
}
openConfirmModal({
title: t("integration.secrets.reset.title"),
children: t("integration.secrets.reset.message"),
onCancel: () => resolve(false),
onConfirm: () => {
form.setFieldValue(`secrets.${index}.value`, secretsMap.get(secret.kind)?.value ?? "");
resolve(true);
},
});
})
}
openConfirmModal({
title: t("integration.secrets.reset.title"),
children: t("integration.secrets.reset.message"),
onCancel: () => resolve(false),
onConfirm: () => {
form.setFieldValue(`secrets.${index}.value`, secretsMap.get(kind)?.value ?? "");
resolve(true);
},
});
})
}
>
<IntegrationSecretInput
label={t(`integration.secrets.kind.${kind}.newLabel`)}
key={kind}
kind={kind}
{...form.getInputProps(`secrets.${index}.value`)}
/>
</SecretCard>
))}
>
<IntegrationSecretInput
label={t(`integration.secrets.kind.${secret.kind}.newLabel`)}
key={secret.kind}
kind={secret.kind}
{...form.getInputProps(`secrets.${index}.value`)}
/>
</SecretCard>
))}
{isInitialSecretKinds
? null
: form.values.secrets.map(({ kind }, index) => (
<IntegrationSecretInput
withAsterisk
key={kind}
kind={kind}
{...form.getInputProps(`secrets.${index}.value`)}
/>
))}
{form.values.secrets.length === 0 && (
<Alert icon={<IconInfoCircle size={"1rem"} />} color={"blue"}>
<Text c={"blue"}>{t("integration.secrets.noSecretsRequired.text")}</Text>
</Alert>
)}
</Stack>
</Fieldset>
@@ -1,26 +1,15 @@
"use client";
import { useCallback, useState } from "react";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
Alert,
Button,
Checkbox,
Collapse,
Fieldset,
Group,
SegmentedControl,
Stack,
Text,
TextInput,
} from "@mantine/core";
import { Alert, Button, Checkbox, Collapse, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
import { z } from "zod/v4";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
import type { IntegrationKind } from "@homarr/definitions";
import {
getAllSecretKindOptions,
getIconUrl,
@@ -28,14 +17,14 @@ import {
getIntegrationName,
integrationDefs,
} from "@homarr/definitions";
import type { UseFormReturnType } from "@homarr/form";
import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { appHrefSchema } from "@homarr/validation/app";
import { integrationCreateSchema } from "@homarr/validation/integration";
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
import { SecretKindsSegmentedControl } from "../_components/secrets/integration-secret-segmented-control";
import { IntegrationTestConnectionError } from "../_components/test-connection/integration-test-connection-error";
import type { AnyMappedTestConnectionError } from "../_components/test-connection/types";
@@ -218,40 +207,4 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
);
};
interface SecretKindsSegmentedControlProps {
secretKinds: IntegrationSecretKind[][];
form: UseFormReturnType<FormType, (values: FormType) => FormType>;
}
const SecretKindsSegmentedControl = ({ secretKinds, form }: SecretKindsSegmentedControlProps) => {
const t = useScopedI18n("integration.secrets");
const secretKindGroups = secretKinds.map((kinds) => ({
label:
kinds.length === 0
? t("noSecretsRequired.segmentTitle")
: kinds.map((kind) => t(`kind.${kind}.label`)).join(" & "),
value: kinds.length === 0 ? "empty" : kinds.join("-"),
}));
const onChange = useCallback(
(value: string) => {
if (value === "empty") {
form.setFieldValue("secrets", []);
return;
}
const kinds = value.split("-") as IntegrationSecretKind[];
const secrets = kinds.map((kind) => ({
kind,
value: "",
}));
form.setFieldValue("secrets", secrets);
},
[form],
);
return <SegmentedControl fullWidth data={secretKindGroups} onChange={onChange}></SegmentedControl>;
};
type FormType = z.infer<typeof formSchema>;
@@ -14,7 +14,7 @@ export const UploadMediaButton = () => {
};
return (
<UploadMedia onSettled={onSettledAsync}>
<UploadMedia onSettled={onSettledAsync} multiple>
{({ onClick, loading }) => (
<Button onClick={onClick} loading={loading} rightSection={<IconUpload size={16} stroke={1.5} />}>
{t("media.action.upload.label")}
+20 -75
View File
@@ -1,25 +1,16 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react";
import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next";
import { isProviderEnabled } from "@homarr/auth/server";
import { getScopedI18n } from "@homarr/translation/server";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { createMetaTitle } from "~/metadata";
import { HeroBanner } from "./_components/hero-banner";
interface LinkProps {
title: string;
subtitle: string;
count: number;
href: string;
hidden?: boolean;
}
export async function generateMetadata() {
export async function generateMetadata(): Promise<Metadata> {
const t = await getScopedI18n("management");
return {
@@ -29,78 +20,32 @@ export async function generateMetadata() {
export default async function ManagementPage() {
const statistics = await api.home.getStats();
const session = await auth();
const t = await getScopedI18n("management.page.home");
const links: LinkProps[] = [
{
count: statistics.countBoards,
href: "/manage/boards",
subtitle: t("statisticLabel.boards"),
title: t("statistic.board"),
},
{
count: statistics.countUsers,
href: "/manage/users",
subtitle: t("statisticLabel.authentication"),
title: t("statistic.user"),
hidden: !session?.user.permissions.includes("admin"),
},
{
count: statistics.countInvites,
href: "/manage/users/invites",
subtitle: t("statisticLabel.authentication"),
title: t("statistic.invite"),
hidden: !isProviderEnabled("credentials") || !session?.user.permissions.includes("admin"),
},
{
count: statistics.countIntegrations,
href: "/manage/integrations",
subtitle: t("statisticLabel.resources"),
title: t("statistic.integration"),
},
{
count: statistics.countApps,
href: "/manage/apps",
subtitle: t("statisticLabel.resources"),
title: t("statistic.app"),
hidden: !session?.user,
},
{
count: statistics.countGroups,
href: "/manage/users/groups",
subtitle: t("statisticLabel.authorization"),
title: t("statistic.group"),
hidden: !session?.user.permissions.includes("admin"),
},
];
return (
<>
<DynamicBreadcrumb />
<HeroBanner />
<Space h="md" />
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
{links.map(
(link) =>
!link.hidden && (
<Card component={Link} href={link.href} key={link.href} radius="lg">
<Group justify="space-between" wrap="nowrap">
<Group wrap="nowrap">
<Text size="2.4rem" fw="bolder">
{link.count}
</Text>
<Stack gap={0}>
<Text c="red" size="xs">
{link.subtitle}
</Text>
<Text fw="bold">{link.title}</Text>
</Stack>
</Group>
<IconArrowRight />
</Group>
</Card>
),
)}
{statistics.map((statistic) => (
<Card component={Link} href={statistic.path} key={statistic.path} radius="lg">
<Group justify="space-between" wrap="nowrap">
<Group wrap="nowrap">
<Text size="2.4rem" fw="bolder">
{statistic.count}
</Text>
<Stack gap={0}>
<Text c="red" size="xs">
{t(`statisticLabel.${statistic.subtitleKey}`)}
</Text>
<Text fw="bold">{t(`statistic.${statistic.titleKey}`)}</Text>
</Stack>
</Group>
<IconArrowRight />
</Group>
</Card>
))}
</SimpleGrid>
</>
);
@@ -92,38 +92,24 @@ export const UserProfileAvatarForm = ({ user }: UserProfileAvatarForm) => {
});
}, [mutate, user.id, openConfirmModal, tManageAvatar]);
const isCredentialsUser = user.provider === "credentials";
return (
<Box pos="relative">
<Menu
opened={opened}
keepMounted
onChange={isCredentialsUser ? toggle : undefined}
position="bottom-start"
withArrow
>
<Menu opened={opened} keepMounted onChange={toggle} position="bottom-start" withArrow>
<Menu.Target>
<UnstyledButton
component={isCredentialsUser ? undefined : "div"}
style={{ cursor: !isCredentialsUser ? "default" : undefined }}
onClick={isCredentialsUser ? toggle : undefined}
>
<UnstyledButton onClick={toggle}>
<UserAvatar user={user} size={200} />
{isCredentialsUser && (
<Button
component="div"
pos="absolute"
bottom={0}
left={0}
size="compact-md"
fw="normal"
variant="default"
leftSection={<IconPencil size={18} stroke={1.5} />}
>
{t("common.action.edit")}
</Button>
)}
<Button
component="div"
pos="absolute"
bottom={0}
left={0}
size="compact-md"
fw="normal"
variant="default"
leftSection={<IconPencil size={18} stroke={1.5} />}
>
{t("common.action.edit")}
</Button>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
@@ -12,6 +12,7 @@ export class DynamicSectionMockBuilder {
options: {
title: "",
borderColor: "",
customCssClasses: [],
},
layouts: [],
...section,
@@ -1,4 +1,5 @@
import { Badge, Box, Card } from "@mantine/core";
import combineClasses from "clsx";
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
@@ -24,7 +25,7 @@ export const BoardDynamicSection = ({ section }: Props) => {
}}
>
<Card
className={classes.itemCard}
className={combineClasses(classes.itemCard, section.options.customCssClasses.join(" "))}
w="100%"
h="100%"
withBorder
@@ -19,6 +19,7 @@ export const addDynamicSectionCallback = () => (board: Board) => {
options: {
title: "",
borderColor: "",
customCssClasses: [],
},
layouts: createDynamicSectionLayouts(board, firstSection),
} satisfies DynamicSection;
@@ -6,6 +6,7 @@ import type { z } from "zod/v4";
import { useZodForm } from "@homarr/form";
import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { TextMultiSelect } from "@homarr/ui";
import { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
interface ModalProps {
@@ -31,6 +32,10 @@ export const DynamicSectionEditModal = createModal<ModalProps>(({ actions, inner
>
<Stack>
<TextInput label={t("section.dynamic.option.title.label")} {...form.getInputProps("title")} />
<TextMultiSelect
label={t("section.dynamic.option.customCssClasses.label")}
{...form.getInputProps("customCssClasses")}
/>
<ColorInput
label={t("section.dynamic.option.borderColor.label")}
format="hex"
@@ -1,3 +0,0 @@
.flagIcon {
border-radius: 4px;
}
@@ -6,8 +6,7 @@ import { IconCheck } from "@tabler/icons-react";
import type { SupportedLanguage } from "@homarr/translation";
import { localeConfigurations, supportedLanguages } from "@homarr/translation";
import classes from "./language-combobox.module.css";
import { LanguageIcon } from "@homarr/ui";
import "flag-icons/css/flag-icons.min.css";
@@ -84,7 +83,7 @@ const OptionItem = ({
return (
<Group wrap="nowrap" justify="space-between">
<Group wrap="nowrap">
<span className={`fi fi-${localeConfigurations[localeKey].flagIcon} ${classes.flagIcon}`}></span>
<LanguageIcon icon={localeConfigurations[localeKey].icon} />
<Group wrap="nowrap" gap="xs">
<Text>{localeConfigurations[localeKey].name}</Text>
<Text size="xs" c="dimmed" inherit>
@@ -0,0 +1,17 @@
import Script from "next/script";
import type { SupportedLanguage } from "@homarr/translation";
export const CrowdinLiveTranslation = (props: { locale: SupportedLanguage }) => {
if (props.locale !== "cr") return null;
return (
<>
<Script type="text/javascript" src="//cdn.crowdin.com/jipt/jipt.js"></Script>
<Script type="text/javascript" id="crowdin-configuration">
{`var _jipt = []; _jipt.push(['project', 'homarr_labs']);`}
</Script>
</>
);
};
@@ -19,6 +19,7 @@ import {
import type { RouterOutputs } from "@homarr/api";
import { signOut, useSession } from "@homarr/auth/client";
import { hotkeys } from "@homarr/definitions";
import { createModal, useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
@@ -34,7 +35,7 @@ interface UserAvatarMenuProps {
export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatarMenuProps) => {
const t = useScopedI18n("common.userAvatar.menu");
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
useHotkeys([["mod+J", toggleColorScheme]]);
useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]);
const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon;
+6 -6
View File
@@ -38,8 +38,8 @@
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.18",
"dotenv": "^17.2.2",
"fastify": "^5.6.0",
"dotenv": "^17.2.3",
"fastify": "^5.6.1",
"superjson": "2.2.2",
"undici": "7.16.0"
},
@@ -47,12 +47,12 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.18.3",
"@types/node": "^22.18.10",
"dotenv-cli": "^10.0.0",
"esbuild": "^0.25.9",
"eslint": "^9.35.0",
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"prettier": "^3.6.2",
"tsx": "4.20.4",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -25,7 +25,7 @@
"@homarr/log": "workspace:^",
"@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^17.2.2",
"dotenv": "^17.2.3",
"tsx": "4.20.4",
"ws": "^8.18.3"
},
@@ -34,9 +34,9 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1",
"esbuild": "^0.25.9",
"eslint": "^9.35.0",
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
+19 -19
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.38.0",
"version": "1.42.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -42,25 +42,25 @@
"@semantic-release/github": "^11.0.6",
"@semantic-release/npm": "^12.0.2",
"@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.5.1",
"@turbo/gen": "^2.5.6",
"@vitejs/plugin-react": "^5.0.2",
"@testcontainers/redis": "^11.7.1",
"@turbo/gen": "^2.5.8",
"@vitejs/plugin-react": "^5.0.4",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"conventional-changelog-conventionalcommits": "^9.1.0",
"cross-env": "^10.0.0",
"cross-env": "^10.1.0",
"jsdom": "^27.0.0",
"prettier": "^3.6.2",
"semantic-release": "^24.2.8",
"testcontainers": "^11.5.1",
"turbo": "^2.5.6",
"typescript": "^5.9.2",
"semantic-release": "^24.2.9",
"testcontainers": "^11.7.1",
"turbo": "^2.5.8",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
},
"packageManager": "pnpm@10.16.1",
"packageManager": "pnpm@10.18.2",
"engines": {
"node": ">=22.19.0"
"node": ">=22.20.0"
},
"pnpm": {
"onlyBuiltDependencies": [
@@ -80,20 +80,20 @@
"axios@>=1.0.0 <1.8.2": ">=1.12.2",
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.25.9",
"esbuild@<=0.24.2": ">=0.25.10",
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
"hono@<4.6.5": ">=4.9.7",
"hono@<4.6.5": ">=4.9.11",
"linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.5",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0",
"proxmox-api>undici": "7.16.0",
"react-is": "^19.1.1",
"rollup@>=4.0.0 <4.22.4": ">=4.50.1",
"react-is": "^19.2.0",
"rollup@>=4.0.0 <4.22.4": ">=4.52.4",
"sha.js@<=2.4.11": ">=2.4.12",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.0",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.0",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
"tmp@<=0.2.3": ">=0.2.5",
"vite@>=5.0.0 <=5.4.18": ">=7.1.5"
"vite@>=5.0.0 <=5.4.18": ">=7.1.9"
},
"patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch",
+2 -2
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+14 -13
View File
@@ -40,27 +40,28 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/request-handler": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.3.0",
"@tanstack/react-query": "^5.87.4",
"@trpc/client": "^11.5.1",
"@trpc/react-query": "^11.5.1",
"@trpc/server": "^11.5.1",
"@trpc/tanstack-react-query": "^11.5.1",
"@kubernetes/client-node": "^1.4.0",
"@tanstack/react-query": "^5.90.2",
"@trpc/client": "^11.6.0",
"@trpc/react-query": "^11.6.0",
"@trpc/server": "^11.6.0",
"@trpc/tanstack-react-query": "^11.6.0",
"lodash.clonedeep": "^4.5.0",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"next": "15.5.4",
"react": "19.2.0",
"react-dom": "19.2.0",
"superjson": "2.2.2",
"trpc-to-openapi": "^3.0.1",
"zod": "^4.1.8"
"trpc-to-openapi": "^3.1.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"eslint": "^9.37.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
+136 -20
View File
@@ -1,31 +1,147 @@
import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
import { isProviderEnabled } from "@homarr/auth/server";
import type { Database } from "@homarr/db";
import { apps, boards, groups, integrations, invites, users } from "@homarr/db/schema";
import { db, eq, inArray, or } from "@homarr/db";
import {
apps,
boards,
boardUserPermissions,
groupMembers,
groups,
integrations,
invites,
medias,
searchEngines,
users,
} from "@homarr/db/schema";
import type { TranslationObject } from "@homarr/translation";
import { createTRPCRouter, publicProcedure } from "../trpc";
interface HomeStatistic {
titleKey: keyof TranslationObject["management"]["page"]["home"]["statistic"];
subtitleKey: keyof TranslationObject["management"]["page"]["home"]["statisticLabel"];
count: number;
path: string;
}
export const homeRouter = createTRPCRouter({
getStats: publicProcedure.query(async ({ ctx }) => {
const isAdmin = ctx.session?.user.permissions.includes("admin") ?? false;
const isCredentialsEnabled = isProviderEnabled("credentials");
return {
countBoards: await getCountForTableAsync(ctx.db, boards, true),
countUsers: await getCountForTableAsync(ctx.db, users, isAdmin),
countGroups: await getCountForTableAsync(ctx.db, groups, true),
countInvites: await getCountForTableAsync(ctx.db, invites, isAdmin),
countIntegrations: await getCountForTableAsync(ctx.db, integrations, isCredentialsEnabled && isAdmin),
countApps: await getCountForTableAsync(ctx.db, apps, true),
};
const statistics: HomeStatistic[] = [];
const boardIds: string[] = [];
if (ctx.session?.user && !ctx.session.user.permissions.includes("board-view-all")) {
const permissionsOfCurrentUserWhenPresent = await ctx.db.query.boardUserPermissions.findMany({
where: eq(boardUserPermissions.userId, ctx.session.user.id),
});
const permissionsOfCurrentUserGroupsWhenPresent = await ctx.db.query.groupMembers.findMany({
where: eq(groupMembers.userId, ctx.session.user.id),
with: {
group: {
with: {
boardPermissions: {},
},
},
},
});
boardIds.push(
...permissionsOfCurrentUserWhenPresent
.map((permission) => permission.boardId)
.concat(
permissionsOfCurrentUserGroupsWhenPresent
.map((groupMember) => groupMember.group.boardPermissions.map((permission) => permission.boardId))
.flat(),
),
);
}
statistics.push({
titleKey: "board",
subtitleKey: "boards",
count: await db.$count(
boards,
ctx.session?.user.permissions.includes("board-view-all")
? undefined
: or(
eq(boards.isPublic, true),
eq(boards.creatorId, ctx.session?.user.id ?? ""),
boardIds.length > 0 ? inArray(boards.id, boardIds) : undefined,
),
),
path: "/manage/boards",
});
if (isAdmin) {
statistics.push({
titleKey: "user",
subtitleKey: "authentication",
count: await db.$count(users),
path: "/manage/users",
});
}
if (isAdmin && isCredentialsEnabled) {
statistics.push({
titleKey: "invite",
subtitleKey: "authentication",
count: await db.$count(invites),
path: "/manage/users/invites",
});
}
if (ctx.session?.user.permissions.includes("integration-create")) {
statistics.push({
titleKey: "integration",
subtitleKey: "resources",
count: await db.$count(integrations),
path: "/manage/integrations",
});
}
if (ctx.session?.user) {
statistics.push({
titleKey: "app",
subtitleKey: "resources",
count: await db.$count(apps),
path: "/manage/apps",
});
}
if (isAdmin) {
statistics.push({
titleKey: "group",
subtitleKey: "authorization",
count: await db.$count(groups),
path: "/manage/users/groups",
});
}
if (ctx.session?.user.permissions.includes("search-engine-create")) {
statistics.push({
titleKey: "searchEngine",
subtitleKey: "resources",
count: await db.$count(searchEngines),
path: "/manage/search-engines",
});
}
if (ctx.session?.user.permissions.includes("media-upload")) {
statistics.push({
titleKey: "media",
subtitleKey: "resources",
count: await db.$count(
medias,
ctx.session.user.permissions.includes("media-view-all")
? undefined
: eq(medias.creatorId, ctx.session.user.id),
),
path: "/manage/medias",
});
}
return statistics;
}),
});
const getCountForTableAsync = async (db: Database, table: AnySQLiteTable, canView: boolean) => {
if (!canView) {
return 0;
}
return await db.$count(table);
};
@@ -4,7 +4,7 @@ import { z } from "zod/v4";
import { createId, objectEntries } from "@homarr/common";
import { decryptSecret, encryptSecret } from "@homarr/common/server";
import type { Database } from "@homarr/db";
import { and, asc, eq, handleTransactionsAsync, inArray, like } from "@homarr/db";
import { and, asc, eq, handleTransactionsAsync, inArray, like, or } from "@homarr/db";
import {
groupMembers,
groupPermissions,
@@ -386,6 +386,21 @@ export const integrationRouter = createTRPCRouter({
}
}
const removedSecrets = integration.secrets.filter(
(dbSecret) => !input.secrets.some((secret) => dbSecret.kind === secret.kind),
);
if (removedSecrets.length >= 1) {
await ctx.db
.delete(integrationSecrets)
.where(
or(
...removedSecrets.map((secret) =>
and(eq(integrationSecrets.integrationId, input.id), eq(integrationSecrets.kind, secret.kind)),
),
),
);
}
logger.info("Updated integration", {
id: input.id,
name: input.name,
+34 -21
View File
@@ -55,34 +55,47 @@ export const mediaRouter = createTRPCRouter({
.requiresPermission("media-upload")
.input(mediaUploadSchema)
.mutation(async ({ ctx, input }) => {
const content = Buffer.from(await input.file.arrayBuffer());
const id = createId();
const media = {
id,
creatorId: ctx.session.user.id,
content,
size: input.file.size,
contentType: input.file.type,
name: input.file.name,
} satisfies InferInsertModel<typeof medias>;
await ctx.db.insert(medias).values(media);
const files = await Promise.all(
input.files.map(async (file) => ({
id: createId(),
meta: file,
content: Buffer.from(await file.arrayBuffer()),
})),
);
const insertMedias = files.map(
(file): InferInsertModel<typeof medias> => ({
id: file.id,
creatorId: ctx.session.user.id,
content: file.content,
size: file.meta.size,
contentType: file.meta.type,
name: file.meta.name,
}),
);
await ctx.db.insert(medias).values(insertMedias);
const localIconRepository = await ctx.db.query.iconRepositories.findFirst({
where: eq(iconRepositories.slug, LOCAL_ICON_REPOSITORY_SLUG),
});
if (!localIconRepository) return id;
const ids = files.map((file) => file.id);
if (!localIconRepository) return ids;
const icon = mapMediaToIcon(media);
await ctx.db.insert(icons).values({
id: createId(),
checksum: icon.checksum,
name: icon.fileNameWithExtension,
url: icon.imageUrl,
iconRepositoryId: localIconRepository.id,
});
await ctx.db.insert(icons).values(
insertMedias.map((media) => {
const icon = mapMediaToIcon(media);
return id;
return {
id: createId(),
checksum: icon.checksum,
name: icon.fileNameWithExtension,
url: icon.imageUrl,
iconRepositoryId: localIconRepository.id,
};
}),
);
return ids;
}),
deleteMedia: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
const dbMedia = await ctx.db.query.medias.findFirst({
+1 -9
View File
@@ -114,11 +114,10 @@ export const userRouter = createTRPCRouter({
.input(
z.object({
userId: z.string(),
// Max image size of 256KB, only png and jpeg are allowed
image: z
.string()
.regex(/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9/+]+=*$/g)
.max(262144)
.max(350000) // approximately 256KB in base64 (256 * 1024 * 4 / 3 + prefixes)
.nullable(),
}),
)
@@ -147,13 +146,6 @@ export const userRouter = createTRPCRouter({
});
}
if (user.provider !== "credentials") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Profile image can not be changed for users with external providers",
});
}
await ctx.db
.update(users)
.set({
+51 -4
View File
@@ -1,6 +1,11 @@
import { observable } from "@trpc/server/observable";
import { z } from "zod/v4";
import type { Modify } from "@homarr/common/types";
import type { Integration } from "@homarr/db/schema";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import type { CalendarEvent } from "@homarr/integrations/types";
import { radarrReleaseTypes } from "@homarr/integrations/types";
import { calendarMonthRequestHandler } from "@homarr/request-handler/calendar";
@@ -19,14 +24,56 @@ export const calendarRouter = createTRPCRouter({
)
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.query(async ({ ctx, input }) => {
const results = await Promise.all(
return await Promise.all(
ctx.integrations.map(async (integration) => {
const innerHandler = calendarMonthRequestHandler.handler(integration, input);
const { integrationIds: _integrationIds, ...handlerInput } = input;
const innerHandler = calendarMonthRequestHandler.handler(integration, handlerInput);
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
return {
events: data,
integration: {
id: integration.id,
name: integration.name,
kind: integration.kind,
},
};
}),
);
return results.flat();
}),
subscribeToEvents: publicProcedure
.input(
z.object({
year: z.number(),
month: z.number(),
releaseType: z.array(z.enum(radarrReleaseTypes)),
showUnmonitored: z.boolean(),
}),
)
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.subscription(({ ctx, input }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"calendar"> }>;
events: CalendarEvent[];
}>((emit) => {
const unsubscribes: (() => void)[] = [];
for (const integrationWithSecrets of ctx.integrations) {
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
const { integrationIds: _integrationIds, ...handlerInput } = input;
const innerHandler = calendarMonthRequestHandler.handler(integrationWithSecrets, handlerInput);
const unsubscribe = innerHandler.subscribe((events) => {
emit.next({
integration,
events,
});
});
unsubscribes.push(unsubscribe);
}
return () => {
unsubscribes.forEach((unsubscribe) => {
unsubscribe();
});
};
});
}),
});
@@ -1,4 +1,7 @@
import { observable } from "@trpc/server/observable";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import type { MediaTranscoding } from "@homarr/request-handler/media-transcoding";
import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding";
import { paginatedSchema } from "@homarr/validation/common";
@@ -15,7 +18,7 @@ export const mediaTranscodingRouter = createTRPCRouter({
.input(paginatedSchema.pick({ page: true, pageSize: true }))
.query(async ({ ctx, input }) => {
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
pageOffset: input.page,
pageOffset: (input.page - 1) * input.pageSize,
pageSize: input.pageSize,
});
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
@@ -25,4 +28,19 @@ export const mediaTranscodingRouter = createTRPCRouter({
data,
};
}),
subscribeData: publicProcedure
.concat(createIndexerManagerIntegrationMiddleware("query"))
.input(paginatedSchema.pick({ page: true, pageSize: true }))
.subscription(({ ctx, input }) => {
return observable<{ integrationId: string; data: MediaTranscoding }>((emit) => {
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
pageOffset: (input.page - 1) * input.pageSize,
pageSize: input.pageSize,
});
const unsubscribe = innerHandler.subscribe((data) => {
emit.next({ integrationId: input.integrationId, data });
});
return unsubscribe;
});
}),
});
+15 -39
View File
@@ -1,6 +1,8 @@
import { observable } from "@trpc/server/observable";
import { z } from "zod/v4";
import { fetchWithTimeout } from "@homarr/common";
import type { Weather } from "@homarr/request-handler/weather";
import { weatherRequestHandler } from "@homarr/request-handler/weather";
import { createTRPCRouter, publicProcedure } from "../../trpc";
@@ -9,45 +11,19 @@ const atLocationInput = z.object({
latitude: z.number(),
});
const atLocationOutput = z.object({
current_weather: z.object({
weathercode: z.number(),
temperature: z.number(),
windspeed: z.number(),
}),
daily: z.object({
time: z.array(z.string()),
weathercode: z.array(z.number()),
temperature_2m_max: z.array(z.number()),
temperature_2m_min: z.array(z.number()),
sunrise: z.array(z.string()),
sunset: z.array(z.string()),
wind_speed_10m_max: z.array(z.number()),
wind_gusts_10m_max: z.array(z.number()),
}),
});
export const weatherRouter = createTRPCRouter({
atLocation: publicProcedure.input(atLocationInput).query(async ({ input }) => {
const res = await fetchWithTimeout(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`,
);
const json: unknown = await res.json();
const weather = await atLocationOutput.parseAsync(json);
return {
current: weather.current_weather,
daily: weather.daily.time.map((value, index) => {
return {
time: value,
weatherCode: weather.daily.weathercode[index] ?? 404,
maxTemp: weather.daily.temperature_2m_max[index],
minTemp: weather.daily.temperature_2m_min[index],
sunrise: weather.daily.sunrise[index],
sunset: weather.daily.sunset[index],
maxWindSpeed: weather.daily.wind_speed_10m_max[index],
maxWindGusts: weather.daily.wind_gusts_10m_max[index],
};
}),
};
const handler = weatherRequestHandler.handler(input);
return await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then((result) => result.data);
}),
subscribeAtLocation: publicProcedure.input(atLocationInput).subscription(({ input }) => {
return observable<Weather>((emit) => {
const handler = weatherRequestHandler.handler(input);
const unsubscribe = handler.subscribe((data) => {
emit.next(data);
});
return unsubscribe;
});
}),
});
+10
View File
@@ -20,6 +20,7 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
where: eq(users.id, user.id),
columns: {
name: true,
image: true,
colorScheme: true,
},
});
@@ -59,6 +60,15 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
`Username for user of oidc provider has changed. user=${user.id} old='${dbUser.name}' new='${profileUsername}'`,
);
}
if (
typeof profile.picture === "string" &&
dbUser.image !== profile.picture &&
!dbUser.image?.startsWith("data:")
) {
await db.update(users).set({ image: profile.picture }).where(eq(users.id, user.id));
logger.info(`Profile picture for user of oidc provider has changed. user=${user.id}'`);
}
}
logger.info(`User '${dbUser.name}' logged in at ${dayjs().format()}`);
+8 -8
View File
@@ -23,8 +23,8 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@auth/core": "^0.40.0",
"@auth/drizzle-adapter": "^1.10.0",
"@auth/core": "^0.41.0",
"@auth/drizzle-adapter": "^1.11.0",
"@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0",
@@ -35,11 +35,11 @@
"bcrypt": "^6.0.0",
"cookies": "^0.9.1",
"ldapts": "8.0.9",
"next": "15.5.3",
"next": "15.5.4",
"next-auth": "5.0.0-beta.29",
"react": "19.1.1",
"react-dom": "19.1.1",
"zod": "^4.1.8"
"react": "19.2.0",
"react-dom": "19.2.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -47,8 +47,8 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"@types/cookies": "0.9.1",
"eslint": "^9.35.0",
"eslint": "^9.37.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
@@ -61,6 +61,7 @@ export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profil
id: profile.sub,
name,
email: profile.email,
image: typeof profile.picture === "string" ? profile.picture : null,
provider: "oidc",
};
},
+4 -4
View File
@@ -25,14 +25,14 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/api": "workspace:^0.1.0",
"react": "19.1.1",
"react-dom": "19.1.1"
"react": "19.2.0",
"react-dom": "19.2.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -30,7 +30,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -28,14 +28,14 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^17.2.2"
"dotenv": "^17.2.3"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"esbuild": "^0.25.9",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+8 -8
View File
@@ -31,20 +31,20 @@
"@homarr/log": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^2.2.2",
"dayjs": "^1.11.18",
"dns-caching": "^0.2.5",
"next": "15.5.3",
"dns-caching": "^0.2.7",
"next": "15.5.4",
"octokit": "^5.0.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"undici": "7.16.0",
"zod": "^4.1.8",
"zod-validation-error": "^4.0.1"
"zod": "^4.1.12",
"zod-validation-error": "^4.0.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+6 -2
View File
@@ -11,13 +11,17 @@ const calculateTimeAgo = (timestamp: Date) => {
};
export const useTimeAgo = (timestamp: Date, updateFrequency = 1000) => {
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp));
const [timeAgo, setTimeAgo] = useState(() => calculateTimeAgo(timestamp));
useEffect(() => {
setTimeAgo(calculateTimeAgo(timestamp));
}, [timestamp]);
useEffect(() => {
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), updateFrequency);
return () => clearInterval(intervalId); // clear interval on hook unmount
}, [timestamp]);
}, [timestamp, updateFrequency]);
return timeAgo;
};
+5
View File
@@ -47,3 +47,8 @@ export const humanFileSize = (size: number, concat = ""): string => {
}
return "∞";
};
const IMPERIAL_MULTIPLIER = 1.609344;
export const metricToImperial = (metricValue: number) => metricValue / IMPERIAL_MULTIPLIER;
export const imperialToMetric = (imperialValue: number) => imperialValue * IMPERIAL_MULTIPLIER;
+4 -4
View File
@@ -25,14 +25,14 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@t3-oss/env-nextjs": "^0.13.8",
"ioredis": "5.7.0",
"zod": "^4.1.8"
"ioredis": "5.8.1",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+9 -9
View File
@@ -29,21 +29,21 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@tanstack/react-query": "^5.87.4",
"@trpc/client": "^11.5.1",
"@trpc/server": "^11.5.1",
"@trpc/tanstack-react-query": "^11.5.1",
"@tanstack/react-query": "^5.90.2",
"@trpc/client": "^11.6.0",
"@trpc/server": "^11.6.0",
"@trpc/tanstack-react-query": "^11.6.0",
"node-cron": "^4.2.1",
"react": "19.1.1",
"zod": "^4.1.8"
"react": "19.2.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"@types/react": "19.1.13",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"@types/react": "19.2.2",
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2
View File
@@ -22,6 +22,7 @@ import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
import { pingJob } from "./jobs/ping";
import { rssFeedsJob } from "./jobs/rss-feeds";
import { updateCheckerJob } from "./jobs/update-checker";
import { weatherJob } from "./jobs/weather";
import { createCronJobGroup } from "./lib";
export const jobGroup = createCronJobGroup({
@@ -48,6 +49,7 @@ export const jobGroup = createCronJobGroup({
firewallVersion: firewallVersionJob,
firewallInterfaces: firewallInterfacesJob,
refreshNotifications: refreshNotificationsJob,
weather: weatherJob,
});
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
+33
View File
@@ -0,0 +1,33 @@
import SuperJSON from "superjson";
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db";
import { items } from "@homarr/db/schema";
import { logger } from "@homarr/log";
import { weatherRequestHandler } from "@homarr/request-handler/weather";
import type { WidgetComponentProps } from "../../../widgets";
import { createCronJob } from "../lib";
export const weatherJob = createCronJob("weather", EVERY_10_MINUTES).withCallback(async () => {
const weatherItems = await db.query.items.findMany({
where: eq(items.kind, "weather"),
});
const parsedItems = weatherItems.map((item) => ({
id: item.id,
options: SuperJSON.parse<WidgetComponentProps<"weather">["options"]>(item.options),
}));
for (const item of parsedItems) {
try {
const innerHandler = weatherRequestHandler.handler({
longitude: item.options.location.longitude,
latitude: item.options.location.latitude,
});
await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
} catch (error) {
logger.error("Failed to update weather", { id: item.id, error });
}
}
});
+12 -12
View File
@@ -43,22 +43,22 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@auth/core": "^0.40.0",
"@auth/core": "^0.41.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"@mantine/core": "^8.3.4",
"@paralleldrive/cuid2": "^2.2.2",
"@testcontainers/mysql": "^11.5.1",
"@testcontainers/postgresql": "^11.5.1",
"better-sqlite3": "^12.2.0",
"dotenv": "^17.2.2",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.5",
"@testcontainers/mysql": "^11.7.1",
"@testcontainers/postgresql": "^11.7.1",
"better-sqlite3": "^12.4.1",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.31.5",
"drizzle-orm": "^0.44.6",
"drizzle-zod": "^0.8.3",
"mysql2": "3.14.5",
"mysql2": "3.15.2",
"pg": "^8.16.3",
"superjson": "2.2.2"
},
@@ -69,10 +69,10 @@
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.15.5",
"dotenv-cli": "^10.0.0",
"esbuild": "^0.25.9",
"eslint": "^9.35.0",
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"prettier": "^3.6.2",
"tsx": "4.20.4",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -24,15 +24,15 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"fast-xml-parser": "^5.2.5",
"zod": "^4.1.8"
"fast-xml-parser": "^5.3.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"eslint": "^9.37.0",
"tsx": "4.20.4",
"typescript": "^5.9.2"
"typescript": "^5.9.3"
}
}
@@ -87,6 +87,7 @@ export type HomarrDocumentationPath =
| "/docs/tags/programming"
| "/docs/tags/proxy"
| "/docs/tags/puid"
| "/docs/tags/redis"
| "/docs/tags/responsive"
| "/docs/tags/roles"
| "/docs/tags/search"
@@ -159,6 +160,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/github"
| "/docs/integrations/gitlab"
| "/docs/integrations/home-assistant"
| "/docs/integrations/ical"
| "/docs/integrations/jellyfin"
| "/docs/integrations/jellyseerr"
| "/docs/integrations/kubernetes"
+6
View File
@@ -0,0 +1,6 @@
export const hotkeys = {
toggleBoardEdit: "mod+e",
toggleColorScheme: "mod+j",
saveNotebook: "mod+s",
openSpotlight: "mod+k",
};
+1
View File
@@ -13,3 +13,4 @@ export * from "./cookie";
export * from "./search-engine";
export * from "./onboarding";
export * from "./emptysuperjson";
export * from "./hotkeys";
+1 -1
View File
@@ -161,7 +161,7 @@ export const integrationDefs = {
name: "Home Assistant",
secretKinds: [["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
category: ["smartHomeServer"],
category: ["smartHomeServer", "calendar"],
documentationUrl: createDocumentationLink("/docs/integrations/home-assistant"),
},
openmediavault: {
+4 -4
View File
@@ -26,14 +26,14 @@
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0",
"dockerode": "^4.0.8"
"dockerode": "^4.0.9"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/dockerode": "^3.3.43",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"@types/dockerode": "^3.3.44",
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -26,15 +26,15 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.1",
"@mantine/form": "^8.3.4",
"mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.1.8"
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -29,15 +29,15 @@
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"react": "19.1.1",
"zod": "^4.1.8"
"@mantine/core": "^8.3.4",
"react": "19.2.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
@@ -165,11 +165,14 @@ export const IconPicker = ({
/>
{session?.user.permissions.includes("media-upload") && (
<UploadMedia
onSuccess={({ url }) => {
onSuccess={(medias) => {
const first = medias.at(0);
if (!first) return;
startTransition(() => {
setValue(url);
setPreviewUrl(url);
setSearch(url);
setValue(first.url);
setPreviewUrl(first.url);
setSearch(first.url);
});
}}
>
@@ -9,27 +9,31 @@ import { supportedMediaUploadFormats } from "@homarr/validation/media";
interface UploadMediaProps {
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
multiple?: boolean;
onSettled?: () => MaybePromise<void>;
onSuccess?: (media: { id: string; url: string }) => MaybePromise<void>;
onSuccess?: (media: { id: string; url: string }[]) => MaybePromise<void>;
}
export const UploadMedia = ({ children, onSettled, onSuccess }: UploadMediaProps) => {
export const UploadMedia = ({ children, onSettled, onSuccess, multiple = false }: UploadMediaProps) => {
const t = useI18n();
const { mutateAsync, isPending } = clientApi.media.uploadMedia.useMutation();
const handleFileUploadAsync = async (file: File | null) => {
if (!file) return;
const handleFileUploadAsync = async (files: File[] | File | null) => {
if (!files || (Array.isArray(files) && files.length === 0)) return;
const filesArray: File[] = Array.isArray(files) ? files : [files];
const formData = new FormData();
formData.append("file", file);
filesArray.forEach((file) => formData.append("files", file));
await mutateAsync(formData, {
async onSuccess(mediaId) {
async onSuccess(mediaIds) {
showSuccessNotification({
message: t("media.action.upload.notification.success.message"),
});
await onSuccess?.({
id: mediaId,
url: `/api/user-medias/${mediaId}`,
});
await onSuccess?.(
mediaIds.map((id) => ({
id,
url: `/api/user-medias/${id}`,
})),
);
},
onError() {
showErrorNotification({
@@ -43,7 +47,7 @@ export const UploadMedia = ({ children, onSettled, onSuccess }: UploadMediaProps
};
return (
<FileButton onChange={handleFileUploadAsync} accept={supportedMediaUploadFormats.join(",")}>
<FileButton onChange={handleFileUploadAsync} accept={supportedMediaUploadFormats.join(",")} multiple={multiple}>
{({ onClick }) => children({ onClick, loading: isPending })}
</FileButton>
);
+2 -2
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+8 -8
View File
@@ -25,9 +25,9 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@ctrl/deluge": "^7.2.0",
"@ctrl/qbittorrent": "^9.7.0",
"@ctrl/transmission": "^7.3.0",
"@ctrl/deluge": "^7.4.0",
"@ctrl/qbittorrent": "^9.9.1",
"@ctrl/transmission": "^7.4.0",
"@gitbeaker/rest": "^43.5.0",
"@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
@@ -40,16 +40,16 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@jellyfin/sdk": "^0.11.0",
"@octokit/auth-app": "^8.1.0",
"@octokit/auth-app": "^8.1.1",
"ical.js": "^2.2.1",
"maria2": "^0.4.1",
"node-ical": "^0.20.1",
"node-ical": "^0.21.0",
"octokit": "^5.0.3",
"proxmox-api": "1.1.1",
"tsdav": "^2.1.5",
"undici": "7.16.0",
"xml2js": "^0.6.2",
"zod": "^4.1.8"
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -57,7 +57,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-unifi": "^2.5.1",
"@types/xml2js": "^0.4.14",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
@@ -117,6 +117,7 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
episodeName: sessionInfo.NowPlayingItem.EpisodeTitle,
albumName: sessionInfo.NowPlayingItem.Album ?? "",
episodeCount: sessionInfo.NowPlayingItem.EpisodeCount,
metadata: null,
};
}
@@ -1,14 +1,19 @@
import z from "zod";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
import type { ISmartHomeIntegration } from "../interfaces/smart-home/smart-home-integration";
import { entityStateSchema } from "./homeassistant-types";
import type { CalendarEvent } from "../types";
import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homeassistant-types";
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration {
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration {
public async getEntityStateAsync(entityId: string) {
try {
const response = await this.getAsync(`/api/states/${entityId}`);
@@ -62,6 +67,35 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
}
}
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
const calendarsResponse = await this.getAsync("/api/calendars");
if (!calendarsResponse.ok) throw new ResponseError(calendarsResponse);
const calendars = await calendarsSchema.parseAsync(await calendarsResponse.json());
return await Promise.all(
calendars.map(async (calendar) => {
const response = await this.getAsync(`/api/calendars/${calendar.entity_id}`, { start, end });
if (!response.ok) throw new ResponseError(response);
return await z.array(calendarEventSchema).parseAsync(await response.json());
}),
).then((events) =>
events.flat().map(
(event): CalendarEvent => ({
title: event.summary,
subTitle: null,
description: event.description,
// If not reseting it to 0 o'clock it uses utc time and therefore shows as 2 o'clock
startDate: "date" in event.start ? new Date(`${event.start.date}T00:00:00`) : new Date(event.start.dateTime),
endDate: "date" in event.end ? new Date(`${event.end.date}T00:00:00`) : new Date(event.end.dateTime),
image: null,
indicatorColor: "#18bcf2",
links: [],
location: event.location,
}),
),
);
}
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(this.url("/api/config"), {
headers: this.getAuthHeaders(),
@@ -82,8 +116,8 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
* @param path full path to the API endpoint
* @returns the response from the API
*/
private async getAsync(path: `/api/${string}`) {
return await fetchWithTrustedCertificatesAsync(this.url(path), {
private async getAsync(path: `/api/${string}`, queryParams?: Record<string, string | Date | number | boolean>) {
return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), {
headers: this.getAuthHeaders(),
});
}
@@ -12,3 +12,27 @@ export const entityStateSchema = z.object({
});
export type EntityState = z.infer<typeof entityStateSchema>;
export const calendarsSchema = z.array(
z.object({
name: z.string(),
entity_id: z.string(),
}),
);
const calendarMomentSchema = z
.object({
date: z.string(),
})
.or(
z.object({
dateTime: z.string(),
}),
);
export const calendarEventSchema = z.object({
start: calendarMomentSchema,
end: calendarMomentSchema,
summary: z.string(),
description: z.string().nullable(),
location: z.string().nullable(),
});
@@ -13,6 +13,30 @@ export interface StreamSession {
episodeName?: string | null;
albumName?: string | null;
episodeCount?: number | null;
metadata: {
video: {
resolution: {
width: number;
height: number;
} | null;
frameRate: number | null;
};
audio: {
channelCount: number | null;
codec: string | null;
};
transcoding: {
container: string | null;
resolution: {
width: number;
height: number;
} | null;
target: {
audioCodec: string | null;
videoCodec: string | null;
};
};
} | null;
} | null;
}
@@ -57,6 +57,36 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
episodeName: sessionInfo.NowPlayingItem.EpisodeTitle,
albumName: sessionInfo.NowPlayingItem.Album ?? "",
episodeCount: sessionInfo.NowPlayingItem.EpisodeCount,
metadata: {
video: {
resolution:
sessionInfo.NowPlayingItem.Width && sessionInfo.NowPlayingItem.Height
? {
width: sessionInfo.NowPlayingItem.Width,
height: sessionInfo.NowPlayingItem.Height,
}
: null,
frameRate: sessionInfo.TranscodingInfo?.Framerate ?? null,
},
audio: {
channelCount: sessionInfo.TranscodingInfo?.AudioChannels ?? null,
codec: sessionInfo.TranscodingInfo?.AudioCodec ?? null,
},
transcoding: {
resolution:
sessionInfo.TranscodingInfo?.Width && sessionInfo.TranscodingInfo.Height
? {
width: sessionInfo.TranscodingInfo.Width,
height: sessionInfo.TranscodingInfo.Height,
}
: null,
target: {
audioCodec: sessionInfo.TranscodingInfo?.AudioCodec ?? null,
videoCodec: sessionInfo.TranscodingInfo?.VideoCodec ?? null,
},
container: sessionInfo.TranscodingInfo?.Container ?? null,
},
},
};
}
@@ -47,6 +47,10 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
? {
src: imageSrc,
aspectRatio: { width: 7, height: 12 },
badge: {
color: "red",
content: `S${event.seasonNumber}/E${event.episodeNumber}`,
},
}
: null,
location: null,
@@ -69,7 +69,7 @@ const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
src: "https://image.tmdb.org/t/p/original/sWgBv7LV2PRoQgkxwlibdGXKz1S.jpg",
aspectRatio: { width: 7, height: 12 },
badge: {
content: "S1:E1",
content: "S1/E1",
color: "red",
},
},
@@ -28,6 +28,7 @@ export class MediaServerMockService implements IMediaServerIntegration {
episodeName: null,
albumName: null,
episodeCount: null,
metadata: null,
}
: null,
};
@@ -1,4 +1,7 @@
import type { Agent } from "https";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
import * as ical from "node-ical";
import { DAVClient } from "tsdav";
@@ -14,6 +17,9 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
dayjs.extend(utc);
dayjs.extend(timezone);
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
export class NextcloudIntegration extends Integration implements ICalendarIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
@@ -41,47 +47,59 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
)
).flat();
return calendarEvents.map((event): CalendarEvent => {
// @ts-expect-error the typescript definitions for this package are wrong
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
const icalData = ical.default.parseICS(event.data) as ical.CalendarResponse;
const veventObject = Object.values(icalData).find((data) => data.type === "VEVENT");
return calendarEvents
.map((event) => {
// @ts-expect-error the typescript definitions for this package are wrong
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
const icalData = ical.default.parseICS(event.data) as ical.CalendarResponse;
const veventObject = Object.values(icalData).find((data) => data.type === "VEVENT");
if (!veventObject) {
throw new Error(`Invalid event data object: ${JSON.stringify(event.data)}. Unable to process the calendar.`);
}
if (!veventObject) {
throw new Error(`Invalid event data object: ${JSON.stringify(event.data)}. Unable to process the calendar.`);
}
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
const date = veventObject.start;
const eventUrlWithoutHost = new URL(event.url).pathname;
const eventSlug = Buffer.from(eventUrlWithoutHost).toString("base64url");
const eventUrlWithoutHost = new URL(event.url).pathname;
const dateInMillis = veventObject.start.valueOf();
const startDates = veventObject.rrule ? veventObject.rrule.between(start, end) : [veventObject.start];
const url = this.url(
`/apps/calendar/timeGridWeek/now/edit/sidebar/${Buffer.from(eventUrlWithoutHost).toString("base64url")}/${dateInMillis / 1000}`,
);
const durationMs = veventObject.end.getTime() - veventObject.start.getTime();
return {
title: veventObject.summary,
subTitle: null,
description: veventObject.description,
startDate: date,
endDate: veventObject.end,
image: null,
location: veventObject.location || null,
indicatorColor: "#ff8600",
links: [
{
href: url.toString(),
name: "Nextcloud",
logo: "/images/apps/nextcloud.svg",
color: undefined,
isDark: true,
},
],
};
});
return startDates.map((startDate) => {
const timezoneOffsetMinutes = veventObject.rrule?.origOptions.tzid
? dayjs(startDate).tz(veventObject.rrule.origOptions.tzid).utcOffset()
: 0;
const utcStartDate = new Date(startDate.getTime() - timezoneOffsetMinutes * 60 * 1000);
const endDate = new Date(utcStartDate.getTime() + durationMs);
const dateInMillis = utcStartDate.valueOf();
return {
title: veventObject.summary,
subTitle: null,
description: veventObject.description,
startDate: utcStartDate,
endDate,
image: null,
location: veventObject.location || null,
indicatorColor:
"color" in veventObject && typeof veventObject.color === "string" ? veventObject.color : "#ff8600",
links: [
{
href: this.url(
`/apps/calendar/timeGridWeek/now/edit/sidebar/${eventSlug}/${dateInMillis / 1000}`,
).toString(),
name: "Nextcloud",
logo: "/images/apps/nextcloud.svg",
color: undefined,
isDark: true,
},
],
};
});
})
.flat();
}
private async createCalendarClientAsync(agent?: Agent) {
@@ -61,6 +61,7 @@ export class PlexIntegration extends Integration implements IMediaServerIntegrat
episodeName: mediaElement.$.title ?? null,
albumName: mediaElement.$.type === "track" ? (mediaElement.$.parentTitle ?? null) : null,
episodeCount: mediaElement.$.index ?? null,
metadata: null,
},
};
})
@@ -141,7 +142,10 @@ export class PlexIntegration extends Integration implements IMediaServerIntegrat
rating: item.rating?.toFixed(1),
tags: item.Genre?.map((genre) => genre.tag) ?? [],
href: super
.url(`/web/index.html#!/server/${machineIdentifier}/details?key=${encodeURIComponent(item.key)}`)
.url(
// Url with children on the end results in infinite loading screen, see https://github.com/homarr-labs/homarr/issues/4078
`/web/index.html#!/server/${machineIdentifier}/details?key=${encodeURIComponent(item.key.replace("/children", ""))}`,
)
.toString(),
length: item.duration ? Math.round(item.duration / 1000) : undefined,
};
+4 -4
View File
@@ -26,14 +26,14 @@
"dependencies": {
"@homarr/core": "workspace:^0.1.0",
"superjson": "2.2.2",
"winston": "3.17.0",
"zod": "^4.1.8"
"winston": "3.18.3",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+8 -8
View File
@@ -33,19 +33,19 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"@tabler/icons-react": "^3.34.1",
"@mantine/core": "^8.3.4",
"@tabler/icons-react": "^3.35.0",
"dayjs": "^1.11.18",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"zod": "^4.1.8"
"next": "15.5.4",
"react": "19.2.0",
"react-dom": "19.2.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+5 -5
View File
@@ -24,15 +24,15 @@
"dependencies": {
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"react": "19.1.1"
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"react": "19.2.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+4 -4
View File
@@ -24,14 +24,14 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/ui": "workspace:^0.1.0",
"@mantine/notifications": "^8.3.1",
"@tabler/icons-react": "^3.34.1"
"@mantine/notifications": "^8.3.4",
"@tabler/icons-react": "^3.35.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+8 -8
View File
@@ -37,14 +37,14 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"adm-zip": "0.5.16",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"next": "15.5.4",
"react": "19.2.0",
"react-dom": "19.2.0",
"superjson": "2.2.2",
"zod": "^4.1.8",
"zod": "^4.1.12",
"zod-form-data": "^3.0.1"
},
"devDependencies": {
@@ -52,7 +52,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/adm-zip": "0.5.7",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
@@ -88,6 +88,7 @@ const optionMapping: OptionMapping = {
location: (oldOptions) => oldOptions.location,
showCity: (oldOptions) => oldOptions.displayCityName,
dateFormat: (oldOptions) => (oldOptions.dateFormat === "hide" ? undefined : oldOptions.dateFormat),
useImperialSpeed: () => undefined,
},
iframe: {
embedUrl: (oldOptions) => oldOptions.embedUrl,
+3 -3
View File
@@ -23,13 +23,13 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"zod": "^4.1.8"
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+2 -2
View File
@@ -30,7 +30,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+3 -3
View File
@@ -27,14 +27,14 @@
"@homarr/db": "workspace:^",
"@homarr/definitions": "workspace:^",
"@homarr/log": "workspace:^",
"ioredis": "5.7.0",
"ioredis": "5.8.1",
"superjson": "2.2.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+4 -3
View File
@@ -32,13 +32,14 @@
"dayjs": "^1.11.18",
"octokit": "^5.0.3",
"superjson": "2.2.2",
"undici": "7.16.0"
"undici": "7.16.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+3 -2
View File
@@ -47,7 +47,7 @@ async function getContainersWithStatsAsync() {
const instance = dockerInstances.find(({ host }) => host === container.instance)?.instance;
if (!instance) return null;
const stats = await instance.getContainer(container.Id).stats({ stream: false });
const stats = await instance.getContainer(container.Id).stats({ stream: false, "one-shot": true });
return {
id: container.Id,
@@ -60,7 +60,8 @@ async function getContainersWithStatsAsync() {
return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
})?.url ?? null,
cpuUsage: calculateCpuUsage(stats),
memoryUsage: stats.memory_stats.usage,
// memory usage by default includes cache, which should not be shown as it is also not shown with docker stats command
memoryUsage: stats.memory_stats.usage - stats.memory_stats.stats.cache,
image: container.Image,
ports: container.Ports,
};
@@ -7,7 +7,7 @@ import type { TdarrQueue, TdarrStatistics, TdarrWorker } from "@homarr/integrati
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
export const mediaTranscodingRequestHandler = createCachedIntegrationRequestHandler<
{ queue: TdarrQueue; workers: TdarrWorker[]; statistics: TdarrStatistics },
MediaTranscoding,
IntegrationKindByCategory<"mediaTranscoding">,
{ pageOffset: number; pageSize: number }
>({
@@ -22,3 +22,9 @@ export const mediaTranscodingRequestHandler = createCachedIntegrationRequestHand
};
},
});
export interface MediaTranscoding {
queue: TdarrQueue;
workers: TdarrWorker[];
statistics: TdarrStatistics;
}
+70
View File
@@ -0,0 +1,70 @@
import dayjs from "dayjs";
import { z } from "zod";
import { fetchWithTimeout } from "@homarr/common";
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
export const weatherRequestHandler = createCachedWidgetRequestHandler({
queryKey: "weatherAtLocation",
widgetKind: "weather",
async requestAsync(input: { latitude: number; longitude: number }) {
const res = await fetchWithTimeout(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`,
);
const json: unknown = await res.json();
const weather = await atLocationOutput.parseAsync(json);
return {
current: weather.current_weather,
daily: weather.daily.time.map((value, index) => {
return {
time: value,
weatherCode: weather.daily.weathercode[index] ?? 404,
maxTemp: weather.daily.temperature_2m_max[index],
minTemp: weather.daily.temperature_2m_min[index],
sunrise: weather.daily.sunrise[index],
sunset: weather.daily.sunset[index],
maxWindSpeed: weather.daily.wind_speed_10m_max[index],
maxWindGusts: weather.daily.wind_gusts_10m_max[index],
};
}),
} satisfies Weather;
},
cacheDuration: dayjs.duration(1, "minute"),
});
const atLocationOutput = z.object({
current_weather: z.object({
weathercode: z.number(),
temperature: z.number(),
windspeed: z.number(),
}),
daily: z.object({
time: z.array(z.string()),
weathercode: z.array(z.number()),
temperature_2m_max: z.array(z.number()),
temperature_2m_min: z.array(z.number()),
sunrise: z.array(z.string()),
sunset: z.array(z.string()),
wind_speed_10m_max: z.array(z.number()),
wind_gusts_10m_max: z.array(z.number()),
}),
});
export interface Weather {
current: {
weathercode: number;
temperature: number;
windspeed: number;
};
daily: {
time: string;
weatherCode: number;
maxTemp: number | undefined;
minTemp: number | undefined;
sunrise: string | undefined;
sunset: string | undefined;
maxWindSpeed: number | undefined;
maxWindGusts: number | undefined;
}[];
}
+2 -2
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+6 -6
View File
@@ -26,16 +26,16 @@
"@homarr/api": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.3.1",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1"
"@mantine/dates": "^8.3.4",
"next": "15.5.4",
"react": "19.2.0",
"react-dom": "19.2.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+10 -10
View File
@@ -33,21 +33,21 @@
"@homarr/settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@mantine/core": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/spotlight": "^8.3.1",
"@tabler/icons-react": "^3.34.1",
"jotai": "^2.14.0",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/spotlight": "^8.3.4",
"@tabler/icons-react": "^3.35.0",
"jotai": "^2.15.0",
"next": "15.5.4",
"react": "19.2.0",
"react-dom": "19.2.0",
"use-deep-compare-effect": "^1.8.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
@@ -6,6 +6,7 @@ import { ActionIcon, Center, Group, Kbd } from "@mantine/core";
import { Spotlight as MantineSpotlight } from "@mantine/spotlight";
import { IconQuestionMark, IconSearch, IconX } from "@tabler/icons-react";
import { hotkeys } from "@homarr/definitions";
import type { TranslationObject } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
@@ -49,6 +50,7 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM
return (
<MantineSpotlight.Root
shortcut={hotkeys.openSpotlight}
yOffset={8}
onSpotlightClose={() => {
setMode(defaultMode);
@@ -3,6 +3,7 @@ import { IconCheck } from "@tabler/icons-react";
import { localeConfigurations, supportedLanguages } from "@homarr/translation";
import { useChangeLocale, useCurrentLocale, useI18n } from "@homarr/translation/client";
import { LanguageIcon } from "@homarr/ui";
import { createChildrenOptions } from "../../../lib/children";
@@ -34,7 +35,7 @@ export const languageChildrenOptions = createChildrenOptions<Record<string, unkn
return (
<Group mx="md" my="sm" wrap="nowrap" justify="space-between" w="100%">
<Group wrap="nowrap">
<span className={`fi fi-${configuration.flagIcon}`} style={{ borderRadius: 4 }}></span>
<LanguageIcon icon={localeConfigurations[localeKey].icon} />
<Group wrap="nowrap" gap="xs">
<Text>{configuration.name}</Text>
<Text size="xs" c="dimmed" inherit>
+6 -6
View File
@@ -32,16 +32,16 @@
"dayjs": "^1.11.18",
"deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.5.3",
"next-intl": "4.3.8",
"react": "19.1.1",
"react-dom": "19.1.1"
"next": "15.5.4",
"next-intl": "4.3.12",
"react": "19.2.0",
"react-dom": "19.2.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.35.0",
"typescript": "^5.9.2"
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
+62 -34
View File
@@ -6,7 +6,7 @@ export const localeConfigurations = {
ca: {
name: "Català",
translatedName: "Catalan",
flagIcon: "es-ct",
icon: flagIcon("es-ct"),
importMrtLocalization() {
return import("./mantine-react-table/ca.json");
},
@@ -17,7 +17,7 @@ export const localeConfigurations = {
cn: {
name: "中文",
translatedName: "Chinese (Simplified)",
flagIcon: "cn",
icon: flagIcon("cn"),
importMrtLocalization() {
return import("mantine-react-table/locales/zh-Hans/index.esm.mjs").then(
(module) => module.MRT_Localization_ZH_HANS,
@@ -27,10 +27,24 @@ export const localeConfigurations = {
return import("dayjs/locale/zh-cn").then((module) => module.default);
},
},
cr: {
name: "Crowdin",
translatedName: "Live translation",
icon: {
type: "custom" as const,
url: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/crowdin.svg",
},
importMrtLocalization() {
return import("mantine-react-table/locales/en/index.esm.mjs").then((module) => module.MRT_Localization_EN);
},
importDayJsLocale() {
return import("dayjs/locale/en-gb").then((module) => module.default);
},
},
cs: {
name: "Čeština",
translatedName: "Czech",
flagIcon: "cz",
icon: flagIcon("cz"),
importMrtLocalization() {
return import("mantine-react-table/locales/cs/index.esm.mjs").then((module) => module.MRT_Localization_CS);
},
@@ -41,7 +55,7 @@ export const localeConfigurations = {
da: {
name: "Dansk",
translatedName: "Danish",
flagIcon: "dk",
icon: flagIcon("dk"),
importMrtLocalization() {
return import("mantine-react-table/locales/da/index.esm.mjs").then((module) => module.MRT_Localization_DA);
},
@@ -52,7 +66,7 @@ export const localeConfigurations = {
de: {
name: "Deutsch",
translatedName: "German",
flagIcon: "de",
icon: flagIcon("de"),
importMrtLocalization() {
return import("mantine-react-table/locales/de/index.esm.mjs").then((module) => module.MRT_Localization_DE);
},
@@ -63,7 +77,7 @@ export const localeConfigurations = {
"de-CH": {
name: "Deutsch (Schweiz)",
translatedName: "German (Swiss)",
flagIcon: "ch",
icon: flagIcon("ch"),
importMrtLocalization() {
return import("mantine-react-table/locales/de/index.esm.mjs").then((module) => module.MRT_Localization_DE);
},
@@ -74,7 +88,7 @@ export const localeConfigurations = {
"en-gb": {
name: "English (UK)",
translatedName: "English (UK)",
flagIcon: "gb",
icon: flagIcon("gb"),
importMrtLocalization() {
return import("mantine-react-table/locales/en/index.esm.mjs").then((module) => module.MRT_Localization_EN);
},
@@ -85,7 +99,7 @@ export const localeConfigurations = {
en: {
name: "English (US)",
translatedName: "English (US)",
flagIcon: "us",
icon: flagIcon("us"),
importMrtLocalization() {
return import("mantine-react-table/locales/en/index.esm.mjs").then((module) => module.MRT_Localization_EN);
},
@@ -96,7 +110,7 @@ export const localeConfigurations = {
el: {
name: "Ελληνικά",
translatedName: "Greek",
flagIcon: "gr",
icon: flagIcon("gr"),
importMrtLocalization() {
return import("mantine-react-table/locales/el/index.esm.mjs").then((module) => module.MRT_Localization_EL);
},
@@ -107,7 +121,7 @@ export const localeConfigurations = {
es: {
name: "Español",
translatedName: "Spanish",
flagIcon: "es",
icon: flagIcon("es"),
importMrtLocalization() {
return import("mantine-react-table/locales/es/index.esm.mjs").then((module) => module.MRT_Localization_ES);
},
@@ -118,7 +132,7 @@ export const localeConfigurations = {
et: {
name: "Eesti",
translatedName: "Estonian",
flagIcon: "ee",
icon: flagIcon("ee"),
importMrtLocalization() {
return import("mantine-react-table/locales/et/index.esm.mjs").then((module) => module.MRT_Localization_ET);
},
@@ -129,7 +143,7 @@ export const localeConfigurations = {
fr: {
name: "Français",
translatedName: "French",
flagIcon: "fr",
icon: flagIcon("fr"),
importMrtLocalization() {
return import("mantine-react-table/locales/fr/index.esm.mjs").then((module) => module.MRT_Localization_FR);
},
@@ -140,7 +154,7 @@ export const localeConfigurations = {
he: {
name: "עברית",
translatedName: "Hebrew",
flagIcon: "il",
icon: flagIcon("il"),
isRTL: true,
importMrtLocalization() {
return import("mantine-react-table/locales/he/index.esm.mjs").then((module) => module.MRT_Localization_HE);
@@ -152,7 +166,7 @@ export const localeConfigurations = {
hr: {
name: "Hrvatski",
translatedName: "Croatian",
flagIcon: "hr",
icon: flagIcon("hr"),
importMrtLocalization() {
return import("mantine-react-table/locales/hr/index.esm.mjs").then((module) => module.MRT_Localization_HR);
},
@@ -163,7 +177,7 @@ export const localeConfigurations = {
hu: {
name: "Magyar",
translatedName: "Hungarian",
flagIcon: "hu",
icon: flagIcon("hu"),
importMrtLocalization() {
return import("mantine-react-table/locales/hu/index.esm.mjs").then((module) => module.MRT_Localization_HU);
},
@@ -174,7 +188,7 @@ export const localeConfigurations = {
it: {
name: "Italiano",
translatedName: "Italian",
flagIcon: "it",
icon: flagIcon("it"),
importMrtLocalization() {
return import("mantine-react-table/locales/it/index.esm.mjs").then((module) => module.MRT_Localization_IT);
},
@@ -185,7 +199,7 @@ export const localeConfigurations = {
ja: {
name: "日本語",
translatedName: "Japanese",
flagIcon: "jp",
icon: flagIcon("jp"),
importMrtLocalization() {
return import("mantine-react-table/locales/ja/index.esm.mjs").then((module) => module.MRT_Localization_JA);
},
@@ -196,7 +210,7 @@ export const localeConfigurations = {
ko: {
name: "한국어",
translatedName: "Korean",
flagIcon: "kr",
icon: flagIcon("kr"),
importMrtLocalization() {
return import("mantine-react-table/locales/ko/index.esm.mjs").then((module) => module.MRT_Localization_KO);
},
@@ -207,7 +221,7 @@ export const localeConfigurations = {
lt: {
name: "Lietuvių",
translatedName: "Lithuanian",
flagIcon: "lt",
icon: flagIcon("lt"),
importMrtLocalization() {
return import("./mantine-react-table/lt.json");
},
@@ -218,7 +232,7 @@ export const localeConfigurations = {
lv: {
name: "Latviešu",
translatedName: "Latvian",
flagIcon: "lv",
icon: flagIcon("lv"),
importMrtLocalization() {
return import("./mantine-react-table/lv.json");
},
@@ -229,7 +243,7 @@ export const localeConfigurations = {
nl: {
name: "Nederlands",
translatedName: "Dutch",
flagIcon: "nl",
icon: flagIcon("nl"),
importMrtLocalization() {
return import("mantine-react-table/locales/nl/index.esm.mjs").then((module) => module.MRT_Localization_NL);
},
@@ -240,7 +254,7 @@ export const localeConfigurations = {
no: {
name: "Norsk",
translatedName: "Norwegian",
flagIcon: "no",
icon: flagIcon("no"),
importMrtLocalization() {
return import("mantine-react-table/locales/no/index.esm.mjs").then((module) => module.MRT_Localization_NO);
},
@@ -251,7 +265,7 @@ export const localeConfigurations = {
pl: {
name: "Polski",
translatedName: "Polish",
flagIcon: "pl",
icon: flagIcon("pl"),
importMrtLocalization() {
return import("mantine-react-table/locales/pl/index.esm.mjs").then((module) => module.MRT_Localization_PL);
},
@@ -262,7 +276,7 @@ export const localeConfigurations = {
pt: {
name: "Português",
translatedName: "Portuguese",
flagIcon: "pt",
icon: flagIcon("pt"),
importMrtLocalization() {
return import("mantine-react-table/locales/pt/index.esm.mjs").then((module) => module.MRT_Localization_PT);
},
@@ -273,7 +287,7 @@ export const localeConfigurations = {
ro: {
name: "Românesc",
translatedName: "Romanian",
flagIcon: "ro",
icon: flagIcon("ro"),
importMrtLocalization() {
return import("mantine-react-table/locales/ro/index.esm.mjs").then((module) => module.MRT_Localization_RO);
},
@@ -284,7 +298,7 @@ export const localeConfigurations = {
ru: {
name: "Русский",
translatedName: "Russian",
flagIcon: "ru",
icon: flagIcon("ru"),
importMrtLocalization() {
return import("mantine-react-table/locales/ru/index.esm.mjs").then((module) => module.MRT_Localization_RU);
},
@@ -295,7 +309,7 @@ export const localeConfigurations = {
sk: {
name: "Slovenčina",
translatedName: "Slovak",
flagIcon: "sk",
icon: flagIcon("sk"),
importMrtLocalization() {
return import("mantine-react-table/locales/sk/index.esm.mjs").then((module) => module.MRT_Localization_SK);
},
@@ -306,7 +320,7 @@ export const localeConfigurations = {
sl: {
name: "Slovenščina",
translatedName: "Slovenian",
flagIcon: "si",
icon: flagIcon("si"),
importMrtLocalization() {
return import("./mantine-react-table/sl.json");
},
@@ -317,7 +331,7 @@ export const localeConfigurations = {
sv: {
name: "Svenska",
translatedName: "Swedish",
flagIcon: "se",
icon: flagIcon("se"),
importMrtLocalization() {
return import("mantine-react-table/locales/sv/index.esm.mjs").then((module) => module.MRT_Localization_SV);
},
@@ -328,7 +342,7 @@ export const localeConfigurations = {
tr: {
name: "Türkçe",
translatedName: "Turkish",
flagIcon: "tr",
icon: flagIcon("tr"),
importMrtLocalization() {
return import("mantine-react-table/locales/tr/index.esm.mjs").then((module) => module.MRT_Localization_TR);
},
@@ -339,7 +353,7 @@ export const localeConfigurations = {
zh: {
name: "中文",
translatedName: "Chinese (Traditional)",
flagIcon: "tw",
icon: flagIcon("tw"),
importMrtLocalization() {
return import("mantine-react-table/locales/zh-Hant/index.esm.mjs").then(
(module) => module.MRT_Localization_ZH_HANT,
@@ -352,7 +366,7 @@ export const localeConfigurations = {
uk: {
name: "Українська",
translatedName: "Ukrainian",
flagIcon: "ua",
icon: flagIcon("ua"),
importMrtLocalization() {
return import("mantine-react-table/locales/uk/index.esm.mjs").then((module) => module.MRT_Localization_UK);
},
@@ -363,7 +377,7 @@ export const localeConfigurations = {
vi: {
name: "Tiếng Việt",
translatedName: "Vietnamese",
flagIcon: "vn",
icon: flagIcon("vn"),
importMrtLocalization() {
return import("mantine-react-table/locales/vi/index.esm.mjs").then((module) => module.MRT_Localization_VI);
},
@@ -376,13 +390,27 @@ export const localeConfigurations = {
{
name: string;
translatedName: string;
flagIcon: string;
icon: LanguageIconDefinition;
importMrtLocalization: () => Promise<MRT_Localization>;
importDayJsLocale: () => Promise<ILocale>;
isRTL?: boolean;
}
>;
function flagIcon<TCode extends string>(flag: TCode) {
return { type: "flag" as const, flag };
}
export type LanguageIconDefinition =
| {
type: "flag";
flag: string;
}
| {
type: "custom";
url: string;
};
export const supportedLanguages = objectKeys(localeConfigurations);
export type SupportedLanguage = (typeof supportedLanguages)[number];
+95 -4
View File
@@ -945,6 +945,10 @@
"label": "",
"newLabel": ""
},
"url": {
"label": "",
"newLabel": ""
},
"opnsenseApiKey": {
"label": "",
"newLabel": ""
@@ -1141,6 +1145,12 @@
"groupNameTaken": ""
}
}
},
"unit": {
"speed": {
"kilometersPerHour": "",
"milesPerHour": ""
}
}
},
"section": {
@@ -1153,6 +1163,9 @@
"title": {
"label": ""
},
"customCssClasses": {
"label": ""
},
"borderColor": {
"label": ""
}
@@ -1543,7 +1556,15 @@
"width": "",
"height": ""
},
"placeholder": ""
"placeholder": "",
"dismiss": {
"title": "",
"message": "",
"action": {
"discard": "",
"keepEditing": ""
}
}
},
"iframe": {
"name": "",
@@ -1695,6 +1716,9 @@
"calendar": {
"name": "",
"description": "",
"duration": {
"allDay": ""
},
"option": {
"releaseType": {
"label": "",
@@ -1729,6 +1753,9 @@
"label": "",
"description": ""
},
"useImperialSpeed": {
"label": ""
},
"location": {
"label": ""
},
@@ -1965,7 +1992,25 @@
"currentlyPlaying": "",
"user": "",
"name": "",
"id": ""
"id": "",
"metadata": {
"title": "",
"video": {
"title": "",
"resolution": ""
},
"audio": {
"title": "",
"channelCount": "",
"codec": ""
},
"transcoding": {
"title": "",
"container": "",
"resolution": "",
"target": ""
}
}
}
},
"downloads": {
@@ -2490,7 +2535,29 @@
"systemResources": {
"name": "",
"description": "",
"option": {},
"option": {
"hasShadow": {
"label": ""
},
"visibleCharts": {
"label": "",
"description": "",
"option": {
"cpu": "",
"memory": "",
"network": ""
}
},
"labelDisplayMode": {
"label": "",
"option": {
"textWithIcon": "",
"text": "",
"icon": "",
"hidden": ""
}
}
},
"card": {
"cpu": "",
"memory": "",
@@ -2919,13 +2986,19 @@
"invite": "",
"integration": "",
"app": "",
"group": ""
"group": "",
"searchEngine": "",
"media": ""
},
"statisticLabel": {
"boards": "",
"resources": "",
"authentication": "",
"authorization": ""
},
"heroBanner": {
"title": "",
"subtitle": ""
}
},
"board": {
@@ -3281,6 +3354,9 @@
},
"firewallInterfaces": {
"label": ""
},
"weather": {
"label": ""
}
},
"interval": {
@@ -3406,6 +3482,21 @@
"libraries": {
"title": "",
"subtitle": ""
},
"hotkeys": {
"title": "",
"subtitle": "",
"field": {
"shortcut": "",
"action": ""
},
"action": {
"toggleBoardEdit": "",
"toggleColorScheme": "",
"saveNotebook": "",
"openSpotlight": ""
},
"note": ""
}
}
}
+119 -28
View File
@@ -945,25 +945,29 @@
"label": "主题",
"newLabel": "新主题"
},
"url": {
"label": "网址",
"newLabel": "新网址"
},
"opnsenseApiKey": {
"label": "",
"newLabel": ""
"label": "API 密钥 (Key)",
"newLabel": "新 API 密钥 (Key)"
},
"opnsenseApiSecret": {
"label": "",
"newLabel": ""
"label": "API 密钥 (保密)",
"newLabel": "新 API 密钥 (保密)"
},
"githubAppId": {
"label": "",
"newLabel": ""
"label": "应用ID",
"newLabel": "新应用ID"
},
"githubInstallationId": {
"label": "",
"newLabel": ""
"label": "安装 ID",
"newLabel": "新安装 ID"
},
"privateKey": {
"label": "",
"newLabel": ""
"label": "私人密钥",
"newLabel": "新私人密钥"
}
}
},
@@ -1015,7 +1019,7 @@
}
},
"common": {
"success": "",
"success": "成功",
"beta": "测试版",
"error": "错误",
"action": {
@@ -1141,6 +1145,12 @@
"groupNameTaken": "用户组名称已存在"
}
}
},
"unit": {
"speed": {
"kilometersPerHour": "",
"milesPerHour": ""
}
}
},
"section": {
@@ -1153,6 +1163,9 @@
"title": {
"label": "标题"
},
"customCssClasses": {
"label": ""
},
"borderColor": {
"label": "边界颜色"
}
@@ -1282,21 +1295,21 @@
"label": "启用状态检查"
},
"layout": {
"label": "",
"label": "界面",
"option": {
"row": "",
"row-reverse": "",
"column": "",
"column-reverse": ""
"row": "横向",
"row-reverse": "横向 (反转)",
"column": "垂直",
"column-reverse": "垂直 (反转)"
}
},
"descriptionDisplayMode": {
"label": "",
"description": "",
"label": "描述信息显示模式",
"description": "选择应用描述的显示方式",
"option": {
"normal": "",
"tooltip": "",
"hidden": ""
"normal": "窗口组件内",
"tooltip": "显示为悬停提示",
"hidden": "隐藏"
}
}
},
@@ -1543,7 +1556,15 @@
"width": "宽度",
"height": "高度"
},
"placeholder": "开始写笔记"
"placeholder": "开始写笔记",
"dismiss": {
"title": "是否放弃修改?",
"message": "仍有未保存的更改,确定要丢弃吗?",
"action": {
"discard": "放弃修改",
"keepEditing": "继续编辑"
}
}
},
"iframe": {
"name": "iFrame",
@@ -1695,6 +1716,9 @@
"calendar": {
"name": "日历",
"description": "在日历视图中显示某个相对时间段内的集成事件",
"duration": {
"allDay": ""
},
"option": {
"releaseType": {
"label": "Radarr 发布类型",
@@ -1729,6 +1753,9 @@
"label": "显示当前风速",
"description": "仅在当前天气时"
},
"useImperialSpeed": {
"label": ""
},
"location": {
"label": "天气位置"
},
@@ -1965,7 +1992,25 @@
"currentlyPlaying": "正在播放",
"user": "用户",
"name": "名称",
"id": "ID"
"id": "ID",
"metadata": {
"title": "",
"video": {
"title": "",
"resolution": ""
},
"audio": {
"title": "",
"channelCount": "",
"codec": ""
},
"transcoding": {
"title": "",
"container": "",
"resolution": "",
"target": ""
}
}
}
},
"downloads": {
@@ -2273,7 +2318,7 @@
"label": "帖子数量限制"
},
"hideDescription": {
"label": ""
"label": "隐藏描述"
}
}
},
@@ -2488,9 +2533,31 @@
"option": {}
},
"systemResources": {
"name": "",
"name": "系统资源",
"description": "",
"option": {},
"option": {
"hasShadow": {
"label": ""
},
"visibleCharts": {
"label": "",
"description": "",
"option": {
"cpu": "",
"memory": "",
"network": ""
}
},
"labelDisplayMode": {
"label": "",
"option": {
"textWithIcon": "",
"text": "",
"icon": "",
"hidden": ""
}
}
},
"card": {
"cpu": "CPU",
"memory": "",
@@ -2919,13 +2986,19 @@
"invite": "邀请",
"integration": "集成",
"app": "应用",
"group": "群组"
"group": "群组",
"searchEngine": "",
"media": ""
},
"statisticLabel": {
"boards": "面板",
"resources": "资源",
"authentication": "认证",
"authorization": "认证"
},
"heroBanner": {
"title": "",
"subtitle": ""
}
},
"board": {
@@ -3281,6 +3354,9 @@
},
"firewallInterfaces": {
"label": "防火墙接口"
},
"weather": {
"label": ""
}
},
"interval": {
@@ -3303,7 +3379,7 @@
},
"field": {
"name": {
"label": ""
"label": "名称"
},
"interval": {
"label": "计划时间间隔"
@@ -3406,6 +3482,21 @@
"libraries": {
"title": "相关的库",
"subtitle": "{count} 用于 Homarr 代码中"
},
"hotkeys": {
"title": "",
"subtitle": "",
"field": {
"shortcut": "",
"action": ""
},
"action": {
"toggleBoardEdit": "",
"toggleColorScheme": "",
"saveNotebook": "",
"openSpotlight": ""
},
"note": ""
}
}
}
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff. Show more