Compare commits

...
Author SHA1 Message Date
Meier Lukas 8dbe5a93d6 chore: add temporary logging of response content for overseerr approval 2025-05-24 19:07:34 +02:00
Releases Homarr f748a12b58 chore(release): 1.21.0 [skip ci] 2025-05-23 19:17:12 +00:00
homarr-releases[bot] 6b746b09b5 chore(release): automatic release v1.21.0 2025-05-23 19:15:16 +00:00
e1eda534da feat: docker widget (#2288)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: homarr-crowdin[bot] <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-23 18:35:04 +00:00
Meier Lukas 09f4e6785b fix(downloads): reordering columns in widget does not work (#3194) 2025-05-23 18:33:35 +00:00
Meier Lukas 09f3989483 fix(items): resize handle overlaps header (#3193) 2025-05-23 18:33:02 +00:00
Andre Silva 4cb004fce0 fix(releases-widget): add hardcoded css classNames(#3181) 2025-05-23 18:17:06 +02:00
homarr-renovate[bot] 0ef2310776 chore(deps): update node.js to v22.16.0 (#3180)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-23 18:11:43 +02:00
homarr-update-contributors[bot] b5bc8379ae chore: update contributors (#3192)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-05-23 12:55:50 +00:00
homarr-renovate[bot] 43b82419e9 fix(deps): update testcontainers-node monorepo to ^10.28.0 (#3191)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-23 10:23:03 +00:00
homarr-renovate[bot] 329d4727f1 chore(deps): update dependency @vitejs/plugin-react to ^4.5.0 (#3189)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-23 01:16:09 +00:00
homarr-renovate[bot] 9840644ca5 fix(deps): update dependency zod to ^3.25.23 (#3188)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-22 23:20:48 +00:00
homarr-renovate[bot] 37d637dcae fix(deps): update dependency zod to ^3.25.21 (#3187)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-22 21:20:03 +00:00
homarr-renovate[bot] 1584114e72 fix(deps): update tanstack-query monorepo to ^5.76.2 (#3186)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-22 16:24:12 +00:00
homarr-renovate[bot] 49498c870e fix(deps): update dependency zod to ^3.25.20 (#3183)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 23:20:30 +00:00
homarr-renovate[bot] 5466ff5047 chore(deps): update node.js to v22.16.0 (#3179)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 16:25:29 +00:00
homarr-renovate[bot] 1e7b2e617b fix(deps): update dependency swagger-ui-react to ^5.22.0 (#3178)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 13:34:52 +00:00
homarr-renovate[bot] 371803ac50 fix(deps): update dependency zod to ^3.25.17 (#3177)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 12:35:21 +00:00
homarr-renovate[bot] 746ada97be chore(deps): update dependency @types/react to v19.1.5 (#3176)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 11:19:20 +00:00
homarr-renovate[bot] 5080f51a8d fix(deps): update dependency zod to ^3.25.16 (#3175)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 10:22:29 +00:00
homarr-renovate[bot] 23404a27f1 fix(deps): update dependency zod to ^3.25.14 (#3174)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 09:23:12 +00:00
homarr-renovate[bot] dae6dedde5 fix(deps): update dependency zod to ^3.25.13 (#3173)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 04:25:16 +00:00
homarr-renovate[bot] caaee503c1 fix(deps): update dependency zod to ^3.25.12 (#3172)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-21 03:15:38 +00:00
homarr-crowdin[bot] a356d61808 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-21 00:52:08 +00:00
homarr-renovate[bot] 72bcbe8eff fix(deps): update dependency zod to ^3.25.8 (#3170)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 23:20:46 +00:00
homarr-renovate[bot] 145365fab7 chore(deps): update dependency @types/node to ^22.15.21 (#3169)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 23:20:28 +00:00
homarr-renovate[bot] 8c59abdde3 fix(deps): update dependency octokit to v5 (#3156)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 21:36:43 +02:00
homarr-renovate[bot] 47869a3af4 chore(deps): update dependency @types/dockerode to ^3.3.39 (#3166)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 19:18:32 +00:00
homarr-renovate[bot] 5403b9c95e chore(deps): update dependency @types/node to ^22.15.20 (#3167)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 18:27:17 +00:00
homarr-renovate[bot] 50445b560e fix(deps): update dependency octokit to ^4.1.4 (#3165)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 16:24:19 +00:00
homarr-renovate[bot] 18c465e81e fix(deps): update testcontainers-node monorepo to ^10.27.0 (#3159)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 09:23:43 +00:00
homarr-renovate[bot] eb5d5f8260 fix(deps): update dependency undici to v7.10.0 (#3158)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 08:27:45 +00:00
homarr-renovate[bot] 096d37be37 chore(deps): update dependency proxmox-api&gt;undici to v7.10.0 (#3157)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-20 08:26:49 +00:00
homarr-crowdin[bot] 279a4b6add chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-20 00:52:28 +00:00
homarr-renovate[bot] 22954b8d26 fix(deps): update dependency zod to ^3.25.7 (#3154)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 23:20:23 +00:00
homarr-renovate[bot] 92ebc05fbf fix(deps): update dependency zod to ^3.25.6 (#3153)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 22:20:45 +00:00
homarr-renovate[bot] 79755c95d3 fix(deps): update dependency zod to ^3.25.5 (#3152)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 21:20:22 +00:00
homarr-renovate[bot] 49b7080415 fix(deps): update dependency zod to ^3.25.4 (#3150)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 20:22:49 +00:00
homarr-renovate[bot] 5b9f75eb45 chore(deps): update vitest monorepo to ^3.1.4 (#3149)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 17:20:34 +00:00
homarr-renovate[bot] 6e2aa87102 fix(deps): update dependency zod to ^3.25.3 (#3148)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 16:25:04 +00:00
homarr-renovate[bot] 17fbb0fc41 chore(deps): update dependency prettier-plugin-packagejson to ^2.5.14 (#3147)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 12:36:05 +00:00
homarr-renovate[bot] 28be77b9c3 chore(deps): update dependency @types/node to ^22.15.19 (#3145)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-19 01:18:27 +00:00
homarr-crowdin[bot] adddae6430 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-18 08:06:41 +00:00
Meier Lukas ef540e980f fix: esbuild not part of dependencies (#3139) 2025-05-17 20:04:05 +02:00
homarr-renovate[bot] 15675d95bd chore(deps): update dependency semantic-release to ^24.2.4 (#3134)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-17 01:13:09 +00:00
homarr-crowdin[bot] 16e2f3f1e4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-17 00:50:13 +00:00
homarr-renovate[bot] bf6b7912f9 chore(deps): update dependency eslint to ^9.27.0 (#3128)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-16 20:24:10 +00:00
homarr-renovate[bot] ee8870b66b fix(deps): update dependency @tabler/icons-react to ^3.33.0 (#3132)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-16 20:23:22 +00:00
Meier Lukas c6464e582e chore(issue-template): add version 1.20.0 2025-05-16 21:27:28 +02:00
homarr-renovate[bot] 886e95d517 fix(deps): update dependency zod-validation-error to ^3.4.1 (#3127)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-16 19:19:41 +00:00
Releases Homarr 25cf9ad7ca chore(release): 1.20.0 [skip ci] 2025-05-16 19:18:07 +00:00
homarr-releases[bot] dec3e9a653 chore(release): automatic release v1.20.0 2025-05-16 19:15:02 +00:00
Meier Lukas ef9a5e9895 feat(integration): improve integration test connection (#3005) 2025-05-16 20:59:12 +02:00
Meier LukasandManuel 3daf1c8341 feat(auth): add account linking for oidc providers (#3106)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2025-05-16 20:57:51 +02:00
Meier Lukas b86159ba7c fix(calendar): some timezones result in offset of one day (#3120) 2025-05-16 18:13:05 +00:00
Meier Lukas 42e7fe3273 fix(deps): missing dependencies in definitions package for docs link codegen (#3119) 2025-05-16 20:10:17 +02:00
homarr-update-contributors[bot] dd250660b8 chore: update contributors (#3125)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-05-16 18:02:43 +00:00
Meier Lukas 6e03e5a40c fix: updated contributors contain unnecessary data (#3124) 2025-05-16 19:57:20 +02:00
tech62andMeier Lukas 9b8320c10d feat: add css class to logo for custom css (#3121)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-16 15:12:46 +00:00
Andre Silva 94134303d7 feat(releases-widget): added auto icon search when editing a repository (#3087) 2025-05-16 16:33:23 +02:00
homarr-renovate[bot] b87e6bcaf2 chore(deps): update node.js to v22.15.1 (#3116)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-16 16:19:23 +02:00
homarr-renovate[bot] 5b16468cab fix(deps): update dependency sass to ^1.89.0 (#3118)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-16 01:16:45 +00:00
homarr-renovate[bot] b32278164e chore(deps): update dependency prettier-plugin-packagejson to ^2.5.13 (#3115)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-15 12:35:54 +00:00
homarr-renovate[bot] cb81efa42c fix(deps): update testcontainers-node monorepo to ^10.26.0 (#3114)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-15 10:23:42 +00:00
homarr-renovate[bot] dc34048ec2 chore(deps): update node.js to v22.15.1 (#3110)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-14 21:19:11 +00:00
homarr-renovate[bot] 25efe3d335 fix(deps): update mantine monorepo to ^8.0.1 (#3107)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-14 13:34:50 +00:00
homarr-renovate[bot] d96bc898fd fix(deps): update dependency @extractus/feed-extractor to v7.1.6 (#3105)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-14 09:21:16 +00:00
homarr-renovate[bot] 56427ef794 chore(deps): update dependency @types/node to ^22.15.18 (#3103)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-14 05:22:20 +00:00
homarr-renovate[bot] c1e5d77176 fix(deps): update tanstack-query monorepo to ^5.76.1 (#3101)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-13 22:20:30 +00:00
homarr-renovate[bot] 6703155c61 chore(deps): update pnpm to v10.11.0 (#3097)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-13 15:57:03 +00:00
homarr-renovate[bot] f776cd6934 chore(deps): update dependency @types/react-dom to v19.1.5 (#3096)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-13 11:19:02 +00:00
homarr-renovate[bot] 93b122c655 chore(deps): update dependency @types/react to v19.1.4 (#3095)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-12 20:23:30 +00:00
homarr-renovate[bot] 42b377d067 fix(deps): update dependency typescript-eslint to ^8.32.1 (#3093)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-12 18:27:38 +00:00
Meier Lukas 6c71724e2e ci: add update-contributors workflow (#3014) 2025-05-12 18:01:27 +02:00
Andre Silva 593d97e27a fix(releases-widget): reduced padding for ul in release description (#3085) 2025-05-12 16:00:01 +00:00
homarr-renovate[bot] ac49910e6e chore(deps): update dependency @types/react-dom to v19.1.4 (#3092)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-12 12:35:06 +00:00
Andre Silva 18fe8ecd39 fix(releases-widget): validation preventing removal of a previously added icon (#3086)
* fix(releases-widget): validation preventing removal of a previously added icon

* fix(releases-widget): bug with input controlled/uncontrolled state
2025-05-12 10:50:16 +00:00
homarr-renovate[bot] 388bc7392c fix(deps): update tanstack-query monorepo to ^5.76.0 (#3090)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-12 09:23:35 +00:00
homarr-crowdin[bot] 2f9f561a8e chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-12 00:54:31 +00:00
Andre Silva 72862efae2 fix(releases-widget): delete the empty repository when canceling Add Reposotory (#3082) 2025-05-11 20:11:55 +00:00
homarr-renovate[bot] 5686910a47 fix(deps): update dependency bcrypt to v6 (#3083)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-11 21:58:25 +02:00
Andre Silva 59cae02623 fix(releases-widget): removed version regex in options overview (#3081) 2025-05-11 15:30:31 +00:00
Meier Lukas e931b19661 chore(docs): update docs-sitemap 2025-05-11 15:38:24 +02:00
homarr-crowdin[bot] 8ff01eb17f chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-11 00:56:05 +00:00
homarr-renovate[bot] b0f7310cb7 fix(deps): update dependency sass to ^1.88.0 (#3078)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 23:20:18 +00:00
8c1b365733 feat(releases-widget): limit number of displayed releases and custom name for repositories (#2974)
Co-authored-by: Andre Silva <asilva01@acuitysso.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-10 22:06:14 +02:00
homarr-renovate[bot] b8bdf1836a chore(deps): update dependency proxmox-api&gt;undici to v7.9.0 (#3076)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 20:00:30 +00:00
homarr-renovate[bot] 01d53eee60 fix(deps): update dependency undici to v7.9.0 (#3077)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 19:18:44 +00:00
Meier Lukas 7f3574f8ba chore(issue-template): add version 1.19.1 2025-05-10 15:27:37 +02:00
homarr-renovate[bot] 3f9f94ea45 fix(deps): update dependency @auth/drizzle-adapter to ^1.9.1 (#3073)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 07:19:27 +00:00
homarr-renovate[bot] 98773e7dba fix(deps): update dependency next-auth to v5.0.0-beta.28 (#3074)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 06:25:49 +00:00
homarr-renovate[bot] 0d2311975c fix(deps): update dependency @auth/core to ^0.39.1 (#3072)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-10 06:24:50 +00:00
homarr-crowdin[bot] a3af95734e chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-10 00:49:16 +00:00
homarr-renovate[bot] 17c4090024 chore(deps): update dependency prettier-plugin-packagejson to ^2.5.12 (#3066)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-09 20:23:33 +00:00
Releases Homarr 93b9c4f6f3 chore(release): 1.19.1 [skip ci] 2025-05-09 19:15:08 +00:00
homarr-releases[bot] 1def17f4b5 chore(release): automatic release v1.19.1 2025-05-09 19:14:15 +00:00
b62d47d303 fix(releases-widget): improved provider parsing of missing properties (#2964)
Co-authored-by: Andre Silva <asilva01@acuitysso.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-09 17:30:46 +00:00
homarr-renovate[bot] b71c5c6fc7 fix(deps): update tanstack-query monorepo to ^5.75.7 (#3063)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-09 17:16:16 +00:00
Eugen StanandMeier Lukas 2bdb1c75f6 fix(logs): log error cause when object, sign in oidc debug messages (#3054)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-09 13:27:06 +02:00
347c6d1519 fix(releases-widget): error display, decouple database repository object from responses and batch widget queries (#2891)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
Co-authored-by: Andre Silva <asilva01@acuitysso.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-09 13:24:37 +02:00
homarr-renovate[bot] d6180ebc73 fix(deps): update tanstack-query monorepo to ^5.75.6 (#3062)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-09 10:21:05 +00:00
homarr-renovate[bot] 395e03c810 fix(deps): update dependency eslint-config-prettier to ^10.1.5 (#3061)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-09 04:25:37 +00:00
homarr-renovate[bot] f26634745e chore(deps): update dependency @types/node to ^22.15.17 (#3057)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-08 14:21:10 +00:00
homarr-renovate[bot] 0ab2ae4cbb chore(deps): update dependency @types/node to ^22.15.16 (#3052)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-08 04:26:28 +00:00
homarr-renovate[bot] 0d5f430bd4 fix(deps): update dependency better-sqlite3 to ^11.10.0 (#3053)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-08 04:25:27 +00:00
homarr-renovate[bot] dd300c2e1f chore(deps): update dependency @types/node to ^22.15.15 (#3050)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-07 16:26:29 +00:00
homarr-renovate[bot] 8cb9ddbce3 fix(deps): update tanstack-query monorepo to ^5.75.5 (#3048)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-07 08:26:58 +00:00
homarr-renovate[bot] e94c05f3d1 fix(deps): update dependency eslint-config-prettier to ^10.1.3 (#3047)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-07 07:22:09 +00:00
homarr-renovate[bot] b6eb8ca0f6 fix(deps): update nextjs monorepo to v15.3.2 (#3045)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 23:21:35 +00:00
homarr-renovate[bot] 9b6110b9c9 fix(deps): update turbo monorepo to ^2.5.3 (#3043)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 21:21:12 +00:00
homarr-renovate[bot] 273d4edb23 fix(deps): update dependency @kubernetes/client-node to ^1.2.0 (#3042)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 19:17:55 +00:00
homarr-renovate[bot] 36bd2a51b5 chore(deps): update dependency @types/node to ^22.15.14 (#3041)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 18:27:40 +00:00
homarr-renovate[bot]andMeier Lukas 44853ea64b fix(deps): update mantine monorepo to v8 (major) (#3022)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-05-06 20:02:20 +02:00
homarr-renovate[bot] 680f7b0c72 fix(deps): update dependency react-error-boundary to v6 (#3016)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 19:39:15 +02:00
homarr-renovate[bot] 7c0baf5fba fix(deps): update dependency ldapts to v8 (#3028)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 19:38:53 +02:00
homarr-renovate[bot] 57264a5d6c chore(deps): update dependency @types/node to ^22.15.13 (#3040)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 17:21:05 +00:00
homarr-renovate[bot] 7ec1793040 fix(deps): update dependency jotai to ^2.12.4 (#3038)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 13:34:27 +00:00
homarr-renovate[bot] 2568a9cecd chore(deps): update dependency @types/node to ^22.15.12 (#3037)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 09:21:47 +00:00
homarr-renovate[bot] 861728a7c3 fix(deps): update tanstack-query monorepo to ^5.75.4 (#3029)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 08:27:10 +00:00
homarr-renovate[bot] 0284c5ec39 chore(deps): update dependency @types/node to ^22.15.11 (#3034)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 03:13:19 +00:00
homarr-renovate[bot] 3315053ba1 chore(deps): update dependency @types/react to v19.1.3 (#3035)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-06 03:12:31 +00:00
homarr-crowdin[bot] fce8bfe1f4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-06 00:51:40 +00:00
homarr-renovate[bot] cb50eda8fb chore(deps): update dependency @types/node to ^22.15.9 (#3032)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 23:21:08 +00:00
homarr-renovate[bot] bee1dddc9f fix(deps): update dependency typescript-eslint to ^8.32.0 (#3027)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 21:22:55 +00:00
homarr-renovate[bot] 0e7eae4553 chore(deps): update dependency @types/node to ^22.15.7 (#3031)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 21:21:08 +00:00
homarr-renovate[bot] c36e1708be chore(deps): update dependency @types/node to ^22.15.5 (#3030)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 20:23:24 +00:00
homarr-renovate[bot] f1c001d728 chore(deps): update vitest monorepo to ^3.1.3 (#3024)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 14:23:48 +00:00
homarr-renovate[bot] 8a0557f927 fix(deps): update tiptap monorepo to v2.12.0 (#3025)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 14:22:25 +00:00
homarr-renovate[bot] 4aa6fbb42d chore(deps): update dependency prettier-plugin-packagejson to ^2.5.11 (#3023)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 13:33:38 +00:00
homarr-renovate[bot] e5e686d590 fix(deps): update dependency zod to ^3.24.4 (#3021)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-05 01:17:37 +00:00
homarr-renovate[bot] 758b1a0490 fix(deps): update dependency @extractus/feed-extractor to v7.1.5 (#3020)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-04 14:18:25 +00:00
homarr-renovate[bot] d512fc042a fix(deps): update tanstack-query monorepo to ^5.75.2 (#3018)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-04 08:39:09 +00:00
homarr-crowdin[bot] d401086d1b chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-04 00:57:16 +00:00
homarr-renovate[bot] 76ce6ff1fa fix(deps): update dependency drizzle-kit to ^0.31.1 (#3013)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-03 12:31:28 +00:00
homarr-renovate[bot] 9bb26ebcc3 fix(deps): update dependency ws to ^8.18.2 (#3012)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-03 06:25:50 +00:00
Meier Lukas a87bb54404 chore(issue-template): add version 1.19.0 2025-05-03 00:22:48 +02:00
homarr-renovate[bot] a9aa0e303b chore(deps): update dependency eslint to ^9.26.0 (#3008)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-02 22:21:25 +00:00
Releases Homarr dbae2d2fdd chore(release): 1.19.0 [skip ci] 2025-05-02 19:15:22 +00:00
homarr-releases[bot] b9b2398cd6 chore(release): automatic release v1.19.0 2025-05-02 19:14:41 +00:00
Meier Lukas 82c5361112 feat(widget): add prefetch for apps and bookmarks (#2895) 2025-05-02 19:23:15 +02:00
homarr-renovate[bot] 2b1c433cef fix(deps): update tanstack-query monorepo to ^5.75.1 (#3006)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-02 12:33:30 +00:00
homarr-renovate[bot] a70b0b7047 fix(deps): update mantine monorepo to ^7.17.7 (#3004)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-02 09:22:12 +00:00
homarr-crowdin[bot] e148698593 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-02 00:51:01 +00:00
homarr-renovate[bot] 0d1b40c381 fix(deps): update tanstack-query monorepo to ^5.75.0 (#2998)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-01 14:19:26 +00:00
homarr-renovate[bot] a934529146 fix(deps): update testcontainers-node monorepo to ^10.25.0 (#2997)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-05-01 11:19:58 +00:00
Meier Lukas 0c9c5b8955 fix(unifi): port is ignored (#2995) 2025-05-01 12:51:20 +02:00
Meier Lukas 4dfde2ba05 refactor: remove unused pretty-print-error dependency (#2992) 2025-05-01 10:08:52 +02:00
Meier Lukas 48aef9e862 refactor: replace deprecated unsable_concat with concat for trpc middlewares (#2991) 2025-05-01 10:08:41 +02:00
Meier Lukas 547a8c34c5 fix(cookie): parsing of header not working with percentage char (#2989) 2025-05-01 10:08:29 +02:00
Meier Lukas 9006d3d2fc fix(media-request): add missing completed status (#2990) 2025-05-01 10:08:19 +02:00
homarr-crowdin[bot] a2934247b2 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-05-01 00:57:45 +00:00
homarr-renovate[bot] 1a07fa9af5 fix(deps): update dependency @t3-oss/env-nextjs to ^0.13.4 (#2993)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-30 21:21:02 +00:00
Meier Lukas ec11c738f2 chore(docs): update docs-sitemap 2025-04-30 21:06:24 +02:00
homarr-renovate[bot] 254c248e12 chore(deps): update dependency @types/react-dom to v19.1.3 (#2988)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-30 11:18:12 +00:00
homarr-renovate[bot] 70686a0066 fix(deps): update tiptap monorepo to v2.11.9 (#2987)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-30 08:45:52 +00:00
homarr-renovate[bot] 3299cedc04 fix(deps): update tanstack-query monorepo to ^5.74.11 (#2984)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-29 18:26:21 +00:00
homarr-renovate[bot] 1012cad0e3 fix(deps): update tanstack-query monorepo to ^5.74.9 (#2983)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-29 11:19:25 +00:00
homarr-renovate[bot] b10f33bf0b fix(deps): update trpc monorepo to ^11.1.2 (#2982)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-29 07:21:16 +00:00
homarr-renovate[bot] a2dbac5ad7 fix(deps): update dependency tsx to v4.19.4 (#2981)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-29 06:27:59 +00:00
Andre Silva f39a21c296 fix(releases-widget): improve ui for smaller widgets (#2962) 2025-04-28 19:04:38 +00:00
homarr-renovate[bot] 284ea239f7 fix(deps): update dependency typescript-eslint to ^8.31.1 (#2979)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-28 18:28:13 +00:00
homarr-renovate[bot] 583a6b6f4c chore(deps): update dependency @types/node to ^22.15.3 (#2978)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-28 07:47:26 +00:00
homarr-renovate[bot] 039fad643e chore(deps): update pnpm to v10.10.0 (#2976)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-28 01:13:56 +00:00
homarr-crowdin[bot] 2f2c00ba3d chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-28 00:52:47 +00:00
Meier Lukas ebb3012a65 Merge branch 'main' into dev 2025-04-27 17:24:06 +02:00
homarr-renovate[bot] f4ce76ac13 fix(deps): update tanstack-query monorepo to ^5.74.7 (#2924)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-27 15:19:19 +00:00
Releases Homarr 830660b1ef chore(release): 1.18.1 [skip ci] 2025-04-27 15:17:33 +00:00
homarr-releases[bot] f7451d182a chore(release): automatic release v1.18.1 2025-04-27 15:16:33 +00:00
Meier Lukas bb92f5bb79 fix: cannot find module http-cookie-agent/http (#2972) 2025-04-27 17:12:03 +02:00
homarr-crowdin[bot] f7ff1e3911 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-27 13:58:54 +00:00
homarr-renovate[bot] 5f80bebef2 fix(deps): update dependency mysql2 to v3.14.1 (#2969)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-27 04:22:58 +00:00
homarr-crowdin[bot] 97fe1f6a3e chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-26 00:48:49 +00:00
homarr-renovate[bot] 35fee2f380 chore(deps): update dependency @semantic-release/github to ^11.0.2 (#2956)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 21:20:30 +00:00
Manuel e1e26b27de docs: add 1.18.0 to issue template (#2951) 2025-04-25 19:19:14 +00:00
Releases Homarr 55cdddb274 chore(release): 1.18.0 [skip ci] 2025-04-25 19:15:32 +00:00
homarr-releases[bot] dd85f5a9d5 chore(release): automatic release v1.18.0 2025-04-25 19:14:47 +00:00
Manuel 8550c69d51 feat: migrate to unifi package (#2894) 2025-04-25 18:58:55 +00:00
3dcee8cb86 feat: add releases widget (#2497)
Co-authored-by: Andre Silva <asilva01@acuitysso.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2025-04-25 18:49:32 +00:00
homarr-renovate[bot] d97e74047d fix(deps): update turbo monorepo to ^2.5.2 (#2949)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 16:24:57 +00:00
homarr-renovate[bot] e36892fc8f chore(deps): update dependency @types/node to ^22.15.2 (#2946)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 09:22:12 +00:00
homarr-renovate[bot] 1d28bf4c93 fix(deps): update dependency drizzle-orm to ^0.43.1 (#2947)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 08:26:07 +00:00
homarr-renovate[bot] 8b076ce761 chore(deps): update dependency @types/node to ^22.15.1 (#2945)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 04:24:44 +00:00
homarr-renovate[bot] d78b2e1e85 fix(deps): update dependency tsdav to ^2.1.4 (#2944)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-25 03:09:10 +00:00
homarr-renovate[bot] f65c0125a9 chore(deps): update dependency @types/node to ^22.15.0 (#2942)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-24 21:21:27 +00:00
homarr-renovate[bot] 253308357a fix(deps): update dependency next-auth to v5.0.0-beta.27 (#2940)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-24 17:19:21 +00:00
homarr-renovate[bot] 295cc4a3c4 fix(deps): update dependency drizzle-orm to ^0.43.0 (#2939)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-24 14:20:39 +00:00
homarr-renovate[bot] 767fe946d7 fix(deps): update dependency next-intl to v4.1.0 (#2938)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-24 09:24:32 +00:00
homarr-renovate[bot] aba963c7cf fix(deps): update mantine monorepo to ^7.17.5 (#2937)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-24 09:23:37 +00:00
homarr-crowdin[bot] 4529cabb8a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-24 04:29:49 +00:00
homarr-renovate[bot] 7766c3cbbc fix(deps): update turbo monorepo to ^2.5.1 (#2934)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 23:21:49 +00:00
homarr-renovate[bot] eb95ee285b fix(deps): update dependency @t3-oss/env-nextjs to ^0.13.0 (#2935)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 22:22:00 +00:00
homarr-renovate[bot] 1421c381cc chore(deps): update node.js to v22.15.0 (#2932)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 20:22:21 +02:00
homarr-renovate[bot] 573d5f89d6 fix(deps): update dependency glob to ^11.0.2 (#2931)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 16:24:21 +00:00
homarr-renovate[bot] e01b909402 chore(deps): update node.js to v22.15.0 (#2928)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 08:27:13 +00:00
homarr-renovate[bot] bd3535dcf8 fix(deps): update trpc monorepo to ^11.1.1 (#2927)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-23 01:13:29 +00:00
homarr-crowdin[bot] c4bd513134 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-23 00:50:32 +00:00
Meier Lukas c64d903f2b feat(widgets): add title to advanced options (#2909) 2025-04-22 18:33:15 +02:00
Meier Lukas f98750d0b3 feat(media-server): add option to only show playing sessions (#2899) 2025-04-22 18:30:46 +02:00
Meier Lukas c51424717d fix(certificates): improve validation and prevent crash (#2910) 2025-04-22 18:28:58 +02:00
homarr-renovate[bot] 3172e6e0c4 fix(deps): update dependency next-intl to v4.0.3 (#2922)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-22 16:25:51 +00:00
homarr-renovate[bot] 7aa379076d fix(deps): update dependency typescript-eslint to ^8.31.0 (#2919)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-22 16:25:09 +00:00
homarr-renovate[bot] 023ffcf7bf chore(deps): update dependency eslint to ^9.25.1 (#2918)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-22 01:13:02 +00:00
homarr-renovate[bot] fed28bd775 fix(deps): update dependency sass to ^1.87.0 (#2921)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-21 23:20:50 +00:00
homarr-renovate[bot] 09718b8258 chore(deps): update vitest monorepo to ^3.1.2 (#2915)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-21 09:22:24 +00:00
homarr-renovate[bot] 6040f063ca chore(deps): update pnpm to v10.9.0 (#2916)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-21 09:21:11 +00:00
homarr-renovate[bot] 3d508088d5 fix(deps): update dependency @ctrl/qbittorrent to ^9.6.0 (#2914)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-21 03:13:01 +00:00
homarr-crowdin[bot] 37484ce032 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-21 00:52:28 +00:00
Meier Lukas ac3c2a6377 chore(issue-template): add missing homarr version 1.17.0 2025-04-21 00:34:42 +02:00
homarr-renovate[bot]andMeier Lukas 6e6edc8a0d fix(deps): update dependency next-auth to v5.0.0-beta.26 (#2906)
* fix(deps): update dependency next-auth to v5.0.0-beta.26

* fix(deps): update dependency next-auth to v5.0.0-beta.26

* fix: lint issue

* fix: format issue

* fix: login with oidc no longer working

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-04-20 12:21:33 +00:00
homarr-renovate[bot] b0d7f9a48c fix(deps): update dependency @auth/core to ^0.39.0 (#2904)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-20 08:56:24 +00:00
homarr-renovate[bot] fdf700dba5 fix(deps): update dependency @auth/drizzle-adapter to ^1.9.0 (#2905)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-20 06:24:43 +00:00
homarr-renovate[bot] 2bf67c3de8 fix(deps): update dependency trpc-to-openapi to ^2.2.0 (#2903)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-19 23:19:45 +00:00
homarr-renovate[bot] 593e3ee5a6 fix(deps): update dependency dockerode to ^4.0.6 (#2901)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-19 13:03:16 +00:00
homarr-renovate[bot] ad5da4e8ac chore(deps): update dependency @vitejs/plugin-react to ^4.4.1 (#2902)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-19 12:26:25 +00:00
Releases Homarr 58ef0960b5 chore(release): 1.17.0 [skip ci] 2025-04-18 19:14:53 +00:00
homarr-releases[bot] 68d6967d24 chore(release): automatic release v1.17.0 2025-04-18 19:14:04 +00:00
homarr-renovate[bot] e5f0bb9792 chore(deps): update dependency eslint to ^9.25.0 (#2897)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-18 18:46:49 +00:00
Jacob HemmingandMeier Lukas 3d68f4d128 feat: add api-key support for tdarr (#2890)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-04-18 20:11:51 +02:00
Jacob Hemming cfca0ba583 fix: calculate available space correctly and filter unmounted drives (#2885) 2025-04-18 10:49:59 +00:00
Meier Lukas 9570abb2c3 chore(docs): update docs-sitemap 2025-04-18 10:52:02 +02:00
homarr-renovate[bot] 5fcb7a3b9c fix(deps): update dependency recharts to ^2.15.3 (#2893)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-18 05:18:26 +00:00
homarr-crowdin[bot] a55acb9eb9 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-18 00:49:15 +00:00
homarr-renovate[bot] d37cdbd080 fix(deps): update nextjs monorepo to v15.3.1 (#2889)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-17 15:56:13 +00:00
homarr-renovate[bot] 17174b6b96 fix(deps): update tanstack-query monorepo to ^5.74.4 (#2888)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-17 09:54:31 +00:00
homarr-crowdin[bot] 0466a2808a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-17 00:49:43 +00:00
homarr-renovate[bot] 1bfe397d68 fix(deps): update dependency zod to ^3.24.3 (#2886)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-16 22:40:20 +00:00
Meier Lukas 60de6361bb chore(docs): update docs-sitemap 2025-04-16 22:58:28 +02:00
homarr-renovate[bot] 47d7abc0d7 fix(deps): update dependency drizzle-orm to ^0.42.0 (#2882)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-16 18:51:44 +00:00
rezstjeandManuel 2d12c1e641 fix: Tdarr widget empty statistics tab (#2883)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2025-04-16 18:34:06 +00:00
Thomas Camlong 00053bda7b feat: Prefer primary color (#2665) 2025-04-16 11:34:33 +02:00
homarr-renovate[bot] 97390c6030 fix(deps): update dependency drizzle-kit to ^0.31.0 (#2881)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 13:58:27 +00:00
homarr-renovate[bot] 865a52ae7d chore(deps): update dependency @vitejs/plugin-react to ^4.4.0 (#2879)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 09:40:02 +00:00
homarr-renovate[bot] fdca513929 fix(deps): update dependency typescript-eslint to ^8.30.1 (#2876)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-15 01:02:52 +00:00
homarr-crowdin[bot] ce5784b6c4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-15 00:50:50 +00:00
homarr-renovate[bot] 1a4c136d41 chore(deps): update dependency @types/react to v19.1.2 (#2874)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 19:17:29 +00:00
homarr-renovate[bot] 089734b293 fix(deps): update dependency typescript-eslint to ^8.30.0 (#2873)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 17:37:45 +00:00
homarr-renovate[bot] 2e59bfcd60 chore(deps): update dependency @types/dockerode to ^3.3.38 (#2872)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 15:55:56 +00:00
homarr-renovate[bot] 02fb1b9dae fix(deps): update tanstack-query monorepo to ^5.74.3 (#2871)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 13:09:11 +00:00
homarr-renovate[bot] bf4a1cf1e8 fix(deps): update tanstack-query monorepo to ^5.74.2 (#2869)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 11:39:37 +00:00
homarr-renovate[bot] 4a0fb5d0bb chore(deps): update pnpm to v10.8.1 (#2870)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 11:31:01 +00:00
homarr-renovate[bot] c57b63b506 fix(deps): update dependency jotai to ^2.12.3 (#2868)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-14 01:03:30 +00:00
homarr-crowdin[bot] fa0e694ce0 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-14 00:52:46 +00:00
homarr-renovate[bot] ff1263b217 fix(deps): update dependency swagger-ui-react to ^5.21.0 (#2866)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 19:53:29 +00:00
homarr-renovate[bot] ac78e87042 fix(deps): update tanstack-query monorepo to ^5.74.0 (#2864)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 18:21:57 +00:00
homarr-renovate[bot] ec7adb03b6 chore(deps): update dependency jsdom to ^26.1.0 (#2863)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-13 10:41:29 +00:00
homarr-crowdin[bot] f5f39bc094 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-13 02:16:30 +00:00
homarr-renovate[bot] 4e6ab037c9 fix(deps): update tanstack-query monorepo to ^5.73.3 (#2860)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-12 16:18:00 +00:00
Andrea a29556ee8e fix(widget): format stock values and changes for better readability (#2836) 2025-04-12 15:53:40 +00:00
homarr-crowdin[bot] 71d8f7920d chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-04-12 00:48:49 +00:00
homarr-renovate[bot] 835e007334 chore(deps): update dependency @types/node to ^22.14.1 (#2857)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-04-11 21:43:33 +00:00
Meier Lukas d46b8f63c7 chore(issue-template): add missing homarr version 1.16.0 2025-04-11 21:56:00 +02:00
322 changed files with 23811 additions and 6739 deletions

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
Dockerfile
.dockerignore
node_modules
**/node_modules
npm-debug.log
README.md
.next
+6
View File
@@ -31,6 +31,12 @@ body:
label: Version
description: What version of Homarr are you running?
options:
- 1.20.0
- 1.19.1
- 1.19.0
- 1.18.0
- 1.17.0
- 1.16.0
- 1.15.0
- 1.14.0
- 1.13.1
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
approve-automatic-prs:
runs-on: ubuntu-latest
if: github.actor_id == 158783068 || github.actor_id == 190541745 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
steps:
- name: Checkout code
uses: actions/checkout@v4
+70
View File
@@ -0,0 +1,70 @@
name: Update Contributors
on:
schedule:
- cron: "0 12 * * FRI" # At 12:00 on Friday.
workflow_dispatch:
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
permissions:
contents: write
jobs:
update-contributors:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22]
steps:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
with:
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
- name: Checkout repository
uses: actions/checkout@v4
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Run update script
run: node ./scripts/update-contributors.mjs
- name: Commit changes
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
git config --global user.email "210161987+homarr-update-contributors[bot]@users.noreply.github.com"
git config --global user.name "Homarr Update Contributors"
git add .
git commit -m "chore: update contributors"
- name: Create Pull Request
id: create-pull-request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.obtainToken.outputs.token }}
branch: update-contributors
base: dev
title: "chore: update contributors"
delete-branch: true
body: |
This PR updates the contributors list in the static-data directory.
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Enable auto-merge
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
+1 -1
View File
@@ -1 +1 @@
22.14.0
22.16.0
+1
View File
@@ -26,6 +26,7 @@
"Sabnzbd",
"SeDemal",
"Sonarr",
"sslverify",
"superjson",
"tabler",
"trpc",
+190
View File
@@ -1,3 +1,193 @@
## [1.21.0](https://github.com/homarr-labs/homarr/compare/v1.20.0...v1.21.0) (2025-05-23)
### Features
* docker widget ([#2288](https://github.com/homarr-labs/homarr/issues/2288)) ([e1eda53](https://github.com/homarr-labs/homarr/commit/e1eda534da6d97655546f3140f9e36eca8817db3))
### Bug Fixes
* **deps:** update dependency @tabler/icons-react to ^3.33.0 ([#3132](https://github.com/homarr-labs/homarr/issues/3132)) ([ee8870b](https://github.com/homarr-labs/homarr/commit/ee8870b66bbcb8eeeef359ece9391a496198be0d))
* **deps:** update dependency octokit to ^4.1.4 ([#3165](https://github.com/homarr-labs/homarr/issues/3165)) ([50445b5](https://github.com/homarr-labs/homarr/commit/50445b560edc3af2631f8d8d9aa81f3387d848f7))
* **deps:** update dependency octokit to v5 ([#3156](https://github.com/homarr-labs/homarr/issues/3156)) ([8c59abd](https://github.com/homarr-labs/homarr/commit/8c59abdde3d8034f22a2b926b3291b55a0d492ea))
* **deps:** update dependency swagger-ui-react to ^5.22.0 ([#3178](https://github.com/homarr-labs/homarr/issues/3178)) ([1e7b2e6](https://github.com/homarr-labs/homarr/commit/1e7b2e617b1437f0f98bc791e763bc49b6589b9e))
* **deps:** update dependency undici to v7.10.0 ([#3158](https://github.com/homarr-labs/homarr/issues/3158)) ([eb5d5f8](https://github.com/homarr-labs/homarr/commit/eb5d5f8260cc9795806ecdb56fbb3f8d19858ed7))
* **deps:** update dependency zod to ^3.25.12 ([#3172](https://github.com/homarr-labs/homarr/issues/3172)) ([caaee50](https://github.com/homarr-labs/homarr/commit/caaee503c128ff2a0b2a7de7a7c353484e4741a5))
* **deps:** update dependency zod to ^3.25.13 ([#3173](https://github.com/homarr-labs/homarr/issues/3173)) ([dae6ded](https://github.com/homarr-labs/homarr/commit/dae6dedde544bf28fcdf87c71a9d585078527b82))
* **deps:** update dependency zod to ^3.25.14 ([#3174](https://github.com/homarr-labs/homarr/issues/3174)) ([23404a2](https://github.com/homarr-labs/homarr/commit/23404a27f1a9dfec4e9126750f8d97e7c5eda572))
* **deps:** update dependency zod to ^3.25.16 ([#3175](https://github.com/homarr-labs/homarr/issues/3175)) ([5080f51](https://github.com/homarr-labs/homarr/commit/5080f51a8dbe1b82571f356f7ae29ed7be69e18e))
* **deps:** update dependency zod to ^3.25.17 ([#3177](https://github.com/homarr-labs/homarr/issues/3177)) ([371803a](https://github.com/homarr-labs/homarr/commit/371803ac509efd246367c812a6c0143e7aeda03f))
* **deps:** update dependency zod to ^3.25.20 ([#3183](https://github.com/homarr-labs/homarr/issues/3183)) ([49498c8](https://github.com/homarr-labs/homarr/commit/49498c870e494b417e6c63d8547a4c0ffd93768b))
* **deps:** update dependency zod to ^3.25.21 ([#3187](https://github.com/homarr-labs/homarr/issues/3187)) ([37d637d](https://github.com/homarr-labs/homarr/commit/37d637dcae6cbf62efda25a8dc01eedba66f2c74))
* **deps:** update dependency zod to ^3.25.23 ([#3188](https://github.com/homarr-labs/homarr/issues/3188)) ([9840644](https://github.com/homarr-labs/homarr/commit/9840644ca50d3157223e7da0cf895f2e750afc58))
* **deps:** update dependency zod to ^3.25.3 ([#3148](https://github.com/homarr-labs/homarr/issues/3148)) ([6e2aa87](https://github.com/homarr-labs/homarr/commit/6e2aa87102b72319236483b9a1d3e5ddd6fe2c82))
* **deps:** update dependency zod to ^3.25.4 ([#3150](https://github.com/homarr-labs/homarr/issues/3150)) ([49b7080](https://github.com/homarr-labs/homarr/commit/49b7080415aaeee576507d48e8cda18acc9943a8))
* **deps:** update dependency zod to ^3.25.5 ([#3152](https://github.com/homarr-labs/homarr/issues/3152)) ([79755c9](https://github.com/homarr-labs/homarr/commit/79755c95d3cf58680e434f0113c130db9f649d96))
* **deps:** update dependency zod to ^3.25.6 ([#3153](https://github.com/homarr-labs/homarr/issues/3153)) ([92ebc05](https://github.com/homarr-labs/homarr/commit/92ebc05fbfded6d20d242ad477cb0dc30243e8cc))
* **deps:** update dependency zod to ^3.25.7 ([#3154](https://github.com/homarr-labs/homarr/issues/3154)) ([22954b8](https://github.com/homarr-labs/homarr/commit/22954b8d2637cdea9a6502cab2d3d40359fa098f))
* **deps:** update dependency zod to ^3.25.8 ([#3170](https://github.com/homarr-labs/homarr/issues/3170)) ([72bcbe8](https://github.com/homarr-labs/homarr/commit/72bcbe8effe47fdfb6a94de8defc0d3c12e7c754))
* **deps:** update dependency zod-validation-error to ^3.4.1 ([#3127](https://github.com/homarr-labs/homarr/issues/3127)) ([886e95d](https://github.com/homarr-labs/homarr/commit/886e95d51714ce20e03d32b4c2cfb673e7e4c218))
* **deps:** update tanstack-query monorepo to ^5.76.2 ([#3186](https://github.com/homarr-labs/homarr/issues/3186)) ([1584114](https://github.com/homarr-labs/homarr/commit/1584114e72246a018b9f49a859e49f7fb985009d))
* **deps:** update testcontainers-node monorepo to ^10.27.0 ([#3159](https://github.com/homarr-labs/homarr/issues/3159)) ([18c465e](https://github.com/homarr-labs/homarr/commit/18c465e81e92cf58b7e32bcb70f1ab404a03a135))
* **deps:** update testcontainers-node monorepo to ^10.28.0 ([#3191](https://github.com/homarr-labs/homarr/issues/3191)) ([43b8241](https://github.com/homarr-labs/homarr/commit/43b82419e9396f0b80bafa0a707d575c3732dc5b))
* **downloads:** reordering columns in widget does not work ([#3194](https://github.com/homarr-labs/homarr/issues/3194)) ([09f4e67](https://github.com/homarr-labs/homarr/commit/09f4e6785b2ce38da3b52003e9c9300cdd0feb85))
* esbuild not part of dependencies ([#3139](https://github.com/homarr-labs/homarr/issues/3139)) ([ef540e9](https://github.com/homarr-labs/homarr/commit/ef540e980f90f730020ef081dd105e33b0671232))
* **items:** resize handle overlaps header ([#3193](https://github.com/homarr-labs/homarr/issues/3193)) ([09f3989](https://github.com/homarr-labs/homarr/commit/09f398948348fb402600506571a967ba405fbafb))
* **releases-widget:** add hardcoded css classNames([#3181](https://github.com/homarr-labs/homarr/issues/3181)) ([4cb004f](https://github.com/homarr-labs/homarr/commit/4cb004fce0750da339f603b944de55135faa4bfa))
## [1.20.0](https://github.com/homarr-labs/homarr/compare/v1.19.1...v1.20.0) (2025-05-16)
### Features
* add css class to logo for custom css ([#3121](https://github.com/homarr-labs/homarr/issues/3121)) ([9b8320c](https://github.com/homarr-labs/homarr/commit/9b8320c10d51cc449b3fb5c1532408bff5655788))
* **auth:** add account linking for oidc providers ([#3106](https://github.com/homarr-labs/homarr/issues/3106)) ([3daf1c8](https://github.com/homarr-labs/homarr/commit/3daf1c8341ab40a580777246e742ff5019a483d2))
* **integration:** improve integration test connection ([#3005](https://github.com/homarr-labs/homarr/issues/3005)) ([ef9a5e9](https://github.com/homarr-labs/homarr/commit/ef9a5e9895d7079008046df66f6a6d538af106c9))
* **releases-widget:** added auto icon search when editing a repository ([#3087](https://github.com/homarr-labs/homarr/issues/3087)) ([9413430](https://github.com/homarr-labs/homarr/commit/94134303d7dc6f2d52e4955dcd9d1afbd8d203fb))
* **releases-widget:** limit number of displayed releases and custom name for repositories ([#2974](https://github.com/homarr-labs/homarr/issues/2974)) ([8c1b365](https://github.com/homarr-labs/homarr/commit/8c1b365733c2bccd0838cbf07aa59115bcc0c0cd))
### Bug Fixes
* **calendar:** some timezones result in offset of one day ([#3120](https://github.com/homarr-labs/homarr/issues/3120)) ([b86159b](https://github.com/homarr-labs/homarr/commit/b86159ba7c1cdaea973466bd7430fc45fd0d8317))
* **deps:** missing dependencies in definitions package for docs link codegen ([#3119](https://github.com/homarr-labs/homarr/issues/3119)) ([42e7fe3](https://github.com/homarr-labs/homarr/commit/42e7fe3273b0f0e8f67e38ac3fd758528cc47913))
* **deps:** update dependency @auth/core to ^0.39.1 ([#3072](https://github.com/homarr-labs/homarr/issues/3072)) ([0d23119](https://github.com/homarr-labs/homarr/commit/0d2311975c9062f538e7cf7166cc354bc6b21468))
* **deps:** update dependency @auth/drizzle-adapter to ^1.9.1 ([#3073](https://github.com/homarr-labs/homarr/issues/3073)) ([3f9f94e](https://github.com/homarr-labs/homarr/commit/3f9f94ea45e001d0f2c966ee21d1e148103a854e))
* **deps:** update dependency @extractus/feed-extractor to v7.1.6 ([#3105](https://github.com/homarr-labs/homarr/issues/3105)) ([d96bc89](https://github.com/homarr-labs/homarr/commit/d96bc898fdc014940207c1d7bead371ac4df33a9))
* **deps:** update dependency bcrypt to v6 ([#3083](https://github.com/homarr-labs/homarr/issues/3083)) ([5686910](https://github.com/homarr-labs/homarr/commit/5686910a4735801c40caed08a4c8b931fd04418f))
* **deps:** update dependency next-auth to v5.0.0-beta.28 ([#3074](https://github.com/homarr-labs/homarr/issues/3074)) ([98773e7](https://github.com/homarr-labs/homarr/commit/98773e7dba3f7f816a50c134cedf81cddb55e5f1))
* **deps:** update dependency sass to ^1.88.0 ([#3078](https://github.com/homarr-labs/homarr/issues/3078)) ([b0f7310](https://github.com/homarr-labs/homarr/commit/b0f7310cb79e014e58a72dcf812d3d4cc876d034))
* **deps:** update dependency sass to ^1.89.0 ([#3118](https://github.com/homarr-labs/homarr/issues/3118)) ([5b16468](https://github.com/homarr-labs/homarr/commit/5b16468cab53429d4f8cf36771ab6efafea86743))
* **deps:** update dependency typescript-eslint to ^8.32.1 ([#3093](https://github.com/homarr-labs/homarr/issues/3093)) ([42b377d](https://github.com/homarr-labs/homarr/commit/42b377d0672973cb3d7b32c0d1e2c0663d68c2a8))
* **deps:** update dependency undici to v7.9.0 ([#3077](https://github.com/homarr-labs/homarr/issues/3077)) ([01d53ee](https://github.com/homarr-labs/homarr/commit/01d53eee60f7269158565cfa46f7aebdfa8d41cf))
* **deps:** update mantine monorepo to ^8.0.1 ([#3107](https://github.com/homarr-labs/homarr/issues/3107)) ([25efe3d](https://github.com/homarr-labs/homarr/commit/25efe3d33504f8e4f1eb3f29edfdeccf4b15a8e4))
* **deps:** update tanstack-query monorepo to ^5.76.0 ([#3090](https://github.com/homarr-labs/homarr/issues/3090)) ([388bc73](https://github.com/homarr-labs/homarr/commit/388bc7392c318e05f85134cf969e10d9627c55f9))
* **deps:** update tanstack-query monorepo to ^5.76.1 ([#3101](https://github.com/homarr-labs/homarr/issues/3101)) ([c1e5d77](https://github.com/homarr-labs/homarr/commit/c1e5d77176126c6248f052d2baa3cf09837fba62))
* **deps:** update testcontainers-node monorepo to ^10.26.0 ([#3114](https://github.com/homarr-labs/homarr/issues/3114)) ([cb81efa](https://github.com/homarr-labs/homarr/commit/cb81efa42c99f4bd8024b21e0818294db8d22fec))
* **releases-widget:** delete the empty repository when canceling `Add Reposotory` ([#3082](https://github.com/homarr-labs/homarr/issues/3082)) ([72862ef](https://github.com/homarr-labs/homarr/commit/72862efae274cfc25fe656aef27f6acb2e1b0ca6))
* **releases-widget:** reduced padding for `ul` in release description ([#3085](https://github.com/homarr-labs/homarr/issues/3085)) ([593d97e](https://github.com/homarr-labs/homarr/commit/593d97e27aa85ab023935d12c8cbf534d38c288f))
* **releases-widget:** removed version regex in options overview ([#3081](https://github.com/homarr-labs/homarr/issues/3081)) ([59cae02](https://github.com/homarr-labs/homarr/commit/59cae02623b09e60b8c300c18349ea282c872822))
* **releases-widget:** validation preventing removal of a previously added icon ([#3086](https://github.com/homarr-labs/homarr/issues/3086)) ([18fe8ec](https://github.com/homarr-labs/homarr/commit/18fe8ecd39f6551135beb0333cbad5b9ff4d6664))
* updated contributors contain unnecessary data ([#3124](https://github.com/homarr-labs/homarr/issues/3124)) ([6e03e5a](https://github.com/homarr-labs/homarr/commit/6e03e5a40c9aa0ee5e0e3de4277ffbe44ad45b66))
## [1.19.1](https://github.com/homarr-labs/homarr/compare/v1.19.0...v1.19.1) (2025-05-09)
### Bug Fixes
* **deps:** update dependency @extractus/feed-extractor to v7.1.5 ([#3020](https://github.com/homarr-labs/homarr/issues/3020)) ([758b1a0](https://github.com/homarr-labs/homarr/commit/758b1a049019d903f152f783eab3b6fc4be4de14))
* **deps:** update dependency @kubernetes/client-node to ^1.2.0 ([#3042](https://github.com/homarr-labs/homarr/issues/3042)) ([273d4ed](https://github.com/homarr-labs/homarr/commit/273d4edb233f5d5faa7105c23208d95268119786))
* **deps:** update dependency better-sqlite3 to ^11.10.0 ([#3053](https://github.com/homarr-labs/homarr/issues/3053)) ([0d5f430](https://github.com/homarr-labs/homarr/commit/0d5f430bd45e6d29d604d4f93ff57cfc7a64e309))
* **deps:** update dependency drizzle-kit to ^0.31.1 ([#3013](https://github.com/homarr-labs/homarr/issues/3013)) ([76ce6ff](https://github.com/homarr-labs/homarr/commit/76ce6ff1fab34223e7ff5abf46193f1ae9b011d1))
* **deps:** update dependency eslint-config-prettier to ^10.1.3 ([#3047](https://github.com/homarr-labs/homarr/issues/3047)) ([e94c05f](https://github.com/homarr-labs/homarr/commit/e94c05f3d1eea1841b978359d3c41d8a46a08a39))
* **deps:** update dependency eslint-config-prettier to ^10.1.5 ([#3061](https://github.com/homarr-labs/homarr/issues/3061)) ([395e03c](https://github.com/homarr-labs/homarr/commit/395e03c8101af873f35388ae1b833dc68d552792))
* **deps:** update dependency jotai to ^2.12.4 ([#3038](https://github.com/homarr-labs/homarr/issues/3038)) ([7ec1793](https://github.com/homarr-labs/homarr/commit/7ec179304040d6782e516358aeaae2eaa143dc1a))
* **deps:** update dependency ldapts to v8 ([#3028](https://github.com/homarr-labs/homarr/issues/3028)) ([7c0baf5](https://github.com/homarr-labs/homarr/commit/7c0baf5fba19735c2b3fac0728359a9aa8c617e6))
* **deps:** update dependency react-error-boundary to v6 ([#3016](https://github.com/homarr-labs/homarr/issues/3016)) ([680f7b0](https://github.com/homarr-labs/homarr/commit/680f7b0c7277ffcbb6a033fd7a2f14146e8a2fca))
* **deps:** update dependency typescript-eslint to ^8.32.0 ([#3027](https://github.com/homarr-labs/homarr/issues/3027)) ([bee1ddd](https://github.com/homarr-labs/homarr/commit/bee1dddc9ff1b213ef9fd9cc1696bf22cc816b19))
* **deps:** update dependency ws to ^8.18.2 ([#3012](https://github.com/homarr-labs/homarr/issues/3012)) ([9bb26eb](https://github.com/homarr-labs/homarr/commit/9bb26ebcc3a3f400317c85bd1fb13621d52049c2))
* **deps:** update dependency zod to ^3.24.4 ([#3021](https://github.com/homarr-labs/homarr/issues/3021)) ([e5e686d](https://github.com/homarr-labs/homarr/commit/e5e686d59086320a565c3367a389d1117fd74d88))
* **deps:** update mantine monorepo to v8 (major) ([#3022](https://github.com/homarr-labs/homarr/issues/3022)) ([44853ea](https://github.com/homarr-labs/homarr/commit/44853ea64bed3a093a26cb07904d8beedf220e4e))
* **deps:** update nextjs monorepo to v15.3.2 ([#3045](https://github.com/homarr-labs/homarr/issues/3045)) ([b6eb8ca](https://github.com/homarr-labs/homarr/commit/b6eb8ca0f6d0e6b2fac40423a6c0157dff292622))
* **deps:** update tanstack-query monorepo to ^5.75.2 ([#3018](https://github.com/homarr-labs/homarr/issues/3018)) ([d512fc0](https://github.com/homarr-labs/homarr/commit/d512fc042a0cb06238f3c86f92b12ad152208a9c))
* **deps:** update tanstack-query monorepo to ^5.75.4 ([#3029](https://github.com/homarr-labs/homarr/issues/3029)) ([861728a](https://github.com/homarr-labs/homarr/commit/861728a7c3156bbe618b9fb186d9f168ba9b339b))
* **deps:** update tanstack-query monorepo to ^5.75.5 ([#3048](https://github.com/homarr-labs/homarr/issues/3048)) ([8cb9ddb](https://github.com/homarr-labs/homarr/commit/8cb9ddbce3f00e6e3df07c796c39fda7dd18b7b1))
* **deps:** update tanstack-query monorepo to ^5.75.6 ([#3062](https://github.com/homarr-labs/homarr/issues/3062)) ([d6180eb](https://github.com/homarr-labs/homarr/commit/d6180ebc7387249265ad9c296c52aab6d393d88a))
* **deps:** update tanstack-query monorepo to ^5.75.7 ([#3063](https://github.com/homarr-labs/homarr/issues/3063)) ([b71c5c6](https://github.com/homarr-labs/homarr/commit/b71c5c6fc73cab4ebc1bfe9d70722f166ebcdb47))
* **deps:** update tiptap monorepo to v2.12.0 ([#3025](https://github.com/homarr-labs/homarr/issues/3025)) ([8a0557f](https://github.com/homarr-labs/homarr/commit/8a0557f927f6423e37a372c4c4ffea224598ff45))
* **deps:** update turbo monorepo to ^2.5.3 ([#3043](https://github.com/homarr-labs/homarr/issues/3043)) ([9b6110b](https://github.com/homarr-labs/homarr/commit/9b6110b9c9ec08bde335cc95d7548e2dddfab45b))
* **logs:** log error cause when object, sign in oidc debug messages ([#3054](https://github.com/homarr-labs/homarr/issues/3054)) ([2bdb1c7](https://github.com/homarr-labs/homarr/commit/2bdb1c75f680e7715bbe68c6f50e24ea861302ca))
* **releases-widget:** error display, decouple database repository object from responses and batch widget queries ([#2891](https://github.com/homarr-labs/homarr/issues/2891)) ([347c6d1](https://github.com/homarr-labs/homarr/commit/347c6d15191a64899ff844cfe689973f44b9c556))
* **releases-widget:** improved provider parsing of missing properties ([#2964](https://github.com/homarr-labs/homarr/issues/2964)) ([b62d47d](https://github.com/homarr-labs/homarr/commit/b62d47d303ca1271b932b6347a489df6f16fc680))
## [1.19.0](https://github.com/homarr-labs/homarr/compare/v1.18.1...v1.19.0) (2025-05-02)
### Features
* **widget:** add prefetch for apps and bookmarks ([#2895](https://github.com/homarr-labs/homarr/issues/2895)) ([82c5361](https://github.com/homarr-labs/homarr/commit/82c53611122dd07e5639a902afa0a6a4fd8ed4e5))
### Bug Fixes
* **cookie:** parsing of header not working with percentage char ([#2989](https://github.com/homarr-labs/homarr/issues/2989)) ([547a8c3](https://github.com/homarr-labs/homarr/commit/547a8c34c58c88e6f0074df327f49aadf9ab07f4))
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.4 ([#2993](https://github.com/homarr-labs/homarr/issues/2993)) ([1a07fa9](https://github.com/homarr-labs/homarr/commit/1a07fa9af59e93f9ce5d70b72855bb070f5b5787))
* **deps:** update dependency tsx to v4.19.4 ([#2981](https://github.com/homarr-labs/homarr/issues/2981)) ([a2dbac5](https://github.com/homarr-labs/homarr/commit/a2dbac5ad73aa50af7f4b40076bb5fcfa61adf80))
* **deps:** update dependency typescript-eslint to ^8.31.1 ([#2979](https://github.com/homarr-labs/homarr/issues/2979)) ([284ea23](https://github.com/homarr-labs/homarr/commit/284ea239f71f49c74e3ea60fa5de2fd968f1675b))
* **deps:** update mantine monorepo to ^7.17.7 ([#3004](https://github.com/homarr-labs/homarr/issues/3004)) ([a70b0b7](https://github.com/homarr-labs/homarr/commit/a70b0b70479df8fff8a2d256995844bad1fb55a0))
* **deps:** update tanstack-query monorepo to ^5.74.11 ([#2984](https://github.com/homarr-labs/homarr/issues/2984)) ([3299ced](https://github.com/homarr-labs/homarr/commit/3299cedc04cd87c7f014f69a900e7c6e6c33d954))
* **deps:** update tanstack-query monorepo to ^5.74.7 ([#2924](https://github.com/homarr-labs/homarr/issues/2924)) ([f4ce76a](https://github.com/homarr-labs/homarr/commit/f4ce76ac13136c2f559d3aff019dcc2add7c98dd))
* **deps:** update tanstack-query monorepo to ^5.74.9 ([#2983](https://github.com/homarr-labs/homarr/issues/2983)) ([1012cad](https://github.com/homarr-labs/homarr/commit/1012cad0e32f43f46bbb4e89ade3f0eb7fa54313))
* **deps:** update tanstack-query monorepo to ^5.75.0 ([#2998](https://github.com/homarr-labs/homarr/issues/2998)) ([0d1b40c](https://github.com/homarr-labs/homarr/commit/0d1b40c381fc068c770480f81c6c7dcefce0ec86))
* **deps:** update tanstack-query monorepo to ^5.75.1 ([#3006](https://github.com/homarr-labs/homarr/issues/3006)) ([2b1c433](https://github.com/homarr-labs/homarr/commit/2b1c433cefe667d930ce5178cc1218ffb7fa43c0))
* **deps:** update testcontainers-node monorepo to ^10.25.0 ([#2997](https://github.com/homarr-labs/homarr/issues/2997)) ([a934529](https://github.com/homarr-labs/homarr/commit/a934529146a0f53a047d2e3e834145c62c8ee891))
* **deps:** update tiptap monorepo to v2.11.9 ([#2987](https://github.com/homarr-labs/homarr/issues/2987)) ([70686a0](https://github.com/homarr-labs/homarr/commit/70686a00662a7f690efbb45920e5c45ad4a866ef))
* **deps:** update trpc monorepo to ^11.1.2 ([#2982](https://github.com/homarr-labs/homarr/issues/2982)) ([b10f33b](https://github.com/homarr-labs/homarr/commit/b10f33bf0b976217b2076ce84d8d1d9219b36e4c))
* **media-request:** add missing completed status ([#2990](https://github.com/homarr-labs/homarr/issues/2990)) ([9006d3d](https://github.com/homarr-labs/homarr/commit/9006d3d2fc21be5ba2eae616e467113356c91ebe))
* **releases-widget:** improve ui for smaller widgets ([#2962](https://github.com/homarr-labs/homarr/issues/2962)) ([f39a21c](https://github.com/homarr-labs/homarr/commit/f39a21c2962530298274ee705f3d85da023ea3e0))
* **unifi:** port is ignored ([#2995](https://github.com/homarr-labs/homarr/issues/2995)) ([0c9c5b8](https://github.com/homarr-labs/homarr/commit/0c9c5b8955c883191e5b6b137660c1a3cbd16ce4))
## [1.18.1](https://github.com/homarr-labs/homarr/compare/v1.18.0...v1.18.1) (2025-04-27)
### Bug Fixes
* cannot find module http-cookie-agent/http ([#2972](https://github.com/homarr-labs/homarr/issues/2972)) ([bb92f5b](https://github.com/homarr-labs/homarr/commit/bb92f5bb79e8f236dd149e87d14c1a35955140c7))
* **deps:** update dependency mysql2 to v3.14.1 ([#2969](https://github.com/homarr-labs/homarr/issues/2969)) ([5f80beb](https://github.com/homarr-labs/homarr/commit/5f80bebef2bd1598ad02dd1e169a1a6c27a4c143))
## [1.18.0](https://github.com/homarr-labs/homarr/compare/v1.17.0...v1.18.0) (2025-04-25)
### Features
* add releases widget ([#2497](https://github.com/homarr-labs/homarr/issues/2497)) ([3dcee8c](https://github.com/homarr-labs/homarr/commit/3dcee8cb869dd11d54631636253ad17e2af97dd7))
* **media-server:** add option to only show playing sessions ([#2899](https://github.com/homarr-labs/homarr/issues/2899)) ([f98750d](https://github.com/homarr-labs/homarr/commit/f98750d0b3e96b234d6f57b82bf7aa8729a091f2))
* migrate to unifi package ([#2894](https://github.com/homarr-labs/homarr/issues/2894)) ([8550c69](https://github.com/homarr-labs/homarr/commit/8550c69d51497676b654e204a85cdc84e8a38e30))
* **widgets:** add title to advanced options ([#2909](https://github.com/homarr-labs/homarr/issues/2909)) ([c64d903](https://github.com/homarr-labs/homarr/commit/c64d903f2b6375970325fb5031afd468dbf0ae11))
### Bug Fixes
* **certificates:** improve validation and prevent crash ([#2910](https://github.com/homarr-labs/homarr/issues/2910)) ([c514247](https://github.com/homarr-labs/homarr/commit/c51424717ddce5a2f4110658a1fbfab375185f66))
* **deps:** update dependency @auth/core to ^0.39.0 ([#2904](https://github.com/homarr-labs/homarr/issues/2904)) ([b0d7f9a](https://github.com/homarr-labs/homarr/commit/b0d7f9a48c05d380ff84ed7f48c9a9fd8dc33602))
* **deps:** update dependency @auth/drizzle-adapter to ^1.9.0 ([#2905](https://github.com/homarr-labs/homarr/issues/2905)) ([fdf700d](https://github.com/homarr-labs/homarr/commit/fdf700dba51d0bc4e72cdd63a63acb99c2196329))
* **deps:** update dependency @ctrl/qbittorrent to ^9.6.0 ([#2914](https://github.com/homarr-labs/homarr/issues/2914)) ([3d50808](https://github.com/homarr-labs/homarr/commit/3d508088d5a7d7fda53f49c196c2c6dd84d598d1))
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.0 ([#2935](https://github.com/homarr-labs/homarr/issues/2935)) ([eb95ee2](https://github.com/homarr-labs/homarr/commit/eb95ee285b727a8bda7ac9df3f75c25ced475358))
* **deps:** update dependency dockerode to ^4.0.6 ([#2901](https://github.com/homarr-labs/homarr/issues/2901)) ([593e3ee](https://github.com/homarr-labs/homarr/commit/593e3ee5a6f9cb8d50a51262c85c3c067fce0691))
* **deps:** update dependency drizzle-orm to ^0.43.0 ([#2939](https://github.com/homarr-labs/homarr/issues/2939)) ([295cc4a](https://github.com/homarr-labs/homarr/commit/295cc4a3c41ba9ad20e93df9d6ea5627d56d310d))
* **deps:** update dependency drizzle-orm to ^0.43.1 ([#2947](https://github.com/homarr-labs/homarr/issues/2947)) ([1d28bf4](https://github.com/homarr-labs/homarr/commit/1d28bf4c93c43c1956c5b386ca8a02ca6ae59eb5))
* **deps:** update dependency glob to ^11.0.2 ([#2931](https://github.com/homarr-labs/homarr/issues/2931)) ([573d5f8](https://github.com/homarr-labs/homarr/commit/573d5f89d6576020c3a4393372a56f8f75628e58))
* **deps:** update dependency next-auth to v5.0.0-beta.26 ([#2906](https://github.com/homarr-labs/homarr/issues/2906)) ([6e6edc8](https://github.com/homarr-labs/homarr/commit/6e6edc8a0d809614588967b51751257929a6ccd3))
* **deps:** update dependency next-auth to v5.0.0-beta.27 ([#2940](https://github.com/homarr-labs/homarr/issues/2940)) ([2533083](https://github.com/homarr-labs/homarr/commit/253308357a55ab2fac7ac4144215beefdc517b8d))
* **deps:** update dependency next-intl to v4.0.3 ([#2922](https://github.com/homarr-labs/homarr/issues/2922)) ([3172e6e](https://github.com/homarr-labs/homarr/commit/3172e6e0c4e55f62a7ee32ca84fcaa940e774565))
* **deps:** update dependency next-intl to v4.1.0 ([#2938](https://github.com/homarr-labs/homarr/issues/2938)) ([767fe94](https://github.com/homarr-labs/homarr/commit/767fe946d78268eddce6e213665176b8c6397e92))
* **deps:** update dependency sass to ^1.87.0 ([#2921](https://github.com/homarr-labs/homarr/issues/2921)) ([fed28bd](https://github.com/homarr-labs/homarr/commit/fed28bd77546be83b9965bd1116996b621f6b639))
* **deps:** update dependency trpc-to-openapi to ^2.2.0 ([#2903](https://github.com/homarr-labs/homarr/issues/2903)) ([2bf67c3](https://github.com/homarr-labs/homarr/commit/2bf67c3de8609eb504e322d830f507973a55d6fc))
* **deps:** update dependency tsdav to ^2.1.4 ([#2944](https://github.com/homarr-labs/homarr/issues/2944)) ([d78b2e1](https://github.com/homarr-labs/homarr/commit/d78b2e1e85a6c72ce1366e50ee4a1e83176b7913))
* **deps:** update dependency typescript-eslint to ^8.31.0 ([#2919](https://github.com/homarr-labs/homarr/issues/2919)) ([7aa3790](https://github.com/homarr-labs/homarr/commit/7aa379076da24c90238dd5fe2714dbceb3b84c2a))
* **deps:** update mantine monorepo to ^7.17.5 ([#2937](https://github.com/homarr-labs/homarr/issues/2937)) ([aba963c](https://github.com/homarr-labs/homarr/commit/aba963c7cf0d554d51e5fc233e546e4917ecdf67))
* **deps:** update trpc monorepo to ^11.1.1 ([#2927](https://github.com/homarr-labs/homarr/issues/2927)) ([bd3535d](https://github.com/homarr-labs/homarr/commit/bd3535dcf84569b87cf647bc5e0b957667b7e199))
* **deps:** update turbo monorepo to ^2.5.1 ([#2934](https://github.com/homarr-labs/homarr/issues/2934)) ([7766c3c](https://github.com/homarr-labs/homarr/commit/7766c3cbbc1187f6c38157fe15182c8b7d329acf))
* **deps:** update turbo monorepo to ^2.5.2 ([#2949](https://github.com/homarr-labs/homarr/issues/2949)) ([d97e740](https://github.com/homarr-labs/homarr/commit/d97e74047dc107abb05ca65d8a91a41c6071fbd4))
## [1.17.0](https://github.com/homarr-labs/homarr/compare/v1.16.0...v1.17.0) (2025-04-18)
### Features
* add api-key support for tdarr ([#2890](https://github.com/homarr-labs/homarr/issues/2890)) ([3d68f4d](https://github.com/homarr-labs/homarr/commit/3d68f4d1283b6cb27f9532d1adf52cef78a0fb03))
* Prefer primary color ([#2665](https://github.com/homarr-labs/homarr/issues/2665)) ([00053bd](https://github.com/homarr-labs/homarr/commit/00053bda7bbcaaf359074c3b35573ee0afc1409d))
### Bug Fixes
* calculate available space correctly and filter unmounted drives ([#2885](https://github.com/homarr-labs/homarr/issues/2885)) ([cfca0ba](https://github.com/homarr-labs/homarr/commit/cfca0ba58323ec09f8f64da2fee251ef14ba914a))
* **deps:** update dependency drizzle-kit to ^0.31.0 ([#2881](https://github.com/homarr-labs/homarr/issues/2881)) ([97390c6](https://github.com/homarr-labs/homarr/commit/97390c60300f3d16dabf0710fa435c6b3ae86a6f))
* **deps:** update dependency drizzle-orm to ^0.42.0 ([#2882](https://github.com/homarr-labs/homarr/issues/2882)) ([47d7abc](https://github.com/homarr-labs/homarr/commit/47d7abc0d739b91328834e0eaea033637632f22c))
* **deps:** update dependency jotai to ^2.12.3 ([#2868](https://github.com/homarr-labs/homarr/issues/2868)) ([c57b63b](https://github.com/homarr-labs/homarr/commit/c57b63b506733459ea0c09776917042fa878829b))
* **deps:** update dependency recharts to ^2.15.3 ([#2893](https://github.com/homarr-labs/homarr/issues/2893)) ([5fcb7a3](https://github.com/homarr-labs/homarr/commit/5fcb7a3b9c279c1b402e120a4a6f08265e8d5912))
* **deps:** update dependency swagger-ui-react to ^5.21.0 ([#2866](https://github.com/homarr-labs/homarr/issues/2866)) ([ff1263b](https://github.com/homarr-labs/homarr/commit/ff1263b217e96cbb2bc6b8e98ff117c44ca9f00d))
* **deps:** update dependency typescript-eslint to ^8.30.0 ([#2873](https://github.com/homarr-labs/homarr/issues/2873)) ([089734b](https://github.com/homarr-labs/homarr/commit/089734b293a21d6eeaa7c5df5ee9930853ed2fcf))
* **deps:** update dependency typescript-eslint to ^8.30.1 ([#2876](https://github.com/homarr-labs/homarr/issues/2876)) ([fdca513](https://github.com/homarr-labs/homarr/commit/fdca513929e9e594af2d59ad17c915cbc7ac2e08))
* **deps:** update dependency zod to ^3.24.3 ([#2886](https://github.com/homarr-labs/homarr/issues/2886)) ([1bfe397](https://github.com/homarr-labs/homarr/commit/1bfe397d68a88037e35701aab8a7b4c9027ec1e3))
* **deps:** update nextjs monorepo to v15.3.1 ([#2889](https://github.com/homarr-labs/homarr/issues/2889)) ([d37cdbd](https://github.com/homarr-labs/homarr/commit/d37cdbd080c2b3a4df536ab7dad4fc4a4b3c915f))
* **deps:** update tanstack-query monorepo to ^5.73.3 ([#2860](https://github.com/homarr-labs/homarr/issues/2860)) ([4e6ab03](https://github.com/homarr-labs/homarr/commit/4e6ab037c9245103b05afe977e9c288082a2b539))
* **deps:** update tanstack-query monorepo to ^5.74.0 ([#2864](https://github.com/homarr-labs/homarr/issues/2864)) ([ac78e87](https://github.com/homarr-labs/homarr/commit/ac78e8704287f45d6a8ca26cba9b87146e1c6161))
* **deps:** update tanstack-query monorepo to ^5.74.2 ([#2869](https://github.com/homarr-labs/homarr/issues/2869)) ([bf4a1cf](https://github.com/homarr-labs/homarr/commit/bf4a1cf1e8c99ada97adf6ee118ddaafc00115e9))
* **deps:** update tanstack-query monorepo to ^5.74.3 ([#2871](https://github.com/homarr-labs/homarr/issues/2871)) ([02fb1b9](https://github.com/homarr-labs/homarr/commit/02fb1b9dae501e475353e8e2510e4f8938c4e124))
* **deps:** update tanstack-query monorepo to ^5.74.4 ([#2888](https://github.com/homarr-labs/homarr/issues/2888)) ([17174b6](https://github.com/homarr-labs/homarr/commit/17174b6b96ae16b480e2bc5eb9f8ab7b914bc843))
* Tdarr widget empty statistics tab ([#2883](https://github.com/homarr-labs/homarr/issues/2883)) ([2d12c1e](https://github.com/homarr-labs/homarr/commit/2d12c1e641398e20a70f8bfb24a48de02498496a))
* **widget:** format stock values and changes for better readability ([#2836](https://github.com/homarr-labs/homarr/issues/2836)) ([a29556e](https://github.com/homarr-labs/homarr/commit/a29556ee8ec50aa384a9a46f6f5a5fa5dfa4cdac))
## [1.16.0](https://github.com/homarr-labs/homarr/compare/v1.15.0...v1.16.0) (2025-04-11)
### Features
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:22.14.0-alpine AS base
FROM node:22.16.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
+25 -25
View File
@@ -48,21 +48,21 @@
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^7.17.4",
"@mantine/core": "^7.17.4",
"@mantine/dropzone": "^7.17.4",
"@mantine/hooks": "^7.17.4",
"@mantine/modals": "^7.17.4",
"@mantine/tiptap": "^7.17.4",
"@mantine/colors-generator": "^8.0.1",
"@mantine/core": "^8.0.1",
"@mantine/dropzone": "^8.0.1",
"@mantine/hooks": "^8.0.1",
"@mantine/modals": "^8.0.1",
"@mantine/tiptap": "^8.0.1",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.31.0",
"@tanstack/react-query": "^5.72.2",
"@tanstack/react-query-devtools": "^5.72.2",
"@tanstack/react-query-next-experimental": "^5.72.2",
"@trpc/client": "^11.1.0",
"@trpc/next": "^11.1.0",
"@trpc/react-query": "^11.1.0",
"@trpc/server": "^11.1.0",
"@tabler/icons-react": "^3.33.0",
"@tanstack/react-query": "^5.76.2",
"@tanstack/react-query-devtools": "^5.76.2",
"@tanstack/react-query-next-experimental": "^5.76.2",
"@trpc/client": "^11.1.2",
"@trpc/next": "^11.1.2",
"@trpc/react-query": "^11.1.2",
"@trpc/server": "^11.1.2",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0",
@@ -71,34 +71,34 @@
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"flag-icons": "^7.3.2",
"glob": "^11.0.1",
"jotai": "^2.12.2",
"glob": "^11.0.2",
"jotai": "^2.12.4",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.3.0",
"next": "15.3.2",
"postcss-preset-mantine": "^1.17.0",
"prismjs": "^1.30.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-error-boundary": "^5.0.0",
"react-error-boundary": "^6.0.0",
"react-simple-code-editor": "^0.14.1",
"sass": "^1.86.3",
"sass": "^1.89.0",
"superjson": "2.2.2",
"swagger-ui-react": "^5.20.8",
"swagger-ui-react": "^5.22.0",
"use-deep-compare-effect": "^1.8.1",
"zod": "^3.24.2"
"zod": "^3.25.23"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1",
"@types/node": "^22.14.0",
"@types/node": "^22.15.21",
"@types/prismjs": "^1.26.5",
"@types/react": "19.1.1",
"@types/react-dom": "19.1.2",
"@types/react": "19.1.5",
"@types/react-dom": "19.1.5",
"@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.1.2",
"eslint": "^9.24.0",
"eslint": "^9.27.0",
"node-loader": "^2.1.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
@@ -2,7 +2,7 @@
import type { PropsWithChildren } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { z } from "zod";
@@ -15,6 +15,8 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica
import { useScopedI18n } from "@homarr/translation/client";
import { userSignInSchema } from "@homarr/validation/user";
type Provider = "credentials" | "ldap" | "oidc";
interface LoginFormProps {
providers: string[];
oidcClientName: string;
@@ -26,6 +28,8 @@ const extendedValidation = userSignInSchema.extend({ provider: z.enum(["credenti
export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => {
const t = useScopedI18n("user");
const searchParams = useSearchParams();
const isError = searchParams.has("error");
const router = useRouter();
const [isPending, setIsPending] = useState(false);
const form = useZodForm(extendedValidation, {
@@ -39,22 +43,34 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
const credentialInputsVisible = providers.includes("credentials") || providers.includes("ldap");
const onSuccess = useCallback(
async (response: Awaited<ReturnType<typeof signIn>>) => {
if (response && (!response.ok || response.error)) {
async (provider: Provider, response: Awaited<ReturnType<typeof signIn>>) => {
if (!response.ok || response.error) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw response.error;
}
if (provider === "oidc") {
if (!response.url) {
showErrorNotification({
title: t("action.login.notification.error.title"),
message: t("action.login.notification.error.message"),
autoClose: 10000,
});
return;
}
router.push(response.url);
return;
}
showSuccessNotification({
title: t("action.login.notification.success.title"),
message: t("action.login.notification.success.message"),
});
// Redirect to the callback URL if the response is defined and comes from a credentials provider (ldap or credentials). oidc is redirected automatically.
if (response) {
await revalidatePathActionAsync("/");
router.push(callbackUrl);
}
await revalidatePathActionAsync("/");
router.push(callbackUrl);
},
[t, router, callbackUrl],
);
@@ -70,14 +86,14 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
}, [t]);
const signInAsync = useCallback(
async (provider: string, options?: Parameters<typeof signIn>[1]) => {
async (provider: Provider, options?: Parameters<typeof signIn>[1]) => {
setIsPending(true);
await signIn(provider, {
...options,
redirect: false,
callbackUrl: new URL(callbackUrl, window.location.href).href,
})
.then(onSuccess)
.then((response) => onSuccess(provider, response))
.catch(onError);
},
[setIsPending, onSuccess, onError, callbackUrl],
@@ -86,11 +102,12 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
const isLoginInProgress = useRef(false);
useEffect(() => {
if (isError) return;
if (isOidcAutoLoginEnabled && !isPending && !isLoginInProgress.current) {
isLoginInProgress.current = true;
void signInAsync("oidc");
}
}, [signInAsync, isOidcAutoLoginEnabled, isPending]);
}, [signInAsync, isOidcAutoLoginEnabled, isPending, isError]);
return (
<Stack gap="xl">
@@ -4,15 +4,21 @@ import { TRPCError } from "@trpc/server";
// Placed here because gridstack styles are used for board content
import "~/styles/gridstack.scss";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { getQueryClient } from "@homarr/api/server";
import { IntegrationProvider } from "@homarr/auth/client";
import { auth } from "@homarr/auth/next";
import { getIntegrationsWithPermissionsAsync } from "@homarr/auth/server";
import { isNullOrWhitespace } from "@homarr/common";
import type { WidgetKind } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { getI18n } from "@homarr/translation/server";
import { prefetchForKindAsync } from "@homarr/widgets/prefetch";
import { createMetaTitle } from "~/metadata";
import { createBoardLayout } from "../_layout-creator";
import type { Board } from "../_types";
import type { Board, Item } from "../_types";
import { DynamicClientBoard } from "./_dynamic-client";
import { BoardContentHeaderActions } from "./_header-actions";
@@ -31,14 +37,36 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
getInitialBoardAsync: getInitialBoard,
}),
// eslint-disable-next-line no-restricted-syntax
page: async () => {
page: async ({ params }: { params: Promise<TParams> }) => {
const session = await auth();
const integrations = await getIntegrationsWithPermissionsAsync(session);
const board = await getInitialBoard(await params);
const queryClient = getQueryClient();
// Prefetch item data
const itemsMap = board.items.reduce((acc, item) => {
const existing = acc.get(item.kind);
if (existing) {
existing.push(item);
} else {
acc.set(item.kind, [item]);
}
return acc;
}, new Map<WidgetKind, Item[]>());
for (const [kind, items] of itemsMap) {
await prefetchForKindAsync(kind, queryClient, items).catch((error) => {
logger.error(new Error("Failed to prefetch widget", { cause: error }));
});
}
return (
<IntegrationProvider integrations={integrations}>
<DynamicClientBoard />
</IntegrationProvider>
<HydrationBoundary state={dehydrate(queryClient)}>
<IntegrationProvider integrations={integrations}>
<DynamicClientBoard />
</IntegrationProvider>
</HydrationBoundary>
);
},
generateMetadataAsync: async ({ params }: { params: Promise<TParams> }): Promise<Metadata> => {
@@ -123,7 +123,7 @@ export const ColorSettingsContent = ({ board }: Props) => {
</Grid.Col>
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -159,7 +159,7 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -38,7 +38,7 @@ export const BehaviorSettingsContent = ({ board }: Props) => {
/>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -46,7 +46,7 @@ export const CustomCssSettingsContent = ({ board }: Props) => {
</Alert>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -116,7 +116,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
</Grid.Col>
</Grid>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -105,7 +105,7 @@ export const LayoutSettingsContent = ({ board }: Props) => {
</Stack>
<Group justify="end">
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
+10 -1
View File
@@ -7,6 +7,7 @@ import "@homarr/ui/styles.css";
import "~/styles/scroll-area.scss";
import { notFound } from "next/navigation";
import type { DayOfWeek } from "@mantine/dates";
import { NextIntlClientProvider } from "next-intl";
import { api } from "@homarr/api/server";
@@ -87,7 +88,15 @@ export default async function Layout(props: {
},
(innerProps) => (
<SettingsProvider
user={user}
user={
user
? {
...user,
// Convert type, because output schema is not smart enough to infer $type from drizzle
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
}
: null
}
serverSettings={{
board: {
homeBoardId: serverSettings.board.homeBoardId,
@@ -0,0 +1,96 @@
import { useMemo } from "react";
import { Accordion, Anchor, Card, Stack, Text } from "@mantine/core";
import { IconSubtask } from "@tabler/icons-react";
import { getMantineColor } from "@homarr/common";
import { useI18n } from "@homarr/translation/client";
import { CertificateErrorDetails } from "./test-connection-certificate";
import type { AnyMappedTestConnectionError, MappedError } from "./types";
interface IntegrationTestConnectionErrorProps {
error: AnyMappedTestConnectionError;
url: string;
}
export const IntegrationTestConnectionError = ({ error, url }: IntegrationTestConnectionErrorProps) => {
const t = useI18n();
const causeArray = useMemo(() => toCauseArray(error.cause), [error.cause]);
return (
<Card withBorder style={{ borderColor: getMantineColor("red", 8) }}>
<Stack>
<Stack gap="sm">
<Text size="lg" fw={500} c="red.8">
{t(`integration.testConnection.error.${error.type}.title`)}
</Text>
{error.type !== "request" && error.type !== "certificate" && error.type !== "statusCode" ? (
<Text size="md">{t(`integration.testConnection.error.${error.type}.description`)}</Text>
) : null}
{error.type === "request" ? (
<Text size="md">
{t(
`integration.testConnection.error.request.description.${error.data.type}.${error.data.reason}` as never,
)}
</Text>
) : null}
{error.type === "statusCode" ? (
error.data.reason === "other" ? (
<Text size="md">
{t.rich("integration.testConnection.error.statusCode.otherDescription", {
statusCode: error.data.statusCode.toString(),
url: () => <Anchor href={error.data.url}>{error.data.url}</Anchor>,
})}
</Text>
) : (
<Text size="md">
{t.rich("integration.testConnection.error.statusCode.description", {
reason: t(`integration.testConnection.error.statusCode.reason.${error.data.reason}`),
statusCode: error.data.statusCode.toString(),
url: () => <Anchor href={error.data.url}>{error.data.url}</Anchor>,
})}
</Text>
)
) : null}
{error.type === "certificate" ? <CertificateErrorDetails error={error} url={url} /> : null}
</Stack>
{error.cause ? (
<Accordion variant="contained">
<Accordion.Item value="cause">
<Accordion.Control icon={<IconSubtask size={16} stroke={1.5} />}>
{t("integration.testConnection.error.common.cause.title")}
</Accordion.Control>
<Accordion.Panel>
<pre style={{ whiteSpace: "pre-wrap" }}>
{error.name}: {error.message}
{"\n"}
{causeArray
.map(
(cause) =>
`caused by ${cause.name}${cause.message ? `: ${cause.message}` : ""} ${cause.metadata.map((item) => `${item.key}=${item.value}`).join(" ")}`,
)
.join("\n")}
</pre>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
) : null}
</Stack>
</Card>
);
};
const toCauseArray = (cause: MappedError | undefined) => {
const causeArray: MappedError[] = [];
let currentCause: MappedError | undefined = cause;
while (currentCause) {
causeArray.push(currentCause);
currentCause = currentCause.cause;
}
return causeArray.map(({ cause: _innerCause, ...cause }) => cause);
};
@@ -0,0 +1,327 @@
import { useState } from "react";
import { ActionIcon, Alert, Anchor, Button, Card, CopyButton, Group, SimpleGrid, Stack, Text } from "@mantine/core";
import { IconAlertTriangle, IconCheck, IconCopy, IconExclamationCircle, IconRepeat } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { getMantineColor } from "@homarr/common";
import { createId } from "@homarr/db/client";
import { createDocumentationLink } from "@homarr/definitions";
import { createModal, useConfirmModal, useModalAction } from "@homarr/modals";
import { AddCertificateModal } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useCurrentLocale, useI18n, useScopedI18n } from "@homarr/translation/client";
import type { MappedCertificate, MappedTestConnectionCertificateError } from "./types";
interface CertificateErrorDetailsProps {
error: MappedTestConnectionCertificateError;
url: string;
}
export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsProps) => {
const tError = useScopedI18n("integration.testConnection.error");
const { data: session } = useSession();
const isAdmin = session?.user.permissions.includes("admin") ?? false;
const [showRetryButton, setShowRetryButton] = useState(false);
const { openModal: openUploadModal } = useModalAction(AddCertificateModal);
const { openConfirmModal } = useConfirmModal();
const { mutateAsync: trustHostnameAsync } = clientApi.certificates.trustHostnameMismatch.useMutation();
const { mutateAsync: addCertificateAsync } = clientApi.certificates.addCertificate.useMutation();
const handleTrustHostname = () => {
const { hostname } = new URL(url);
openConfirmModal({
title: tError("certificate.hostnameMismatch.confirm.title"),
children: tError("certificate.hostnameMismatch.confirm.message"),
// eslint-disable-next-line no-restricted-syntax
async onConfirm() {
await trustHostnameAsync(
{
hostname,
certificate: error.data.certificate.pem,
},
{
onSuccess() {
showSuccessNotification({
title: tError("certificate.hostnameMismatch.notification.success.title"),
message: tError("certificate.hostnameMismatch.notification.success.message"),
});
setShowRetryButton(true);
},
onError() {
showErrorNotification({
title: tError("certificate.hostnameMismatch.notification.error.title"),
message: tError("certificate.hostnameMismatch.notification.error.message"),
});
},
},
);
},
});
};
const handleTrustSelfSigned = () => {
const { hostname } = new URL(url);
openConfirmModal({
title: tError("certificate.selfSigned.confirm.title"),
children: tError("certificate.selfSigned.confirm.message"),
// eslint-disable-next-line no-restricted-syntax
async onConfirm() {
const formData = new FormData();
formData.append(
"file",
new File([error.data.certificate.pem], `${hostname}-${createId()}.crt`, {
type: "application/x-x509-ca-cert",
}),
);
await addCertificateAsync(formData, {
onSuccess() {
showSuccessNotification({
title: tError("certificate.selfSigned.notification.success.title"),
message: tError("certificate.selfSigned.notification.success.message"),
});
setShowRetryButton(true);
},
onError() {
showErrorNotification({
title: tError("certificate.selfSigned.notification.error.title"),
message: tError("certificate.selfSigned.notification.error.message"),
});
},
});
},
});
};
const description = <Text size="md">{tError(`certificate.description.${error.data.reason}`)}</Text>;
if (!isAdmin) {
return (
<>
{description}
<NotEnoughPermissionsAlert />
</>
);
}
return (
<>
{description}
<CertificateDetailsCard certificate={error.data.certificate} />
{error.data.reason === "hostnameMismatch" && <HostnameMismatchAlert />}
{!error.data.certificate.isSelfSigned && error.data.reason === "untrusted" && <CertificateExtractAlert />}
{showRetryButton && (
<Button
variant="default"
fullWidth
leftSection={<IconRepeat size={16} color={getMantineColor("blue", 6)} stroke={1.5} />}
type="submit"
>
{tError("certificate.action.retry.label")}
</Button>
)}
{(error.data.reason === "untrusted" && error.data.certificate.isSelfSigned) ||
error.data.reason === "hostnameMismatch" ? (
<Button
variant="default"
fullWidth
onClick={error.data.reason === "hostnameMismatch" ? handleTrustHostname : handleTrustSelfSigned}
>
{tError("certificate.action.trust.label")}
</Button>
) : null}
{error.data.reason === "untrusted" && !error.data.certificate.isSelfSigned ? (
<Button
variant="default"
fullWidth
onClick={() =>
openUploadModal({
onSuccess() {
setShowRetryButton(true);
},
})
}
>
{tError("certificate.action.upload.label")}
</Button>
) : null}
</>
);
};
const NotEnoughPermissionsAlert = () => {
const t = useI18n();
return (
<Alert
icon={<IconAlertTriangle size={16} />}
title={t("integration.testConnection.error.certificate.alert.permission.title")}
color="yellow"
>
{t("integration.testConnection.error.certificate.alert.permission.message")}
</Alert>
);
};
const HostnameMismatchAlert = () => {
const t = useI18n();
return (
<Alert
icon={<IconAlertTriangle size={16} />}
title={t("integration.testConnection.error.certificate.alert.hostnameMismatch.title")}
color="yellow"
>
{t("integration.testConnection.error.certificate.alert.hostnameMismatch.message")}
</Alert>
);
};
const CertificateExtractAlert = () => {
const t = useI18n();
return (
<Alert
icon={<IconExclamationCircle size={16} />}
title={t("integration.testConnection.error.certificate.alert.extract.title")}
color="red"
>
{t.rich("integration.testConnection.error.certificate.alert.extract.message", {
docsLink: () => (
<Anchor
href={createDocumentationLink("/docs/management/certificates", "#obtaining-certificates")}
target="_blank"
>
{t("common.here")}
</Anchor>
),
})}
</Alert>
);
};
interface CertificateDetailsProps {
certificate: MappedCertificate;
}
export const CertificateDetailsCard = ({ certificate }: CertificateDetailsProps) => {
const { openModal } = useModalAction(PemContentModal);
const locale = useCurrentLocale();
const tDetails = useScopedI18n("integration.testConnection.error.certificate.details");
const tCertificateField = useScopedI18n("certificate.field");
return (
<Card withBorder>
<Text fw={500}>{tDetails("title")}</Text>
<Group justify="space-between">
<Text size="sm" c="dimmed">
{tDetails("description")}
</Text>
<Anchor
size="sm"
ta="start"
component="button"
type="button"
onClick={() => openModal({ content: certificate.pem })}
>
{tDetails("content.action")}
</Anchor>
</Group>
<SimpleGrid cols={{ base: 1, md: 2 }} mt="md">
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("subject.label")}
</Text>
<Text size="sm">{certificate.subject}</Text>
</Stack>
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("issuer.label")}
</Text>
<Text size="sm">{certificate.issuer}</Text>
</Stack>
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("validFrom.label")}
</Text>
<Text size="sm">
{new Intl.DateTimeFormat(locale, {
dateStyle: "full",
timeStyle: "long",
}).format(certificate.validFrom)}
</Text>
</Stack>
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("validTo.label")}
</Text>
<Text size="sm">
{new Intl.DateTimeFormat(locale, {
dateStyle: "full",
timeStyle: "long",
}).format(certificate.validTo)}
</Text>
</Stack>
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("serialNumber.label")}
</Text>
<Text size="sm">{certificate.serialNumber}</Text>
</Stack>
</SimpleGrid>
<SimpleGrid cols={1} mt="md">
<Stack gap={0}>
<Text size="xs" c="dimmed">
{tCertificateField("fingerprint.label")}
</Text>
<Text size="sm">{certificate.fingerprint}</Text>
</Stack>
</SimpleGrid>
</Card>
);
};
const PemContentModal = createModal<{ content: string }>(({ actions, innerProps }) => {
const t = useI18n();
return (
<Stack>
<Card w="100%" pos="relative" bg="dark.6" fz="xs" p="sm">
<pre
style={{
whiteSpace: "pre-wrap",
wordWrap: "break-word",
}}
>
{innerProps.content}
</pre>
<CopyButton value={innerProps.content}>
{({ copy, copied }) => (
<ActionIcon onClick={copy} pos="absolute" top={8} right={8} variant="default">
{copied ? (
<IconCheck size={16} stroke={1.5} color={getMantineColor("green", 6)} />
) : (
<IconCopy size={16} stroke={1.5} />
)}
</ActionIcon>
)}
</CopyButton>
</Card>
<Button variant="light" color="gray" onClick={actions.closeModal}>
{t("common.action.close")}
</Button>
</Stack>
);
}).withOptions({
defaultTitle(t) {
return t("integration.testConnection.error.certificate.details.content.title");
},
size: "lg",
});
@@ -0,0 +1,6 @@
import type { RouterOutputs } from "@homarr/api";
export type AnyMappedTestConnectionError = Exclude<RouterOutputs["integration"]["create"], undefined>["error"];
export type MappedTestConnectionCertificateError = Extract<AnyMappedTestConnectionError, { type: "certificate" }>;
export type MappedCertificate = MappedTestConnectionCertificateError["data"]["certificate"];
export type MappedError = Exclude<AnyMappedTestConnectionError["cause"], undefined>;
@@ -1,5 +1,6 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
@@ -10,7 +11,6 @@ import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions";
import { useZodForm } from "@homarr/form";
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
import { useConfirmModal } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client";
@@ -18,6 +18,8 @@ import { integrationUpdateSchema } from "@homarr/validation/integration";
import { SecretCard } from "../../_components/secrets/integration-secret-card";
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
import { IntegrationTestConnectionError } from "../../_components/test-connection/integration-test-connection-error";
import type { AnyMappedTestConnectionError } from "../../_components/test-connection/types";
interface EditIntegrationForm {
integration: RouterOutputs["integration"]["byId"];
@@ -43,6 +45,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
},
});
const { mutateAsync, isPending } = clientApi.integration.update.useMutation();
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret]));
@@ -57,26 +60,24 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
})),
},
{
onSuccess: () => {
onSuccess: (data) => {
// We do it this way as we are unable to send a typesafe error through onError
if (data?.error) {
setError(data.error);
showErrorNotification({
title: t("integration.page.edit.notification.error.title"),
message: t("integration.page.edit.notification.error.message"),
});
return;
}
showSuccessNotification({
title: t("integration.page.edit.notification.success.title"),
message: t("integration.page.edit.notification.success.message"),
});
void revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
},
onError: (error) => {
const testConnectionError = convertIntegrationTestConnectionError(error.data?.error);
if (testConnectionError) {
showErrorNotification({
title: t(`integration.testConnection.notification.${testConnectionError.key}.title`),
message:
testConnectionError.message ??
t(`integration.testConnection.notification.${testConnectionError.key}.message`),
});
return;
}
onError: () => {
showErrorNotification({
title: t("integration.page.edit.notification.error.title"),
message: t("integration.page.edit.notification.error.message"),
@@ -128,6 +129,8 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
</Stack>
</Fieldset>
{error !== null && <IntegrationTestConnectionError error={error} url={form.values.url} />}
<Group justify="end" align="center">
<Button variant="default" component={Link} href="/manage/integrations">
{t("common.action.backToOverview")}
@@ -24,13 +24,14 @@ import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions
import { getAllSecretKindOptions, getIconUrl, getIntegrationName, integrationDefs } from "@homarr/definitions";
import type { UseFormReturnType } from "@homarr/form";
import { useZodForm } from "@homarr/form";
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } 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 { IntegrationTestConnectionError } from "../_components/test-connection/integration-test-connection-error";
import type { AnyMappedTestConnectionError } from "../_components/test-connection/types";
interface NewIntegrationFormProps {
searchParams: Partial<z.infer<typeof integrationCreateSchema>> & {
@@ -73,6 +74,7 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
clientApi.integration.create.useMutation();
const { mutateAsync: createAppAsync, isPending: isPendingApp } = clientApi.app.create.useMutation();
const isPending = isPendingIntegration || isPendingApp;
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
const handleSubmitAsync = async (values: FormType) => {
await createIntegrationAsync(
@@ -81,7 +83,17 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
...values,
},
{
async onSuccess() {
async onSuccess(data) {
// We do it this way as we are unable to send a typesafe error through onError
if (data?.error) {
setError(data.error);
showErrorNotification({
title: t("integration.page.create.notification.error.title"),
message: t("integration.page.create.notification.error.message"),
});
return;
}
showSuccessNotification({
title: t("integration.page.create.notification.success.title"),
message: t("integration.page.create.notification.success.message"),
@@ -114,19 +126,7 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
},
);
},
onError: (error) => {
const testConnectionError = convertIntegrationTestConnectionError(error.data?.error);
if (testConnectionError) {
showErrorNotification({
title: t(`integration.testConnection.notification.${testConnectionError.key}.title`),
message:
testConnectionError.message ??
t(`integration.testConnection.notification.${testConnectionError.key}.message`),
});
return;
}
onError: () => {
showErrorNotification({
title: t("integration.page.create.notification.error.title"),
message: t("integration.page.create.notification.error.message"),
@@ -164,6 +164,8 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
</Stack>
</Fieldset>
{error !== null && <IntegrationTestConnectionError error={error} url={form.values.url} />}
{supportsSearchEngine && (
<Checkbox
label={t("integration.field.attemptSearchEngineCreation.label")}
@@ -1,82 +1,23 @@
"use client";
import { Button, FileInput, Group, Stack } from "@mantine/core";
import { IconCertificate } from "@tabler/icons-react";
import { z } from "zod";
import { Button } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { useZodForm } from "@homarr/form";
import { createModal, useModalAction } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useModalAction } from "@homarr/modals";
import { AddCertificateModal } from "@homarr/modals-collection";
import { useI18n } from "@homarr/translation/client";
import { superRefineCertificateFile } from "@homarr/validation/certificates";
export const AddCertificateButton = () => {
const { openModal } = useModalAction(AddCertificateModal);
const t = useI18n();
const handleClick = () => {
openModal({});
openModal({
async onSuccess() {
await revalidatePathActionAsync("/manage/tools/certificates");
},
});
};
return <Button onClick={handleClick}>{t("certificate.action.create.label")}</Button>;
};
const AddCertificateModal = createModal(({ actions }) => {
const t = useI18n();
const form = useZodForm(
z.object({
file: z.instanceof(File).nullable().superRefine(superRefineCertificateFile),
}),
{
initialValues: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
file: null!,
},
},
);
const { mutateAsync } = clientApi.certificates.addCertificate.useMutation();
return (
<form
onSubmit={form.onSubmit(async (values) => {
const formData = new FormData();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
formData.set("file", values.file!);
await mutateAsync(formData, {
async onSuccess() {
showSuccessNotification({
title: t("certificate.action.create.notification.success.title"),
message: t("certificate.action.create.notification.success.message"),
});
await revalidatePathActionAsync("/manage/tools/certificates");
actions.closeModal();
},
onError() {
showErrorNotification({
title: t("certificate.action.create.notification.error.title"),
message: t("certificate.action.create.notification.error.message"),
});
},
});
})}
>
<Stack>
<FileInput leftSection={<IconCertificate size={16} />} {...form.getInputProps("file")} />
<Group justify="end">
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" loading={form.submitting}>
{t("common.action.add")}
</Button>
</Group>
</Stack>
</form>
);
}).withOptions({
defaultTitle(t) {
return t("certificate.action.create.label");
},
});
@@ -0,0 +1,52 @@
"use client";
import { ActionIcon } from "@mantine/core";
import { IconTrash } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { useConfirmModal } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client";
interface RemoveHostnameActionIconProps {
hostname: string;
thumbprint: string;
}
export const RemoveHostnameActionIcon = (input: RemoveHostnameActionIconProps) => {
const { mutateAsync } = clientApi.certificates.removeTrustedHostname.useMutation();
const { openConfirmModal } = useConfirmModal();
const t = useI18n();
const handleRemove = () => {
openConfirmModal({
title: t("certificate.action.removeHostname.label"),
children: t("certificate.action.removeHostname.confirm"),
// eslint-disable-next-line no-restricted-syntax
async onConfirm() {
await mutateAsync(input, {
async onSuccess() {
await revalidatePathActionAsync("/manage/tools/certificates/hostnames");
showSuccessNotification({
title: t("certificate.action.removeHostname.notification.success.title"),
message: t("certificate.action.removeHostname.notification.success.message"),
});
},
onError() {
showErrorNotification({
title: t("certificate.action.removeHostname.notification.error.title"),
message: t("certificate.action.removeHostname.notification.error.message"),
});
},
});
},
});
};
return (
<ActionIcon color="red" variant="subtle" onClick={handleRemove}>
<IconTrash color="red" size={16} stroke={1.5} />
</ActionIcon>
);
};
@@ -0,0 +1,99 @@
import { X509Certificate } from "node:crypto";
import Link from "next/link";
import { notFound } from "next/navigation";
import {
Button,
Group,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from "@mantine/core";
import { IconCertificateOff } from "@tabler/icons-react";
import { auth } from "@homarr/auth/next";
import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server";
import { getI18n } from "@homarr/translation/server";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results";
import { RemoveHostnameActionIcon } from "./_components/remove-hostname";
export default async function TrustedHostnamesPage() {
const session = await auth();
if (!session?.user.permissions.includes("admin")) {
notFound();
}
const t = await getI18n();
const trustedHostnames = await getTrustedCertificateHostnamesAsync().then((hostnames) => {
return hostnames.map((hostname) => {
let subject: string | null;
try {
subject = new X509Certificate(hostname.certificate).subject;
} catch {
subject = null;
}
return {
...hostname,
subject,
};
});
});
return (
<>
<DynamicBreadcrumb />
<Stack>
<Group justify="space-between">
<Stack gap={4}>
<Title>{t("certificate.page.hostnames.title")}</Title>
<Text>{t("certificate.page.hostnames.description")}</Text>
</Stack>
<Button variant="default" component={Link} href="/manage/tools/certificates">
{t("certificate.page.hostnames.toCertificates")}
</Button>
</Group>
{trustedHostnames.length === 0 && (
<NoResults icon={IconCertificateOff} title={t("certificate.page.hostnames.noResults.title")} />
)}
{trustedHostnames.length >= 1 && (
<Table>
<TableThead>
<TableTr>
<TableTh>{t("certificate.field.hostname.label")}</TableTh>
<TableTh>{t("certificate.field.subject.label")}</TableTh>
<TableTh>{t("certificate.field.fingerprint.label")}</TableTh>
<TableTh></TableTh>
</TableTr>
</TableThead>
<TableTbody>
{trustedHostnames.map(({ hostname, subject, thumbprint }) => (
<TableTr key={`${hostname}-${thumbprint}`}>
<TableTd>{hostname}</TableTd>
<TableTd>{subject}</TableTd>
<TableTd>{thumbprint}</TableTd>
<TableTd>
<Group justify="end">
<RemoveHostnameActionIcon hostname={hostname} thumbprint={thumbprint} />
</Group>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
)}
</Stack>
</>
);
}
@@ -1,7 +1,8 @@
import { X509Certificate } from "node:crypto";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core";
import { IconCertificate, IconCertificateOff } from "@tabler/icons-react";
import { Button, Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core";
import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/icons-react";
import dayjs from "dayjs";
import { auth } from "@homarr/auth/next";
@@ -31,11 +32,27 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
const t = await getI18n();
const certificates = await loadCustomRootCertificatesAsync();
const x509Certificates = certificates
.map((cert) => ({
...cert,
x509: new X509Certificate(cert.content),
}))
.sort((certA, certB) => certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime());
.map((cert) => {
try {
const x509 = new X509Certificate(cert.content);
return {
...cert,
isError: false,
x509,
} as const;
} catch {
return {
...cert,
isError: true,
x509: null,
} as const;
}
})
.sort((certA, certB) => {
if (certA.isError) return -1;
if (certB.isError) return 1;
return certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime();
});
return (
<>
@@ -48,7 +65,12 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
<Text>{t("certificate.page.list.description")}</Text>
</Stack>
<AddCertificateButton />
<Group>
<Button variant="default" component={Link} href="/manage/tools/certificates/hostnames">
{t("certificate.page.list.toHostnames")}
</Button>
<AddCertificateButton />
</Group>
</Group>
{x509Certificates.length === 0 && (
@@ -57,32 +79,47 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
<SimpleGrid cols={{ sm: 1, lg: 2, xl: 3 }} spacing="lg">
{x509Certificates.map((cert) => (
<Card key={cert.x509.fingerprint} withBorder>
<Card key={cert.fileName} withBorder>
<Group wrap="nowrap">
<IconCertificate
color={getMantineColor(iconColor(cert.x509.validToDate), 6)}
style={{ minWidth: 32 }}
size={32}
stroke={1.5}
/>
{cert.isError ? (
<IconAlertTriangle
color={getMantineColor("red", 6)}
style={{ minWidth: 32 }}
size={32}
stroke={1.5}
/>
) : (
<IconCertificate
color={getMantineColor(iconColor(cert.x509.validToDate), 6)}
style={{ minWidth: 32 }}
size={32}
stroke={1.5}
/>
)}
<Stack flex={1} gap="xs" maw="calc(100% - 48px)">
<Group justify="space-between" wrap="nowrap">
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
{cert.x509.subject}
{cert.isError ? t("certificate.page.list.invalid.title") : cert.x509.subject}
</Text>
<Text c="gray.6" ta="end" size="sm">
{cert.fileName}
</Text>
</Group>
<Group justify="space-between">
<Text size="sm" c="gray.6" title={cert.x509.validToDate.toISOString()}>
{t("certificate.page.list.expires", {
when: new Intl.RelativeTimeFormat(locale).format(
dayjs(cert.x509.validToDate).diff(dayjs(), "days"),
"days",
),
})}
</Text>
{cert.isError ? (
<Text size="sm" c="gray.6">
{t("certificate.page.list.invalid.description")}
</Text>
) : (
<Text size="sm" c="gray.6" title={cert.x509.validToDate.toISOString()}>
{t("certificate.page.list.expires", {
when: new Intl.RelativeTimeFormat(locale).format(
dayjs(cert.x509.validToDate).diff(dayjs(), "days"),
"days",
),
})}
</Text>
)}
<RemoveCertificate fileName={cert.fileName} />
</Group>
</Stack>
@@ -17,6 +17,7 @@ import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useTimeAgo } from "@homarr/common";
import type { ContainerState } from "@homarr/docker";
import { containerStateColorMap } from "@homarr/docker/shared";
import { useModalAction } from "@homarr/modals";
import { AddDockerAppToHomarr } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
@@ -244,21 +245,11 @@ const ContainerActionBarButton = (props: ContainerActionBarButtonProps) => {
);
};
const containerStates = {
created: "cyan",
running: "green",
paused: "yellow",
restarting: "orange",
exited: "red",
removing: "pink",
dead: "dark",
} satisfies Record<ContainerState, MantineColor>;
const ContainerStateBadge = ({ state }: { state: ContainerState }) => {
const t = useScopedI18n("docker.field.state.option");
return (
<Badge size="lg" radius="sm" variant="light" w={120} color={containerStates[state]}>
<Badge size="lg" radius="sm" variant="light" w={120} color={containerStateColorMap[state]}>
{t(state)}
</Badge>
);
@@ -73,7 +73,7 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -66,7 +66,7 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -71,7 +71,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
</Radio.Group>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -57,7 +57,7 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -81,7 +81,7 @@ export const UserProfileForm = ({ user }: UserProfileFormProps) => {
{isProviderCredentials && (
<Group justify="end">
<Button type="submit" color="teal" disabled={!form.isDirty()} loading={isPending}>
<Button type="submit" disabled={!form.isDirty()} loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -85,7 +85,7 @@ export const ChangePasswordForm = ({ user }: ChangePasswordFormProps) => {
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.confirm")}
</Button>
</Group>
@@ -68,7 +68,7 @@ export const RenameGroupForm = ({ group, disabled }: RenameGroupFormProps) => {
{!disabled && (
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -40,9 +40,5 @@ export const AddGroupMember = ({ groupId, presentUserIds }: AddGroupMemberProps)
);
}, [openModal, presentUserIds, groupId, mutateAsync, tMembersAdd]);
return (
<MobileAffixButton color="teal" onClick={handleAddMember}>
{tMembersAdd("label")}
</MobileAffixButton>
);
return <MobileAffixButton onClick={handleAddMember}>{tMembersAdd("label")}</MobileAffixButton>;
};
@@ -1,7 +1,7 @@
"use client";
import { useCallback } from "react";
import type { PropsWithChildren } from "react";
import { useCallback } from "react";
import { Button, Card, Group, Switch, Text, Transition } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
@@ -108,7 +108,7 @@ export const SaveAffix = ({ groupId }: SaveAffixProps) => {
<Button disabled={isPending} onClick={form.reset}>
{t("common.action.discard")}
</Button>
<Button color="teal" loading={isPending} onClick={handleSubmit}>
<Button loading={isPending} onClick={handleSubmit}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -71,7 +71,7 @@ export const GroupHomeBoards = ({ homeBoardId, mobileHomeBoardId, groupId }: Gro
/>
<Group justify="end">
<Button type="submit" color="teal" loading={isPending}>
<Button type="submit" loading={isPending}>
{t("common.action.save")}
</Button>
</Group>
@@ -57,9 +57,5 @@ const AddGroup = () => {
openModal();
}, [openModal]);
return (
<MobileAffixButton onClick={handleAddGroup} color="teal">
{t("group.action.create.label")}
</MobileAffixButton>
);
return <MobileAffixButton onClick={handleAddGroup}>{t("group.action.create.label")}</MobileAffixButton>;
};
@@ -264,7 +264,7 @@ const SaveAffix = ({ visible, isPending, onDiscard, onSave }: SaveAffixProps) =>
<Button disabled={isPending} onClick={onDiscard}>
{t("common.action.discard")}
</Button>
<Button color="teal" loading={isPending} onClick={onSave}>
<Button loading={isPending} onClick={onSave}>
{t("common.action.saveChanges")}
</Button>
</Group>
@@ -89,11 +89,7 @@ const RenderTopToolbarCustomActions = () => {
openModal();
}, [openModal]);
return (
<Button color="teal" onClick={handleNewInvite}>
{t("action.new.title")}
</Button>
);
return <Button onClick={handleNewInvite}>{t("action.new.title")}</Button>;
};
const RenderRowActions = ({ row }: { row: MRT_Row<RouterOutputs["invite"]["getAll"][number]> }) => {
@@ -48,6 +48,7 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
options: reduceWidgetOptionsWithDefaultValues(kind, settings, {}),
integrationIds: [],
advancedOptions: {
title: null,
customCssClasses: [],
borderColor: "",
},
@@ -82,7 +82,7 @@ export const GroupAccessForm = <TPermission extends string>({
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
{t("common.action.add")}
</Button>
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("permission.action.saveGroup")}
</Button>
</Group>
@@ -104,7 +104,7 @@ export const UsersAccessForm = <TPermission extends string>({
<Button rightSection={<IconPlus size="1rem" />} variant="light" onClick={handleAddUser}>
{t("common.action.add")}
</Button>
<Button type="submit" loading={isPending} color="teal">
<Button type="submit" loading={isPending}>
{t("permission.action.saveUser")}
</Button>
</Group>
@@ -29,6 +29,7 @@ export const createItemCallback =
layouts: createItemLayouts(previous, firstSection),
integrationIds: [],
advancedOptions: {
title: null,
customCssClasses: [],
borderColor: "",
},
@@ -20,7 +20,7 @@ describe("item actions duplicate-item", () => {
kind: itemKind,
integrationIds: ["1"],
options: { address: "localhost" },
advancedOptions: { customCssClasses: ["test"], borderColor: "#ff0000" },
advancedOptions: { title: "The best one", customCssClasses: ["test"], borderColor: "#ff0000" },
})
.addLayout({ layoutId, sectionId: currentSectionId, ...currentItemSize })
.build();
@@ -13,6 +13,7 @@ export class ItemMockBuilder {
layouts: [],
integrationIds: [],
advancedOptions: {
title: null,
customCssClasses: [],
borderColor: "",
},
@@ -0,0 +1,12 @@
.badge {
@mixin dark {
--background-color: rgb(from var(--mantine-color-dark-6) r g b / var(--opacity));
--border-color: rgb(from var(--mantine-color-dark-4) r g b / var(--opacity));
}
@mixin light {
--background-color: rgb(from var(--mantine-color-white) r g b / var(--opacity));
--border-color: rgb(from var(--mantine-color-gray-3) r g b / var(--opacity));
}
background-color: var(--background-color) !important;
border-color: var(--border-color) !important;
}
@@ -1,4 +1,4 @@
import { Card } from "@mantine/core";
import { Badge, Card } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { QueryErrorResetBoundary } from "@tanstack/react-query";
import combineClasses from "clsx";
@@ -14,6 +14,7 @@ import { WidgetError } from "@homarr/widgets/errors";
import type { SectionItem } from "~/app/[locale]/boards/_types";
import classes from "../sections/item.module.css";
import { useItemActions } from "./item-actions";
import itemContentClasses from "./item-content.module.css";
import { BoardItemMenu } from "./item-menu";
interface BoardItemContentProps {
@@ -25,28 +26,51 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
const board = useRequiredBoard();
return (
<Card
ref={ref}
className={combineClasses(
classes.itemCard,
`${item.kind}-wrapper`,
"grid-stack-item-content",
item.advancedOptions.customCssClasses.join(" "),
<>
<Card
ref={ref}
className={combineClasses(
classes.itemCard,
`${item.kind}-wrapper`,
"grid-stack-item-content",
item.advancedOptions.customCssClasses.join(" "),
)}
radius={board.itemRadius}
withBorder
styles={{
root: {
"--opacity": board.opacity / 100,
containerType: "size",
overflow: item.kind === "iframe" ? "hidden" : undefined,
"--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined,
},
}}
p={0}
>
<InnerContent item={item} width={width} height={height} />
</Card>
{item.advancedOptions.title?.trim() && (
<Badge
pos="absolute"
// It's 4 because of the mantine-react-table that has z-index 3
style={{ zIndex: 4 }}
top={2}
left={16}
size="xs"
radius={board.itemRadius}
styles={{
root: {
"--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined,
"--opacity": board.opacity / 100,
},
}}
className={itemContentClasses.badge}
c="var(--mantine-color-text)"
>
{item.advancedOptions.title}
</Badge>
)}
radius={board.itemRadius}
withBorder
styles={{
root: {
"--opacity": board.opacity / 100,
containerType: "size",
overflow: item.kind === "iframe" ? "hidden" : undefined,
"--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined,
},
}}
p={0}
>
<InnerContent item={item} width={width} height={height} />
</Card>
</>
);
};
@@ -101,8 +125,8 @@ const InnerContent = ({ item, ...dimensions }: InnerContentProps) => {
setOptions={(partialNewOptions) =>
updateOptions({
newOptions: {
...partialNewOptions.newOptions,
...options,
...partialNewOptions.newOptions,
},
})
}
@@ -40,9 +40,7 @@ export const CategoryEditModal = createModal<InnerProps>(({ actions, innerProps
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{innerProps.submitLabel}
</Button>
<Button type="submit">{innerProps.submitLabel}</Button>
</Group>
</Stack>
</form>
@@ -1,5 +1,5 @@
import type { WidgetKind } from "@homarr/definitions";
import type { SettingsContextProps } from "@homarr/settings";
import type { SettingsContextProps } from "@homarr/settings/creator";
import type { WidgetComponentProps } from "@homarr/widgets";
import { reduceWidgetOptionsWithDefaultValues } from "@homarr/widgets";
@@ -48,9 +48,7 @@ export const DynamicSectionEditModal = createModal<ModalProps>(({ actions, inner
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{t("common.action.saveChanges")}
</Button>
<Button type="submit">{t("common.action.saveChanges")}</Button>
</Group>
</Group>
</Stack>
@@ -11,11 +11,11 @@ interface LogoProps {
export const Logo = ({ size = 60, shouldUseNextImage = false, src, alt }: LogoProps) =>
shouldUseNextImage ? (
<Image src={src} alt={alt} width={size} height={size} />
<Image className="logo" src={src} alt={alt} width={size} height={size} />
) : (
// we only want to use next/image for logos that we are sure will be preloaded and are allowed
// eslint-disable-next-line @next/next/no-img-element
<img src={src} alt={alt} width={size} height={size} />
<img className="logo" src={src} alt={alt} width={size} height={size} />
);
const logoWithTitleSizes = {
+6
View File
@@ -6,6 +6,12 @@
--gridstack-cell-size: 0;
}
.ui-resizable-handle {
// Override default z-index of 100 to prevent it from being on top of the header.
// See https://github.com/homarr-labs/homarr/issues/1999
z-index: 1 !important;
}
.grid-stack-placeholder > .placeholder-content {
background-color: rgb(248, 249, 250) !important;
border-radius: 12px;
+2 -1
View File
@@ -3,7 +3,8 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
"~/*": ["./src/*"],
"@homarr/node-unifi": ["../../node_modules/@types/node-unifi"]
},
"plugins": [
{
+6 -5
View File
@@ -10,7 +10,7 @@
"main": "./src/main.ts",
"types": "./src/main.ts",
"scripts": {
"build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:@opentelemetry/api --outfile=tasks.cjs",
"build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:*.node --external:@opentelemetry/api --external:deasync --outfile=tasks.cjs",
"clean": "rm -rf .turbo node_modules",
"dev": "pnpm with-env tsx ./src/main.ts",
"format": "prettier --check . --ignore-path ../../.gitignore",
@@ -38,17 +38,18 @@
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"superjson": "2.2.2",
"undici": "7.8.0"
"undici": "7.10.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.14.0",
"@types/node": "^22.15.21",
"dotenv-cli": "^8.0.0",
"eslint": "^9.24.0",
"esbuild": "^0.25.4",
"eslint": "^9.27.0",
"prettier": "^3.5.3",
"tsx": "4.19.3",
"tsx": "4.19.4",
"typescript": "^5.8.3"
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
"~/*": ["src/*"],
"@homarr/node-unifi": ["../../node_modules/@types/node-unifi"]
},
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
+5 -4
View File
@@ -7,7 +7,7 @@
"main": "./src/main.ts",
"types": "./src/main.ts",
"scripts": {
"build": "esbuild src/main.ts --bundle --platform=node --outfile=wssServer.cjs --external:bcrypt --external:@opentelemetry/api --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text",
"build": "esbuild src/main.ts --bundle --platform=node --outfile=wssServer.cjs --external:bcrypt --external:@opentelemetry/api --external:deasync --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text",
"clean": "rm -rf .turbo node_modules",
"dev": "pnpm with-env tsx ./src/main.ts",
"format": "prettier --check . --ignore-path ../../.gitignore",
@@ -26,15 +26,16 @@
"@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^16.5.0",
"tsx": "4.19.3",
"ws": "^8.18.1"
"tsx": "4.19.4",
"ws": "^8.18.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1",
"eslint": "^9.24.0",
"esbuild": "^0.25.4",
"eslint": "^9.27.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
+2 -1
View File
@@ -3,7 +3,8 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
"~/*": ["src/*"],
"@homarr/node-unifi": ["../../node_modules/@types/node-unifi"]
},
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
+18 -18
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.16.0",
"version": "1.21.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -35,27 +35,27 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^11.0.1",
"@semantic-release/github": "^11.0.2",
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.3",
"@turbo/gen": "^2.5.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/ui": "^3.1.1",
"@turbo/gen": "^2.5.3",
"@vitejs/plugin-react": "^4.5.0",
"@vitest/coverage-v8": "^3.1.4",
"@vitest/ui": "^3.1.4",
"conventional-changelog-conventionalcommits": "^8.0.0",
"cross-env": "^7.0.3",
"jsdom": "^26.0.0",
"jsdom": "^26.1.0",
"prettier": "^3.5.3",
"semantic-release": "^24.2.3",
"testcontainers": "^10.24.2",
"turbo": "^2.5.0",
"semantic-release": "^24.2.4",
"testcontainers": "^10.28.0",
"turbo": "^2.5.3",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.1"
"vitest": "^3.1.4"
},
"packageManager": "pnpm@10.8.0",
"packageManager": "pnpm@10.11.0",
"engines": {
"node": ">=22.14.0"
"node": ">=22.16.0"
},
"pnpm": {
"onlyBuiltDependencies": [
@@ -70,16 +70,16 @@
"tree-sitter-json"
],
"overrides": {
"proxmox-api>undici": "7.8.0"
},
"patchedDependencies": {
"pretty-print-error": "patches/pretty-print-error.patch"
"proxmox-api>undici": "7.10.0"
},
"allowUnusedPatches": true,
"ignoredBuiltDependencies": [
"@scarf/scarf",
"core-js-pure",
"protobufjs"
]
],
"patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch"
}
}
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.27.0",
"typescript": "^5.8.3"
}
}
+10 -9
View File
@@ -40,24 +40,25 @@
"@homarr/request-handler": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.1.2",
"@trpc/client": "^11.1.0",
"@trpc/react-query": "^11.1.0",
"@trpc/server": "^11.1.0",
"@kubernetes/client-node": "^1.2.0",
"@tanstack/react-query": "^5.76.2",
"@trpc/client": "^11.1.2",
"@trpc/react-query": "^11.1.2",
"@trpc/server": "^11.1.2",
"@trpc/tanstack-react-query": "^11.1.2",
"lodash.clonedeep": "^4.5.0",
"next": "15.3.0",
"pretty-print-error": "^1.1.2",
"next": "15.3.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"superjson": "2.2.2",
"trpc-to-openapi": "^2.1.5",
"zod": "^3.24.2"
"trpc-to-openapi": "^2.2.0",
"zod": "^3.25.23"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.27.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
+2 -2
View File
@@ -20,7 +20,7 @@ export type IntegrationAction = "query" | "interact";
* @param action query for showing data or interact for mutating data
* @param kinds kinds of integrations that are supported
* @returns middleware that can be used with trpc
* @example publicProcedure.unstable_concat(createOneIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @example publicProcedure.concat(createOneIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @throws TRPCError NOT_FOUND if the integration was not found
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on the specified integration
*/
@@ -82,7 +82,7 @@ export const createOneIntegrationMiddleware = <TKind extends IntegrationKind>(
* @param action query for showing data or interact for mutating data
* @param kinds kinds of integrations that are supported
* @returns middleware that can be used with trpc
* @example publicProcedure.unstable_concat(createManyIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @example publicProcedure.concat(createManyIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
* @throws TRPCError NOT_FOUND if the integration was not found
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on at least one of the specified integrations
*/
@@ -1,7 +1,12 @@
import { X509Certificate } from "node:crypto";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { zfd } from "zod-form-data";
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
import { and, eq } from "@homarr/db";
import { trustedCertificateHostnames } from "@homarr/db/schema";
import { logger } from "@homarr/log";
import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates";
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
@@ -16,12 +21,106 @@ export const certificateRouter = createTRPCRouter({
)
.mutation(async ({ input }) => {
const content = await input.file.text();
// Validate the certificate
let x509Certificate: X509Certificate;
try {
x509Certificate = new X509Certificate(content);
logger.info("Adding trusted certificate", {
subject: x509Certificate.subject,
issuer: x509Certificate.issuer,
});
} catch {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid certificate",
});
}
await addCustomRootCertificateAsync(input.file.name, content);
logger.info("Added trusted certificate", {
subject: x509Certificate.subject,
issuer: x509Certificate.issuer,
});
}),
trustHostnameMismatch: permissionRequiredProcedure
.requiresPermission("admin")
.input(z.object({ hostname: z.string(), certificate: z.string() }))
.mutation(async ({ ctx, input }) => {
// Validate the certificate
let x509Certificate: X509Certificate;
try {
x509Certificate = new X509Certificate(input.certificate);
logger.info("Adding trusted hostname", {
subject: x509Certificate.subject,
issuer: x509Certificate.issuer,
thumbprint: x509Certificate.fingerprint256,
hostname: input.hostname,
});
} catch {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid certificate",
});
}
await ctx.db.insert(trustedCertificateHostnames).values({
hostname: input.hostname,
thumbprint: x509Certificate.fingerprint256,
certificate: input.certificate,
});
logger.info("Added trusted hostname", {
subject: x509Certificate.subject,
issuer: x509Certificate.issuer,
thumbprint: x509Certificate.fingerprint256,
hostname: input.hostname,
});
}),
removeTrustedHostname: permissionRequiredProcedure
.requiresPermission("admin")
.input(z.object({ hostname: z.string(), thumbprint: z.string() }))
.mutation(async ({ ctx, input }) => {
logger.info("Removing trusted hostname", {
hostname: input.hostname,
thumbprint: input.thumbprint,
});
const dbResult = await ctx.db
.delete(trustedCertificateHostnames)
.where(
and(
eq(trustedCertificateHostnames.hostname, input.hostname),
eq(trustedCertificateHostnames.thumbprint, input.thumbprint),
),
);
logger.info("Removed trusted hostname", {
hostname: input.hostname,
thumbprint: input.thumbprint,
count: dbResult.changes,
});
}),
removeCertificate: permissionRequiredProcedure
.requiresPermission("admin")
.input(z.object({ fileName: certificateValidFileNameSchema }))
.mutation(async ({ input }) => {
await removeCustomRootCertificateAsync(input.fileName);
.mutation(async ({ input, ctx }) => {
logger.info("Removing trusted certificate", {
fileName: input.fileName,
});
const certificate = await removeCustomRootCertificateAsync(input.fileName);
if (!certificate) return;
// Delete all trusted hostnames for this certificate
await ctx.db
.delete(trustedCertificateHostnames)
.where(eq(trustedCertificateHostnames.thumbprint, certificate.fingerprint256));
logger.info("Removed trusted certificate", {
fileName: input.fileName,
subject: certificate.subject,
issuer: certificate.issuer,
});
}),
});
+37 -89
View File
@@ -1,94 +1,52 @@
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { db, like, or } from "@homarr/db";
import { icons } from "@homarr/db/schema";
import type { Container, ContainerState, Docker, Port } from "@homarr/docker";
import { DockerSingleton } from "@homarr/docker";
import type { Container, ContainerInfo, ContainerState, Docker, Port } from "@homarr/docker";
import { logger } from "@homarr/log";
import { createCacheChannel } from "@homarr/redis";
import { dockerContainersRequestHandler } from "@homarr/request-handler/docker";
import { dockerMiddleware } from "../../middlewares/docker";
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
const dockerCache = createCacheChannel<{
containers: (ContainerInfo & { instance: string; iconUrl: string | null })[];
}>("docker-containers", 5 * 60 * 1000);
export const dockerRouter = createTRPCRouter({
getContainers: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.query(async () => {
const result = await dockerCache
.consumeAsync(async () => {
const dockerInstances = DockerSingleton.getInstances();
const containers = await Promise.all(
// Return all the containers of all the instances into only one item
dockerInstances.map(({ instance, host: key }) =>
instance.listContainers({ all: true }).then((containers) =>
containers.map((container) => ({
...container,
instance: key,
})),
),
),
).then((containers) => containers.flat());
const extractImage = (container: ContainerInfo) => container.Image.split("/").at(-1)?.split(":").at(0) ?? "";
const likeQueries = containers.map((container) => like(icons.name, `%${extractImage(container)}%`));
const dbIcons =
likeQueries.length >= 1
? await db.query.icons.findMany({
where: or(...likeQueries),
})
: [];
return {
containers: containers.map((container) => ({
...container,
iconUrl:
dbIcons.find((icon) => {
const extractedImage = extractImage(container);
if (!extractedImage) return false;
return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
})?.url ?? null,
})),
};
})
.catch((error) => {
logger.error(error);
return {
isError: true,
error: error as unknown,
};
});
if ("isError" in result) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "An error occurred while fetching the containers",
cause: result.error,
});
}
const innerHandler = dockerContainersRequestHandler.handler({});
const result = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
const { data, timestamp } = result;
return {
containers: sanitizeContainers(data.containers),
containers: data satisfies DockerContainer[],
timestamp,
};
}),
subscribeContainers: permissionRequiredProcedure
.requiresPermission("admin")
.concat(dockerMiddleware())
.subscription(() => {
return observable<DockerContainer[]>((emit) => {
const innerHandler = dockerContainersRequestHandler.handler({});
const unsubscribe = innerHandler.subscribe((data) => {
emit.next(data);
});
return unsubscribe;
});
}),
invalidate: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.mutation(async () => {
await dockerCache.invalidateAsync();
return;
const innerHandler = dockerContainersRequestHandler.handler({});
await innerHandler.invalidateAsync();
}),
startAll: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.input(z.object({ ids: z.array(z.string()) }))
.mutation(async ({ input }) => {
await Promise.allSettled(
@@ -98,11 +56,12 @@ export const dockerRouter = createTRPCRouter({
}),
);
await dockerCache.invalidateAsync();
const innerHandler = dockerContainersRequestHandler.handler({});
await innerHandler.invalidateAsync();
}),
stopAll: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.input(z.object({ ids: z.array(z.string()) }))
.mutation(async ({ input }) => {
await Promise.allSettled(
@@ -112,11 +71,12 @@ export const dockerRouter = createTRPCRouter({
}),
);
await dockerCache.invalidateAsync();
const innerHandler = dockerContainersRequestHandler.handler({});
await innerHandler.invalidateAsync();
}),
restartAll: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.input(z.object({ ids: z.array(z.string()) }))
.mutation(async ({ input }) => {
await Promise.allSettled(
@@ -126,11 +86,12 @@ export const dockerRouter = createTRPCRouter({
}),
);
await dockerCache.invalidateAsync();
const innerHandler = dockerContainersRequestHandler.handler({});
await innerHandler.invalidateAsync();
}),
removeAll: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(dockerMiddleware())
.concat(dockerMiddleware())
.input(z.object({ ids: z.array(z.string()) }))
.mutation(async ({ input }) => {
await Promise.allSettled(
@@ -140,7 +101,8 @@ export const dockerRouter = createTRPCRouter({
}),
);
await dockerCache.invalidateAsync();
const innerHandler = dockerContainersRequestHandler.handler({});
await innerHandler.invalidateAsync();
}),
});
@@ -180,20 +142,6 @@ interface DockerContainer {
image: string;
ports: Port[];
iconUrl: string | null;
}
function sanitizeContainers(
containers: (ContainerInfo & { instance: string; iconUrl: string | null })[],
): DockerContainer[] {
return containers.map((container) => {
return {
name: container.Names[0]?.split("/")[1] ?? "Unknown",
id: container.Id,
instance: container.instance,
state: container.State as ContainerState,
image: container.Image,
ports: container.Ports,
iconUrl: container.iconUrl,
};
});
cpuUsage: number;
memoryUsage: number;
}
@@ -24,6 +24,7 @@ import {
integrationSecretKindObject,
} from "@homarr/definitions";
import { createIntegrationAsync } from "@homarr/integrations";
import { logger } from "@homarr/log";
import { byIdSchema } from "@homarr/validation/common";
import {
integrationCreateSchema,
@@ -34,7 +35,8 @@ import {
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
import { throwIfActionForbiddenAsync } from "./integration-access";
import { testConnectionAsync } from "./integration-test-connection";
import { MissingSecretError, testConnectionAsync } from "./integration-test-connection";
import { mapTestConnectionError } from "./map-test-connection-error";
export const integrationRouter = createTRPCRouter({
all: publicProcedure.query(async ({ ctx }) => {
@@ -185,14 +187,34 @@ export const integrationRouter = createTRPCRouter({
.requiresPermission("integration-create")
.input(integrationCreateSchema)
.mutation(async ({ ctx, input }) => {
await testConnectionAsync({
logger.info("Creating integration", {
name: input.name,
kind: input.kind,
url: input.url,
});
const result = await testConnectionAsync({
id: "new",
name: input.name,
url: input.url,
kind: input.kind,
secrets: input.secrets,
}).catch((error) => {
if (!(error instanceof MissingSecretError)) throw error;
throw new TRPCError({
code: "BAD_REQUEST",
message: error.message,
});
});
if (!result.success) {
logger.error(result.error);
return {
error: mapTestConnectionError(result.error),
};
}
const integrationId = createId();
await ctx.db.insert(integrations).values({
id: integrationId,
@@ -211,6 +233,13 @@ export const integrationRouter = createTRPCRouter({
);
}
logger.info("Created integration", {
id: integrationId,
name: input.name,
kind: input.kind,
url: input.url,
});
if (
input.attemptSearchEngineCreation &&
integrationDefs[input.kind].category.flatMap((category) => category).includes("search")
@@ -229,6 +258,10 @@ export const integrationRouter = createTRPCRouter({
update: protectedProcedure.input(integrationUpdateSchema).mutation(async ({ ctx, input }) => {
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
logger.info("Updating integration", {
id: input.id,
});
const integration = await ctx.db.query.integrations.findFirst({
where: eq(integrations.id, input.id),
with: {
@@ -243,7 +276,7 @@ export const integrationRouter = createTRPCRouter({
});
}
await testConnectionAsync(
const testResult = await testConnectionAsync(
{
id: input.id,
name: input.name,
@@ -252,7 +285,21 @@ export const integrationRouter = createTRPCRouter({
secrets: input.secrets,
},
integration.secrets,
);
).catch((error) => {
if (!(error instanceof MissingSecretError)) throw error;
throw new TRPCError({
code: "BAD_REQUEST",
message: error.message,
});
});
if (!testResult.success) {
logger.error(testResult.error);
return {
error: mapTestConnectionError(testResult.error),
};
}
await ctx.db
.update(integrations)
@@ -286,6 +333,13 @@ export const integrationRouter = createTRPCRouter({
}
}
}
logger.info("Updated integration", {
id: input.id,
name: input.name,
kind: integration.kind,
url: input.url,
});
}),
delete: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
@@ -467,7 +521,7 @@ export const integrationRouter = createTRPCRouter({
});
}),
searchInIntegration: protectedProcedure
.unstable_concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("search")))
.concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("search")))
.input(z.object({ integrationId: z.string(), query: z.string() }))
.query(async ({ ctx, input }) => {
const integrationInstance = await createIntegrationAsync(ctx.integration);
@@ -2,7 +2,7 @@ import { decryptSecret } from "@homarr/common/server";
import type { Integration } from "@homarr/db/schema";
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
import { getAllSecretKindOptions } from "@homarr/definitions";
import { createIntegrationAsync, IntegrationTestConnectionError } from "@homarr/integrations";
import { createIntegrationAsync } from "@homarr/integrations";
import { logger } from "@homarr/log";
type FormIntegration = Integration & {
@@ -19,6 +19,12 @@ export const testConnectionAsync = async (
value: `${string}.${string}`;
}[] = [],
) => {
logger.info("Testing connection", {
integrationName: integration.name,
integrationKind: integration.kind,
integrationUrl: integration.url,
});
const formSecrets = integration.secrets
.filter((secret) => secret.value !== null)
.map((secret) => ({
@@ -72,7 +78,15 @@ export const testConnectionAsync = async (
decryptedSecrets,
});
await integrationInstance.testConnectionAsync();
const result = await integrationInstance.testConnectionAsync();
if (result.success) {
logger.info("Tested connection successfully", {
integrationName: integration.name,
integrationKind: integration.kind,
integrationUrl: integration.url,
});
}
return result;
};
interface SourcedIntegrationSecret {
@@ -87,7 +101,7 @@ const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedInteg
);
if (matchingSecretKindOptions.length === 0) {
throw new IntegrationTestConnectionError("secretNotDefined");
throw new MissingSecretError();
}
if (matchingSecretKindOptions.length === 1) {
@@ -101,6 +115,18 @@ const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedInteg
);
if (onlyFormSecretsKindOptions.length >= 1) {
// If the first option is no secret it would always be selected even if we want to have a secret
if (
onlyFormSecretsKindOptions.length >= 2 &&
onlyFormSecretsKindOptions.some((secretKinds) => secretKinds.length === 0)
) {
return (
// Will never be undefined because of the check above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
onlyFormSecretsKindOptions.find((secretKinds) => secretKinds.length >= 1) ?? onlyFormSecretsKindOptions[0]!
);
}
// Will never be undefined because of the check above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return onlyFormSecretsKindOptions[0]!;
@@ -110,3 +136,9 @@ const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedInteg
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return matchingSecretKindOptions[0]!;
};
export class MissingSecretError extends Error {
constructor() {
super("No secret defined for this integration");
}
}
@@ -0,0 +1,141 @@
import type { X509Certificate } from "node:crypto";
import type { RequestErrorCode } from "@homarr/common/server";
import type {
AnyTestConnectionError,
TestConnectionErrorDataOfType,
TestConnectionErrorType,
} from "@homarr/integrations/test-connection";
export interface MappedError {
name: string;
message: string;
metadata: { key: string; value: string | number | boolean }[];
cause?: MappedError;
}
const ignoredErrorProperties = ["name", "message", "cause", "stack"];
const mapError = (error: Error): MappedError => {
const metadata = Object.entries(error)
.filter(([key]) => !ignoredErrorProperties.includes(key))
.map(([key, value]) => {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return { key, value };
}
return null;
})
.filter((value) => value !== null);
return {
name: error.name,
message: error.message,
metadata,
cause: error.cause && error.cause instanceof Error ? mapError(error.cause) : undefined,
};
};
export interface MappedCertificate {
isSelfSigned: boolean;
issuer: string;
subject: string;
serialNumber: string;
validFrom: Date;
validTo: Date;
fingerprint: string;
pem: string;
}
const mapCertificate = (certificate: X509Certificate, code: RequestErrorCode): MappedCertificate => ({
isSelfSigned: certificate.ca || code === "DEPTH_ZERO_SELF_SIGNED_CERT",
issuer: certificate.issuer,
subject: certificate.subject,
serialNumber: certificate.serialNumber,
validFrom: certificate.validFromDate,
validTo: certificate.validToDate,
fingerprint: certificate.fingerprint256,
pem: certificate.toString(),
});
type MappedData<TType extends TestConnectionErrorType> = TType extends "unknown" | "parse"
? undefined
: TType extends "certificate"
? {
type: TestConnectionErrorDataOfType<TType>["requestError"]["type"];
reason: TestConnectionErrorDataOfType<TType>["requestError"]["reason"];
certificate: MappedCertificate;
}
: TType extends "request"
? {
type: TestConnectionErrorDataOfType<TType>["requestError"]["type"];
reason: TestConnectionErrorDataOfType<TType>["requestError"]["reason"];
}
: TType extends "authorization"
? {
statusCode: TestConnectionErrorDataOfType<TType>["statusCode"];
reason: TestConnectionErrorDataOfType<TType>["reason"];
}
: TType extends "statusCode"
? {
statusCode: TestConnectionErrorDataOfType<TType>["statusCode"];
reason: TestConnectionErrorDataOfType<TType>["reason"];
url: TestConnectionErrorDataOfType<TType>["url"];
}
: never;
type AnyMappedData = {
[TType in TestConnectionErrorType]: MappedData<TType>;
}[TestConnectionErrorType];
const mapData = (error: AnyTestConnectionError): AnyMappedData => {
if (error.type === "unknown") return undefined;
if (error.type === "parse") return undefined;
if (error.type === "certificate") {
return {
type: error.data.requestError.type,
reason: error.data.requestError.reason,
certificate: mapCertificate(error.data.certificate, error.data.requestError.code),
};
}
if (error.type === "request") {
return {
type: error.data.requestError.type,
reason: error.data.requestError.reason,
};
}
if (error.type === "authorization") {
return {
statusCode: error.data.statusCode,
reason: error.data.reason,
};
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (error.type === "statusCode") {
return {
statusCode: error.data.statusCode,
reason: error.data.reason,
url: error.data.url,
};
}
throw new Error(`Unsupported error type: ${(error as AnyTestConnectionError).type}`);
};
interface MappedTestConnectionError<TType extends TestConnectionErrorType> {
type: TType;
name: string;
message: string;
data: MappedData<TType>;
cause?: MappedError;
}
export type AnyMappedTestConnectionError = {
[TType in TestConnectionErrorType]: MappedTestConnectionError<TType>;
}[TestConnectionErrorType];
export const mapTestConnectionError = (error: AnyTestConnectionError) => {
return {
type: error.type,
name: error.name,
message: error.message,
data: mapData(error),
cause: error.cause ? mapError(error.cause) : undefined,
} as AnyMappedTestConnectionError;
};
@@ -13,7 +13,7 @@ import { MemoryResourceParser } from "../resource-parser/memory-resource-parser"
export const clusterRouter = createTRPCRouter({
getCluster: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesCluster> => {
const { coreApi, metricsApi, versionApi, kubeConfig } = KubernetesClient.getInstance();
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const configMapsRouter = createTRPCRouter({
getConfigMaps: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesBaseResource[]> => {
const { coreApi } = KubernetesClient.getInstance();
@@ -11,7 +11,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const ingressesRouter = createTRPCRouter({
getIngresses: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesIngress[]> => {
const { networkingApi } = KubernetesClient.getInstance();
try {
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const namespacesRouter = createTRPCRouter({
getNamespaces: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesNamespace[]> => {
const { coreApi } = KubernetesClient.getInstance();
@@ -12,7 +12,7 @@ import { MemoryResourceParser } from "../resource-parser/memory-resource-parser"
export const nodesRouter = createTRPCRouter({
getNodes: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesNode[]> => {
const { coreApi, metricsApi } = KubernetesClient.getInstance();
@@ -12,7 +12,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const podsRouter = createTRPCRouter({
getPods: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesPod[]> => {
const { coreApi, kubeConfig } = KubernetesClient.getInstance();
try {
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const secretsRouter = createTRPCRouter({
getSecrets: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesSecret[]> => {
const { coreApi } = KubernetesClient.getInstance();
try {
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const servicesRouter = createTRPCRouter({
getServices: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesService[]> => {
const { coreApi } = KubernetesClient.getInstance();
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
export const volumesRouter = createTRPCRouter({
getVolumes: permissionRequiredProcedure
.requiresPermission("admin")
.unstable_concat(kubernetesMiddleware())
.concat(kubernetesMiddleware())
.query(async (): Promise<KubernetesVolume[]> => {
const { coreApi } = KubernetesClient.getInstance();
@@ -133,14 +133,14 @@ export const searchEngineRouter = createTRPCRouter({
});
}),
getMediaRequestOptions: protectedProcedure
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
.concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
.input(mediaRequestOptionsSchema)
.query(async ({ ctx, input }) => {
const integration = await createIntegrationAsync(ctx.integration);
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
}),
requestMedia: protectedProcedure
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
.concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
.input(mediaRequestRequestSchema)
.mutation(async ({ ctx, input }) => {
const integration = await createIntegrationAsync(ctx.integration);
@@ -4,14 +4,26 @@ import { describe, expect, test, vi } from "vitest";
import type { Session } from "@homarr/auth";
import { objectKeys } from "@homarr/common";
import type { Database } from "@homarr/db";
import { getPermissionsWithChildren } from "@homarr/definitions";
import type { GroupPermissionKey } from "@homarr/definitions";
import { getPermissionsWithChildren } from "@homarr/definitions";
import type { RouterInputs } from "../../..";
import { dockerRouter } from "../../docker/docker-router";
// Mock the auth module to return an empty session
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
vi.mock("@homarr/request-handler/docker", () => ({
dockerContainersRequestHandler: {
handler: () => ({
getCachedOrUpdatedDataAsync: async () => {
return await Promise.resolve({ containers: [] });
},
invalidateAsync: async () => {
return await Promise.resolve();
},
}),
},
}));
vi.mock("@homarr/redis", () => ({
createCacheChannel: () => ({
// eslint-disable-next-line @typescript-eslint/require-await
@@ -22,6 +34,7 @@ vi.mock("@homarr/redis", () => ({
// eslint-disable-next-line @typescript-eslint/no-empty-function
invalidateAsync: async () => {},
}),
createWidgetOptionsChannel: () => ({}),
}));
vi.mock("@homarr/docker/env", () => ({
@@ -46,6 +59,7 @@ const validInputs: {
[key in (typeof procedureKeys)[number]]: RouterInputs["docker"][key];
} = {
getContainers: undefined,
subscribeContainers: undefined,
startAll: { ids: ["1"] },
stopAll: { ids: ["1"] },
restartAll: { ids: ["1"] },
@@ -25,7 +25,7 @@ const defaultSessionWithPermissions = (permissions: GroupPermissionKey[] = []) =
// Mock the auth module to return an empty session
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
vi.mock("../../integration/integration-test-connection", () => ({
testConnectionAsync: async () => await Promise.resolve(undefined),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
}));
describe("all should return all integrations", () => {
@@ -22,7 +22,7 @@ describe("testConnectionAsync should run test connection of integration", () =>
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve(),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([["apiKey"]]);
@@ -64,7 +64,7 @@ describe("testConnectionAsync should run test connection of integration", () =>
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve(),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([["apiKey"]]);
@@ -113,7 +113,7 @@ describe("testConnectionAsync should run test connection of integration", () =>
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve(),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([["apiKey"]]);
@@ -162,7 +162,7 @@ describe("testConnectionAsync should run test connection of integration", () =>
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve(),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([["username", "password"], ["apiKey"]]);
@@ -215,7 +215,7 @@ describe("testConnectionAsync should run test connection of integration", () =>
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve(),
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([["username", "password"], ["apiKey"]]);
+1 -1
View File
@@ -17,7 +17,7 @@ export const calendarRouter = createTRPCRouter({
showUnmonitored: z.boolean(),
}),
)
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.query(async ({ ctx, input }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
+4 -4
View File
@@ -14,7 +14,7 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trp
export const dnsHoleRouter = createTRPCRouter({
summary: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
.query(async ({ ctx }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -36,7 +36,7 @@ export const dnsHoleRouter = createTRPCRouter({
}),
subscribeToSummary: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
.subscription(({ ctx }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"dnsHole"> }>;
@@ -63,7 +63,7 @@ export const dnsHoleRouter = createTRPCRouter({
}),
enable: protectedProcedure
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
.mutation(async ({ ctx: { integration } }) => {
const client = await createIntegrationAsync(integration);
await client.enableAsync();
@@ -81,7 +81,7 @@ export const dnsHoleRouter = createTRPCRouter({
duration: z.number().optional(),
}),
)
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
.mutation(async ({ ctx: { integration }, input }) => {
const client = await createIntegrationAsync(integration);
await client.disableAsync(input.duration);
+21 -25
View File
@@ -18,7 +18,7 @@ const createDownloadClientIntegrationMiddleware = (action: IntegrationAction) =>
export const downloadsRouter = createTRPCRouter({
getJobsAndStatuses: publicProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("query"))
.concat(createDownloadClientIntegrationMiddleware("query"))
.query(async ({ ctx }) => {
return await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -39,7 +39,7 @@ export const downloadsRouter = createTRPCRouter({
);
}),
subscribeToJobsAndStatuses: publicProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("query"))
.concat(createDownloadClientIntegrationMiddleware("query"))
.subscription(({ ctx }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"downloadClient"> }>;
@@ -64,18 +64,16 @@ export const downloadsRouter = createTRPCRouter({
};
});
}),
pause: protectedProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
.mutation(async ({ ctx }) => {
await Promise.all(
ctx.integrations.map(async (integration) => {
const integrationInstance = await createIntegrationAsync(integration);
await integrationInstance.pauseQueueAsync();
}),
);
}),
pause: protectedProcedure.concat(createDownloadClientIntegrationMiddleware("interact")).mutation(async ({ ctx }) => {
await Promise.all(
ctx.integrations.map(async (integration) => {
const integrationInstance = await createIntegrationAsync(integration);
await integrationInstance.pauseQueueAsync();
}),
);
}),
pauseItem: protectedProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
.concat(createDownloadClientIntegrationMiddleware("interact"))
.input(z.object({ item: downloadClientItemSchema }))
.mutation(async ({ ctx, input }) => {
await Promise.all(
@@ -85,18 +83,16 @@ export const downloadsRouter = createTRPCRouter({
}),
);
}),
resume: protectedProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
.mutation(async ({ ctx }) => {
await Promise.all(
ctx.integrations.map(async (integration) => {
const integrationInstance = await createIntegrationAsync(integration);
await integrationInstance.resumeQueueAsync();
}),
);
}),
resume: protectedProcedure.concat(createDownloadClientIntegrationMiddleware("interact")).mutation(async ({ ctx }) => {
await Promise.all(
ctx.integrations.map(async (integration) => {
const integrationInstance = await createIntegrationAsync(integration);
await integrationInstance.resumeQueueAsync();
}),
);
}),
resumeItem: protectedProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
.concat(createDownloadClientIntegrationMiddleware("interact"))
.input(z.object({ item: downloadClientItemSchema }))
.mutation(async ({ ctx, input }) => {
await Promise.all(
@@ -107,7 +103,7 @@ export const downloadsRouter = createTRPCRouter({
);
}),
deleteItem: protectedProcedure
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
.concat(createDownloadClientIntegrationMiddleware("interact"))
.input(z.object({ item: downloadClientItemSchema, fromDisk: z.boolean() }))
.mutation(async ({ ctx, input }) => {
await Promise.all(
@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
export const healthMonitoringRouter = createTRPCRouter({
getSystemHealthStatus: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
.query(async ({ ctx }) => {
return await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -26,7 +26,7 @@ export const healthMonitoringRouter = createTRPCRouter({
);
}),
subscribeSystemHealthStatus: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
.subscription(({ ctx }) => {
return observable<{ integrationId: string; healthInfo: HealthMonitoring; timestamp: Date }>((emit) => {
const unsubscribes: (() => void)[] = [];
@@ -49,14 +49,14 @@ export const healthMonitoringRouter = createTRPCRouter({
});
}),
getClusterHealthStatus: publicProcedure
.unstable_concat(createOneIntegrationMiddleware("query", "proxmox"))
.concat(createOneIntegrationMiddleware("query", "proxmox"))
.query(async ({ ctx }) => {
const innerHandler = clusterInfoRequestHandler.handler(ctx.integration, {});
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
}),
subscribeClusterHealthStatus: publicProcedure
.unstable_concat(createOneIntegrationMiddleware("query", "proxmox"))
.concat(createOneIntegrationMiddleware("query", "proxmox"))
.subscription(({ ctx }) => {
return observable<ProxmoxClusterInfo>((emit) => {
const unsubscribes: (() => void)[] = [];
+2
View File
@@ -12,6 +12,7 @@ import { minecraftRouter } from "./minecraft";
import { networkControllerRouter } from "./network-controller";
import { notebookRouter } from "./notebook";
import { optionsRouter } from "./options";
import { releasesRouter } from "./releases";
import { rssFeedRouter } from "./rssFeed";
import { smartHomeRouter } from "./smart-home";
import { stockPriceRouter } from "./stocks";
@@ -34,5 +35,6 @@ export const widgetRouter = createTRPCRouter({
mediaTranscoding: mediaTranscodingRouter,
minecraft: minecraftRouter,
options: optionsRouter,
releases: releasesRouter,
networkController: networkControllerRouter,
});
@@ -16,7 +16,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
export const indexerManagerRouter = createTRPCRouter({
getIndexersStatus: publicProcedure
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
.concat(createIndexerManagerIntegrationMiddleware("query"))
.query(async ({ ctx }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -33,7 +33,7 @@ export const indexerManagerRouter = createTRPCRouter({
}),
subscribeIndexersStatus: publicProcedure
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
.concat(createIndexerManagerIntegrationMiddleware("query"))
.subscription(({ ctx }) => {
return observable<{ integrationId: string; indexers: Indexer[] }>((emit) => {
const unsubscribes: (() => void)[] = [];
@@ -55,7 +55,7 @@ export const indexerManagerRouter = createTRPCRouter({
});
}),
testAllIndexers: protectedProcedure
.unstable_concat(createIndexerManagerIntegrationMiddleware("interact"))
.concat(createIndexerManagerIntegrationMiddleware("interact"))
.mutation(async ({ ctx }) => {
await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -12,7 +12,7 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trp
export const mediaRequestsRouter = createTRPCRouter({
getLatestRequests: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.query(async ({ ctx }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -39,7 +39,7 @@ export const mediaRequestsRouter = createTRPCRouter({
});
}),
subscribeToLatestRequests: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.subscription(({ ctx }) => {
return observable<{
integrationId: string;
@@ -65,7 +65,7 @@ export const mediaRequestsRouter = createTRPCRouter({
});
}),
getStats: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
.query(async ({ ctx }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -91,7 +91,7 @@ export const mediaRequestsRouter = createTRPCRouter({
};
}),
answerRequest: protectedProcedure
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("mediaRequest")))
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("mediaRequest")))
.input(z.object({ requestId: z.number(), answer: z.enum(["approve", "decline"]) }))
.mutation(async ({ ctx: { integration }, input }) => {
const integrationInstance = await createIntegrationAsync(integration);
@@ -1,4 +1,5 @@
import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import type { StreamSession } from "@homarr/integrations";
@@ -13,11 +14,14 @@ const createMediaServerIntegrationMiddleware = (action: IntegrationAction) =>
export const mediaServerRouter = createTRPCRouter({
getCurrentStreams: publicProcedure
.unstable_concat(createMediaServerIntegrationMiddleware("query"))
.query(async ({ ctx }) => {
.concat(createMediaServerIntegrationMiddleware("query"))
.input(z.object({ showOnlyPlaying: z.boolean() }))
.query(async ({ ctx, input }) => {
return await Promise.all(
ctx.integrations.map(async (integration) => {
const innerHandler = mediaServerRequestHandler.handler(integration, {});
const innerHandler = mediaServerRequestHandler.handler(integration, {
showOnlyPlaying: input.showOnlyPlaying,
});
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return {
integrationId: integration.id,
@@ -28,12 +32,15 @@ export const mediaServerRouter = createTRPCRouter({
);
}),
subscribeToCurrentStreams: publicProcedure
.unstable_concat(createMediaServerIntegrationMiddleware("query"))
.subscription(({ ctx }) => {
.concat(createMediaServerIntegrationMiddleware("query"))
.input(z.object({ showOnlyPlaying: z.boolean() }))
.subscription(({ ctx, input }) => {
return observable<{ integrationId: string; data: StreamSession[] }>((emit) => {
const unsubscribes: (() => void)[] = [];
for (const integration of ctx.integrations) {
const innerHandler = mediaServerRequestHandler.handler(integration, {});
const innerHandler = mediaServerRequestHandler.handler(integration, {
showOnlyPlaying: input.showOnlyPlaying,
});
const unsubscribe = innerHandler.subscribe((sessions) => {
emit.next({
@@ -11,7 +11,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
export const mediaTranscodingRouter = createTRPCRouter({
getDataAsync: publicProcedure
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
.concat(createIndexerManagerIntegrationMiddleware("query"))
.input(paginatedSchema.pick({ page: true, pageSize: true }))
.query(async ({ ctx, input }) => {
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
@@ -12,7 +12,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
export const networkControllerRouter = createTRPCRouter({
summary: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
.query(async ({ ctx }) => {
const results = await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -34,7 +34,7 @@ export const networkControllerRouter = createTRPCRouter({
}),
subscribeToSummary: publicProcedure
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
.subscription(({ ctx }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"networkController"> }>;
@@ -0,0 +1,53 @@
import { escapeForRegEx } from "@tiptap/react";
import { z } from "zod";
import { releasesRequestHandler } from "@homarr/request-handler/releases";
import { createTRPCRouter, publicProcedure } from "../../trpc";
const formatVersionFilterRegex = (versionFilter: z.infer<typeof releaseVersionFilterSchema> | undefined) => {
if (!versionFilter) return undefined;
const escapedPrefix = versionFilter.prefix ? escapeForRegEx(versionFilter.prefix) : "";
const precision = "[0-9]+\\.".repeat(versionFilter.precision).slice(0, -2);
const escapedSuffix = versionFilter.suffix ? escapeForRegEx(versionFilter.suffix) : "";
return `^${escapedPrefix}${precision}${escapedSuffix}$`;
};
const releaseVersionFilterSchema = z.object({
prefix: z.string().optional(),
precision: z.number(),
suffix: z.string().optional(),
});
export const releasesRouter = createTRPCRouter({
getLatest: publicProcedure
.input(
z.object({
repositories: z.array(
z.object({
providerKey: z.string(),
identifier: z.string(),
versionFilter: releaseVersionFilterSchema.optional(),
}),
),
}),
)
.query(async ({ input }) => {
const result = await Promise.all(
input.repositories.map(async (repository) => {
const innerHandler = releasesRequestHandler.handler({
providerKey: repository.providerKey,
identifier: repository.identifier,
versionRegex: formatVersionFilterRegex(repository.versionFilter),
});
return await innerHandler.getCachedOrUpdatedDataAsync({
forceUpdate: false,
});
}),
);
return result;
}),
});
@@ -15,14 +15,14 @@ const createSmartHomeIntegrationMiddleware = (action: IntegrationAction) =>
export const smartHomeRouter = createTRPCRouter({
entityState: publicProcedure
.input(z.object({ entityId: z.string() }))
.unstable_concat(createSmartHomeIntegrationMiddleware("query"))
.concat(createSmartHomeIntegrationMiddleware("query"))
.query(async ({ ctx: { integration }, input }) => {
const innerHandler = smartHomeEntityStateRequestHandler.handler(integration, { entityId: input.entityId });
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
}),
subscribeEntityState: publicProcedure
.unstable_concat(createSmartHomeIntegrationMiddleware("query"))
.concat(createSmartHomeIntegrationMiddleware("query"))
.input(z.object({ entityId: z.string() }))
.subscription(({ input, ctx }) => {
return observable<{
@@ -42,7 +42,7 @@ export const smartHomeRouter = createTRPCRouter({
});
}),
switchEntity: protectedProcedure
.unstable_concat(createSmartHomeIntegrationMiddleware("interact"))
.concat(createSmartHomeIntegrationMiddleware("interact"))
.input(z.object({ entityId: z.string() }))
.mutation(async ({ ctx: { integration }, input }) => {
const client = await createIntegrationAsync(integration);
@@ -54,7 +54,7 @@ export const smartHomeRouter = createTRPCRouter({
return success;
}),
executeAutomation: protectedProcedure
.unstable_concat(createSmartHomeIntegrationMiddleware("interact"))
.concat(createSmartHomeIntegrationMiddleware("interact"))
.input(z.object({ automationId: z.string() }))
.mutation(async ({ ctx: { integration }, input }) => {
const client = await createIntegrationAsync(integration);
+13 -1
View File
@@ -1,9 +1,12 @@
import { cache } from "react";
import { headers } from "next/headers";
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
import { createCaller, createTRPCContext } from "@homarr/api";
import { appRouter, createCaller, createTRPCContext } from "@homarr/api";
import { auth } from "@homarr/auth/next";
import { makeQueryClient } from "./shared";
/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a tRPC call from a React Server Component.
@@ -19,3 +22,12 @@ const createContext = cache(async () => {
});
export const api = createCaller(createContext);
// IMPORTANT: Create a stable getter for the query client that
// will return the same client during the same request.
export const getQueryClient = cache(makeQueryClient);
export const trpc = createTRPCOptionsProxy({
ctx: createContext,
router: appRouter,
queryClient: getQueryClient,
});
+15
View File
@@ -1,3 +1,5 @@
import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query";
/**
* Creates a headers callback for a given source
* It will set the x-trpc-source header and cookies if needed
@@ -51,3 +53,16 @@ export const trpcPath = "/api/trpc";
export function getTrpcUrl() {
return `${getBaseUrl()}${trpcPath}`;
}
export const makeQueryClient = () => {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 1000,
},
dehydrate: {
shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending",
},
},
});
};
+1
View File
@@ -40,6 +40,7 @@ export const env = createEnv({
AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE: z.string().optional(),
AUTH_OIDC_FORCE_USERINFO: createBooleanSchema(false),
AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING: createBooleanSchema(false),
}
: {}),
...(authProviders.includes("ldap")
+3
View File
@@ -13,6 +13,7 @@ import { extractProfileName } from "./providers/oidc/oidc-provider";
export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {
return async ({ user, profile }) => {
logger.debug(`SignIn EventHandler for user: ${JSON.stringify(user)} . profile: ${JSON.stringify(profile)}`);
if (!user.id) throw new Error("User ID is missing");
const dbUser = await db.query.users.findFirst({
@@ -28,11 +29,13 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
const groupsKey = env.AUTH_OIDC_GROUPS_ATTRIBUTE;
// Groups from oidc provider are provided from the profile, it's not typed.
if (profile && groupsKey in profile && Array.isArray(profile[groupsKey])) {
logger.debug(`Using profile groups (${groupsKey}): ${JSON.stringify(profile[groupsKey])}`);
await synchronizeGroupsWithExternalForUserAsync(db, user.id, profile[groupsKey] as string[]);
}
// In ldap-authroization we return the groups from ldap, it's not typed.
if ("groups" in user && Array.isArray(user.groups)) {
logger.debug(`Using profile groups: ${JSON.stringify(user.groups)}`);
await synchronizeGroupsWithExternalForUserAsync(db, user.id, user.groups as string[]);
}
await addUserToEveryoneGroupIfNotMemberAsync(db, user.id);
+8 -9
View File
@@ -23,8 +23,8 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@auth/core": "^0.38.0",
"@auth/drizzle-adapter": "^1.8.0",
"@auth/core": "^0.39.1",
"@auth/drizzle-adapter": "^1.9.1",
"@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
@@ -32,15 +32,14 @@
"@homarr/env": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^5.1.1",
"bcrypt": "^6.0.0",
"cookies": "^0.9.1",
"ldapts": "7.4.0",
"next": "15.3.0",
"next-auth": "5.0.0-beta.25",
"pretty-print-error": "^1.1.2",
"ldapts": "8.0.0",
"next": "15.3.2",
"next-auth": "5.0.0-beta.28",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "^3.24.2"
"zod": "^3.25.23"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -48,7 +47,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "5.0.2",
"@types/cookies": "0.9.0",
"eslint": "^9.24.0",
"eslint": "^9.27.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
@@ -15,6 +15,7 @@ export const OidcProvider = (headers: ReadonlyHeaders | null): OIDCConfig<Profil
clientId: env.AUTH_OIDC_CLIENT_ID,
clientSecret: env.AUTH_OIDC_CLIENT_SECRET,
issuer: env.AUTH_OIDC_ISSUER,
allowDangerousEmailAccountLinking: env.AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING,
authorization: {
params: {
scope: env.AUTH_OIDC_SCOPE_OVERWRITE,
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.24.0",
"eslint": "^9.27.0",
"typescript": "^5.8.3"
}
}
Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff. Show more