Compare commits

...
Author SHA1 Message Date
ajnart 3457bb3288 style: auto-fix lint and format 2026-06-08 21:56:10 +00:00
Thomas Camlong e6403bbe52 feat: wire survey into header user menu
Add SurveyIndicator (purple dot) around the avatar and render
SurveyMenuItem + SurveyModal in the user avatar dropdown menu.
2026-06-08 23:55:01 +02:00
Thomas Camlong 3a9f10e6ba feat: add in-app survey form with PostHog integration
Time-gated survey (expires 2026-07-01) using a Mantine modal with
horizontal stepper. Sends responses to hog.homarr.dev PostHog endpoint.
Gated by homarr-answered-survey cookie to show only once per user.
2026-06-08 23:54:54 +02:00
Thomasandhomarr-releases[bot] 69412f2d10 chore(release): 1.64.0 [skip ci] (#5902)
Co-authored-by: homarr-releases[bot] <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-08 22:25:40 +02:00
Afonso Jorge Ramos f6d5f97933 fix(integrations): support truenas json-rpc api, certificate trust, and api key auth (#5861)
* chore(integrations): add ws dependency for the truenas client

* feat(definitions): allow api key authentication for truenas

* fix(integrations): connect to truenas over trusted-certificate websocket with protocol autodetection

* test(integrations): cover truenas protocol detection and authentication

* refactor(integrations): avoid shorthand index variable in truenas mapping

* feat(integrations): add a websocket url helper to the integration base

* refactor(integrations): extract truenas connection handling into a client
2026-06-08 22:20:42 +02:00
homarr-renovate[bot] a447a9f615 chore(deps): update dependency axios@>=1.0.0 <1.8.2 to >=1.17.0 (#5897)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-08 14:15:50 +00:00
homarr-renovate[bot] 87d3f6a0a1 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.4 (#5896)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-08 09:30:40 +00:00
homarr-renovate[bot] f2705eeb57 chore(deps): update dependency oxfmt to ^0.53.0 (#5883)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 23:12:23 +00:00
homarr-renovate[bot] ac37d2c73f chore(deps): update react monorepo (#5886)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 21:13:36 +00:00
homarr-renovate[bot] e00ea624cd chore(deps): update nextjs monorepo to v16.2.7 (#5885)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 19:32:30 +00:00
homarr-renovate[bot] 20b62c42e3 chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.61.0 (#5878)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 13:54:21 +00:00
homarr-renovate[bot] 59971991c7 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.16 (#5880)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 11:36:28 +00:00
homarr-renovate[bot] a629681ae5 chore(deps): update vitest monorepo to ^4.1.8 (#5879)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 09:34:48 +00:00
homarr-renovate[bot] 572d5fb5ec chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.15 (#5877)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-06 07:01:54 +00:00
Thomasandhomarr-releases[bot] 91a1858743 chore: merge main into dev
Co-authored-by: homarr-releases[bot] <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-06 07:47:59 +02:00
homarr-renovate[bot] f00d3627c9 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.3 (#5873)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-05 18:04:10 +00:00
homarr-releases[bot] 7760e01ccb chore: update bug report template (#5821)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-05 17:47:57 +00:00
Releases Homarr 8a1ead3674 chore(release): 1.64.0 [skip ci] 2026-06-05 17:35:36 +00:00
homarr-releases[bot] 9c05f44916 chore(release): automatic release v1.64.0 2026-06-05 17:34:47 +00:00
Thomas Camlong efd37dca93 fix(db): log when UNSAFE_ENABLE_MOCK_INTEGRATION is enabled during seeding
Warn on startup seed when mock integration env flag is set, matching demo mode visibility.
2026-06-05 18:43:33 +02:00
Thomas Camlong 796c679140 chore: merge main into dev 2026-06-05 18:42:31 +02:00
homarr-update-contributors[bot] 76fd4c5fb1 chore: update contributors (#5871) 2026-06-05 18:08:47 +02:00
Thomas Camlong 68f24e34d5 fix: disable onboarding tours when demo mode is enabled
Return completed tour status in demo mode so board and manage tours do not start for demo users.
2026-06-05 18:02:08 +02:00
homarr-update-contributors[bot] 22d7ebab50 chore: update integrations list readme (#5872)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-05 15:45:46 +00:00
Thomas Camlong 4f5f3e14a1 fix: sync docs sitemap and remove stale coolify ts-expect-error 2026-06-05 17:39:03 +02:00
Thomasandcopilot-swe-agent[bot] dbf021e04a feat(docker): add container logs modal and stats columns (#4807)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-05 16:57:26 +02:00
CopilotandThomas cc24f070db fix: redirect to login after credentials logout instead of crashing on current page (#5819)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-06-05 16:56:24 +02:00
Thomas 0b663a7fc6 feat(onboarding): guided management and board tours (#5756) 2026-06-05 16:54:50 +02:00
Afonso Jorge Ramos 0dc2558faf feat: add PeaNUT integration and UPS monitoring widget (#5862) 2026-06-05 16:51:09 +02:00
Thomas dd7a4908a3 feat(onboarding): enhance integration setup with Docker discovery and URL templating (#5607) 2026-06-05 16:50:35 +02:00
Thomas 0a6467979a feat: add comprehensive demo mode with mock integrations (#5870) 2026-06-05 16:36:45 +02:00
ThomasandMeier Lukas 25d964a197 refactor(spotlight): registry-driven CMDK preferences (#5817)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-06-05 16:26:48 +02:00
Thomas 3d3e3059a9 feat: media request search as first-class spotlight mode (#5823) 2026-06-05 16:09:14 +02:00
homarr-crowdin[bot]andMeier Lukas 9ae8cb2768 chore(lang): updated translations from crowdin (#5863)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-06-05 13:48:42 +02:00
Thomas 99346f288b feat(widgets): add in-place app editing in widget edit modal (#5809) 2026-06-05 12:31:50 +02:00
Thomas 41fe5928f1 fix: add bookmark widget option to toggle card borders (#5854) 2026-06-04 11:07:31 +02:00
homarr-renovate[bot] 6eb59d6f53 chore(deps): update tiptap monorepo to v3.24.0 (#5858)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-04 08:58:12 +00:00
Afonso Jorge Ramos 2c6b7a818e fix(immich): respect app color scheme in server-stats widget (#5859) 2026-06-04 09:50:02 +02:00
Thomas fd10788838 fix(auth): relax password complexity requirements to suggestions-only (#5764) 2026-06-04 07:56:34 +02:00
Thomas fe4598f722 fix(docker): restore container selection toolbar broken by Mantine v9 (#5855) 2026-06-03 10:03:37 +02:00
Thomas a755692d81 fix(ui): restore pre-v9 light variant colors (#5856) 2026-06-03 06:49:38 +00:00
homarr-renovate[bot] 3ff0d72ba1 chore(deps): update dependency next-intl to v4.13.0 (#5853)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-03 04:21:57 +00:00
homarr-renovate[bot] a63a191203 chore(deps): update turbo monorepo to ^2.9.16 (#5851)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 23:54:09 +00:00
homarr-renovate[bot] 1be906a0cd chore(deps): update dependency isomorphic-dompurify to ^3.15.0 (#5852)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 21:55:53 +00:00
homarr-renovate[bot] 63d8fa8c26 chore(deps): update dependency @ctrl/qbittorrent to ^9.12.2 (#5834)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 18:47:29 +00:00
homarr-renovate[bot] b5dd6ae8ef chore(deps): update babel monorepo to >=7.29.7 (#5830)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 14:11:26 +00:00
homarr-renovate[bot] d540ad374a chore(deps): update pnpm to v10.34.0 (#5846)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 09:17:24 +00:00
homarr-renovate[bot] 6a971c1986 chore(deps): update mantine monorepo to ^9.2.2 (#5844)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-02 04:13:28 +00:00
homarr-crowdin[bot] d86fe3ddf0 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-06-02 02:23:34 +00:00
homarr-renovate[bot] 06db8960a7 chore(deps): update turbo monorepo to ^2.9.15 (#5841)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-01 23:12:09 +00:00
homarr-renovate[bot] a5719acf34 chore(deps): update dependency tmp@<=0.2.3 to >=0.2.7 (#5848)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-01 21:06:21 +00:00
homarr-renovate[bot] d1d597c8e1 chore(deps): update testcontainers-node monorepo to ^12.0.1 (#5845)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-01 16:48:41 +00:00
homarr-renovate[bot] 657e42e4bb chore(deps): update dependency mysql2 to v3.22.4 (#5839)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-31 18:12:08 +00:00
homarr-renovate[bot] 256ebcb864 chore(deps): update dependency oxlint to ^1.67.0 (#5837)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-31 16:18:04 +00:00
homarr-renovate[bot] 72e86dee01 chore(deps): update dependency ioredis to v5.11.0 (#5838)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-31 14:53:18 +00:00
homarr-renovate[bot] b7910b16e0 chore(deps): update dependency oxfmt to ^0.52.0 (#5836)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-31 11:08:29 +00:00
homarr-renovate[bot] 5a83bb6e39 chore(deps): update dependency dayjs to ^1.11.21 (#5835)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-31 08:54:20 +00:00
homarr-renovate[bot] 433b66582d chore(deps): update dependency hono@<4.6.5 to >=4.12.23 (#5828)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-30 09:25:22 +00:00
homarr-renovate[bot] acbcb6818b chore(deps): update dependency react-error-boundary to ^6.1.2 (#5829)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-30 06:54:05 +00:00
homarr-crowdin[bot] 07d991a0a1 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-05-30 05:27:45 +00:00
homarr-renovate[bot] 92364e9574 chore(deps): update tanstack-query monorepo to ^5.100.14 (#5813)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-29 21:15:51 +00:00
Releases Homarr ff1e4c1381 chore(release): 1.63.0 [skip ci] 2026-05-29 21:07:15 +00:00
homarr-releases[bot] 21bb20ce0c chore(release): automatic release v1.63.0 2026-05-29 21:06:26 +00:00
ThomasandMeier Lukas 2991777b0a feat(users): rework general settings (#4915)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-29 21:22:55 +02:00
ThomasandMeier Lukas 6cc2ac56a9 fix: prevent GitHub releases check from blocking app on API failure (#5807)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-29 21:14:51 +02:00
homarr-update-contributors[bot] 92086e060f chore: update contributors (#5818) 2026-05-29 20:52:54 +02:00
homarr-renovate[bot] 2aa5ab95e6 chore(deps): update dependency trpc-to-openapi to ^3.3.0 (#5802)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-29 11:44:26 +00:00
Meier Lukas 650f761325 ci(format): fails to apply format due to conflict between --write and --check (#5815) 2026-05-29 11:07:48 +02:00
David be61602576 feat(auth): support custom token endpoint auth methods (#5806) 2026-05-29 10:51:28 +02:00
homarr-crowdin[bot] d01834d975 chore(lang): updated translations from crowdin (#5814) 2026-05-29 10:07:48 +02:00
Thomas Camlong 4a67bf0ef3 fix(ui): use imported theme instead of undefined createTheme
createTheme was called without being imported, causing a runtime
ReferenceError on every page load. Use the already-imported theme
object from @homarr/ui which contains the full Mantine configuration.
2026-05-28 15:54:37 +02:00
Thomas 4454e3fb7a fix: matine ui fixes for concistency (#5733) 2026-05-28 10:59:03 +02:00
Thomas c252b4f9ac feat(manage): unified management page layout (#5758) 2026-05-28 10:56:46 +02:00
homarr-renovate[bot] 4f2b3cf3ee chore(deps): update node.js to v24.16.0 (#5790)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-28 08:09:15 +00:00
Vivaan Dhawan b19549b601 fix: parse Speedtest Tracker created_at as local time (#5751) 2026-05-28 07:48:00 +00:00
homarr-renovate[bot] a0cc68798d chore(deps): update mantine monorepo to ^9.2.1 (#5801)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-28 03:50:55 +00:00
homarr-renovate[bot] 9a538941ee chore(deps): update dependency ws to ^8.21.0 (#5804)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 23:20:59 +00:00
homarr-renovate[bot] fc12500b3e chore(deps): update tanstack-query monorepo to ^5.100.13 (#5803)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 21:45:28 +00:00
homarr-renovate[bot] 64dccec362 chore(deps): update dependency oxfmt to ^0.51.0 (#5799)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 16:46:40 +00:00
homarr-renovate[bot]andThomas Camlong f7ea1b33a0 chore(deps): update mantine monorepo to v9 (#5411)
Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
2026-05-27 17:29:52 +02:00
homarr-renovate[bot] fe3cebb902 chore(deps): update dependency hono@<4.6.5 to >=4.12.22 (#5797)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 13:06:33 +00:00
homarr-renovate[bot] ba1d213870 chore(deps): update dependency oxlint to ^1.66.0 (#5798)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 10:00:11 +00:00
Thomas 8f03a38de5 perf(boards): parallelize board page data fetching (#5736) 2026-05-27 07:44:37 +00:00
605af19cd8 refactor: replace ESLint + Prettier with OXC (oxlint + oxfmt) (#5741)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
Co-authored-by: Meierschlumpf <63781622+Meierschlumpf@users.noreply.github.com>
2026-05-27 09:37:44 +02:00
homarr-renovate[bot] b4921af60d chore(deps): update dependency sass to ^1.100.0 (#5795)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-27 05:24:00 +00:00
homarr-renovate[bot] 32385206ad chore(deps): update tiptap monorepo to v3.23.6 (#5793)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-26 21:03:11 +00:00
homarr-renovate[bot] 7e59ba3d5e chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.14 (#5791)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-26 07:57:31 +00:00
homarr-renovate[bot] 81caaa9d6c chore(deps): update vitest monorepo to ^4.1.7 (#5789)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-25 08:30:36 +00:00
homarr-renovate[bot] 09243148da chore(deps): update dependency ldapts to v8.1.8 (#5788)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-25 04:03:12 +00:00
homarr-update-contributors[bot] c7c57887cf chore: update contributors (#5771) 2026-05-24 22:44:36 +02:00
homarr-renovate[bot] 990a924631 chore(deps): update testcontainers-node monorepo to v12 (#5785) 2026-05-24 22:09:13 +02:00
homarr-renovate[bot] 83caf3ef4b chore(deps): update dependency @types/react to v19.2.15 (#5787)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-24 19:26:04 +00:00
homarr-renovate[bot] 991f07ae51 chore(deps): update tiptap monorepo to v3.23.5 (#5783)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-24 15:12:39 +00:00
homarr-renovate[bot] bc05128cd4 chore(deps): update dependency hono@<4.6.5 to >=4.12.21 (#5782)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-24 13:34:35 +00:00
homarr-renovate[bot] cf0fb8a879 chore(deps): update dependency hono@<4.6.5 to >=4.12.20 (#5780)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-24 11:25:35 +00:00
homarr-renovate[bot] 1f31735ee0 chore(deps): update dependency isomorphic-dompurify to ^3.14.0 (#5779)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-24 10:02:26 +00:00
homarr-crowdin[bot] ff974bf343 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-05-24 02:04:49 +00:00
homarr-renovate[bot] d42bbfce17 chore(deps): update tanstack-query monorepo to ^5.100.11 (#5777)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-23 20:13:50 +00:00
homarr-renovate[bot] 0130303e74 chore(deps): update dependency semver-parser to ^4.2.0 (#5766)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-23 15:08:34 +00:00
AustinandClaude Sonnet 4.6 66212b4e47 feat(board): persist category collapse state in localStorage for guests (#5714)
* feat(board): persist category collapse state in localStorage for guests

Authenticated users already had their collapse state saved server-side.
This extends the same behaviour to unauthenticated (guest) users by
reading/writing localStorage keyed by section ID.

On mount, if the user is a guest, the stored value is applied to the
disclosure. On toggle, guests write to localStorage instead of calling
the protected API mutation.

Closes #3355

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: set localStorage value as useDisclosure default instead of useEffect

Replaces the useEffect that read localStorage after mount with a direct
initial value computation passed to useDisclosure. Removes the effect,
the open/close destructuring, and the useEffect import.

Note: initialCollapsed is computed during render rather than post-mount,
which may cause a hydration mismatch on SSR if localStorage differs from
the server-provided section.collapsed. The typeof window guard handles
the SSR case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 16:25:10 +02:00
Thomas f10bb3ae23 feat: auto-open widget settings on add with all integrations pre-selected (#5767)
When adding a widget from the "Choose item to add" modal, the widget
edit modal now opens immediately after placement, allowing the user to
configure options and integrations before the widget renders.

All available integrations for the widget are selected by default.
2026-05-23 16:19:36 +02:00
homarr-renovate[bot] af1991a4a3 chore(deps): update dependency pg to ^8.21.0 (#5776)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-23 13:35:11 +00:00
homarr-releases[bot] 9a9551130c chore: update bug report template (#5775)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-05-22 21:01:54 +00:00
Releases Homarr 92b75cb924 chore(release): 1.62.0 [skip ci] 2026-05-22 20:48:36 +00:00
Meier Lukas 1ea008b71c chore(release): automatic release v1.62.0 2026-05-22 22:47:05 +02:00
Meier Lukas a93f03eae4 Merge branch 'main' into dev 2026-05-22 22:46:15 +02:00
Thomas df4b57bd41 perf(boards): remove loading overlay and show board immediately (#5735) 2026-05-22 07:06:48 +00:00
ThomasandManuel dade477b32 feat(seed): add demo mode with pre-seeded user (#5739)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2026-05-21 16:08:46 +02:00
Thomas 79f88dc3b8 ci: add --affected filtering and fix Turbo cache invalidation (#5740)
* ci: add --affected filtering and fix Turbo cache invalidation

- Remove globalDependencies **/.env which invalidated all caches on any .env change
- Move 40+ env vars from globalEnv to build-specific env (lint/format/typecheck don't need DB_URL etc.)
- Add per-task inputs with test file exclusions so test changes don't trigger build/typecheck
- Enable affectedUsingTaskInputs for task-level --affected detection
- Add --affected to all turbo commands in CI (lint, format, typecheck, build)
- Add fetch-depth: 0 for proper git history comparison
- Add Docker Buildx + GHA layer caching for E2E builds

* ci: add Cloudflare remote cache for Turborepo

Configure turborepo-remote-cache-cloudflare as the remote cache backend.
This commit can be reverted independently to disable remote caching.

* fix(ci): set TURBO_SCM_BASE for --affected to work in PRs

Turbo defaults to comparing against `main`, but PRs target `dev`.
Without explicit base/head refs, --affected falls back to running
all tasks. Use the PR base SHA for pull_requests, the before SHA
for pushes, and HEAD~1 as a last resort.

* perf(typecheck): exclude bundled .cjs/.css from tsc

websocket (835K lines) and tasks (755K lines) had bundled output
files being typechecked due to `include: ["."]` in their tsconfig.
Full typecheck drops from ~70s to ~26s.

* perf(turbo): scope topo task inputs to package.json only

The topo virtual task had no explicit inputs, defaulting to all files.
This caused any source change to cascade cache invalidation through
all dependent packages via ^topo. Since topo is only for dependency
ordering, its hash should only change when dependencies change.

* fix(turbo): disable remote cache signature verification

The cache lives on a private CF worker, signature verification
just adds noise without security benefit.

* fix(turbo): remove runtime env vars from build cache key

These env vars (DB_*, AUTH_*, DOCKER_*, etc.) are all runtime
configuration provided by users when running the Docker container.
They don't influence the build output since Next.js standalone mode
reads process.env at runtime and SKIP_ENV_VALIDATION=true skips
validation during build. Listing them in build.env caused unnecessary
cache busting in local dev.

Also restores coverage report comments per review feedback.

* fix(turbo): include test files in typecheck cache inputs

Test files must be typechecked — excluding them from typecheck inputs
caused changed tests to skip type validation.
2026-05-21 14:11:27 +02:00
homarr-renovate[bot] 4a12f8be61 chore(deps): update dependency hono@<4.6.5 to >=4.12.19 (#5762)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-21 11:33:05 +00:00
homarr-renovate[bot] 044ad9edf9 fix(deps): update dependency better-sqlite3 to ^12.10.0 (#5727) 2026-05-21 07:16:58 +02:00
homarr-renovate[bot] 57d2c6a7e1 chore(deps): update dependency eslint to ^10.4.0 (#5761)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-20 16:59:38 +00:00
homarr-renovate[bot]andMeier Lukas 46f6301fdd chore(deps): update turbo monorepo to ^2.9.14 (#5754)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-20 17:05:07 +02:00
homarr-renovate[bot]andMeier Lukas c3f300d87d chore(deps): update dependency turbo to v2.9.14 [security] (#5760)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-20 16:54:18 +02:00
homarr-renovate[bot] 1de558dd51 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.13 (#5749)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-20 13:46:33 +00:00
homarr-renovate[bot] 2711f3db13 chore(deps): update dependency turbo to v2.9.14 [security] (#5759)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-20 10:57:42 +00:00
homarr-renovate[bot] ff3933b335 chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.60.4 (#5752)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-20 07:50:23 +00:00
Thomas fa737638a8 fix: exclude .env files from Docker build context (#5738) 2026-05-20 09:05:02 +02:00
homarr-renovate[bot] 97b7c417ab chore(deps): update dependency isomorphic-dompurify to ^3.13.0 (#5750)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-19 12:46:32 +00:00
homarr-renovate[bot] 58d8005d38 chore(deps): update dependency tsdav to ^2.2.2 (#5748)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-19 05:08:44 +00:00
homarr-renovate[bot] 507ac5181f chore(deps): update dependency @playwright/test to ^1.60.0 (#5746)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-19 00:14:25 +00:00
homarr-renovate[bot] b0cdb81533 chore(deps): update tiptap monorepo to v3.23.4 (#5743)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-18 22:10:37 +00:00
homarr-renovate[bot] fc0367a32f chore(deps): update dependency hono@<4.6.5 to >=4.12.18 (#5745)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-18 20:31:45 +00:00
Releases Homarr 48ef8eb6c0 chore(release): 1.61.1 [skip ci] 2026-05-18 19:45:44 +00:00
Meier Lukas 7cd30b640c fix(docker): improve icon matching (#5693)
* fix(docker): improve icon matching

* fix: unused import
2026-05-18 21:45:33 +02:00
Austin e9142c0dbc fix(downloads-widget): increase name column default width ratio 2026-05-18 21:44:47 +02:00
Meier LukasandThomas a9855fe8d3 chore(deps): remove deprecated million lint (#5671)
* chore(deps): remove deprecated million lint

* fix: install now missing playwright dependency

* fix: broken lockfile

* fix: broken lockfile

---------

Co-authored-by: Thomas <thomas@ajnart.fr>
2026-05-18 21:42:02 +02:00
StrandedTurtleandMeier Lukas 7a1272146c fix(tracearr): support absolute image urls (#5654)
* Update base URL handling for tracearr integration

* fix: remove unnecessary prefix from bcrypt hashing

* refactor: add is-absolute-url check

* test: add unit tests

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-18 21:39:01 +02:00
homarr-renovate[bot] db61b6f390 chore(deps): update dependency next-intl to v4.12.0 (#5734)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-18 19:02:14 +00:00
homarr-renovate[bot] 887c294191 chore(deps): update dependency axios@>=1.0.0 <1.8.2 to >=1.16.1 (#5742)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-18 16:31:32 +00:00
homarr-renovate[bot] 2f2a6291a2 chore(deps): update dependency linkifyjs@<4.3.2 to >=4.3.3 (#5732)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-18 04:30:21 +00:00
homarr-renovate[bot] 1b3353118d chore(deps): update tiptap monorepo to v3.23.2 (#5731)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-17 18:06:09 +00:00
homarr-renovate[bot] e03f56ac31 chore(deps): update dependency ws to ^8.20.1 (#5729)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-17 16:10:10 +00:00
homarr-renovate[bot] d571a7f75e chore(deps): update dependency swagger-ui-react to ^5.32.6 (#5726)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-17 11:13:32 +00:00
homarr-renovate[bot] e3b765b9bd chore(deps): update dependency fast-xml-parser to ^5.8.0 (#5725)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-17 06:51:41 +00:00
homarr-renovate[bot] 4da313826d chore(deps): update dependency @types/node to ^24.12.4 (#5724)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 22:53:08 +00:00
homarr-renovate[bot] 948ed7a7fa chore(deps): update vitest monorepo to ^4.1.6 (#5723)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 16:08:51 +00:00
homarr-renovate[bot] f7fc64ebc4 chore(deps): update tanstack-query monorepo to ^5.100.10 (#5722)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 15:04:21 +00:00
homarr-renovate[bot] b7f51d6f5c chore(deps): update dependency next-intl to v4.11.2 (#5721)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 11:58:47 +00:00
homarr-renovate[bot] e185eeb6d7 chore(deps): update dependency yaml to v2.9.0 (#5720)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 10:25:24 +00:00
homarr-renovate[bot] 90d703b870 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.12 (#5719)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-16 08:54:57 +00:00
75640f21e0 chore: merge current beta into dev (#5718)
* ci: support releases on next branch (#5599)

Co-authored-by: Copilot <copilot@github.com>

* perf(memory): combine 3 node processes into one (#5600)

Co-authored-by: Copilot <copilot@github.com>

* ci: add beta release workflow (#5601)

Co-authored-by: Copilot <copilot@github.com>

* chore(release): 1.60.1-beta.1 [skip ci]

* chore: retrigger release v1.60.1-beta.1

* chore(release): 1.60.1-beta.1 [skip ci]

* ci: disable package-manager cache

* chore(release): 1.60.1-beta.1 [skip ci]

* perf: (next branch) reduce idle memory on startup (#5637)

* chore(release): 1.60.1-beta.2 [skip ci]

* chore(release): 1.61.1-beta.1 [skip ci]

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: homarr-releases[bot] <175486441+homarr-releases[bot]@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-05-15 19:35:55 +02:00
Meier Lukas 4aa43ae27a Merge branch 'dev' into beta [skip-ci] 2026-05-15 19:25:38 +02:00
homarr-update-contributors[bot] 6b67671e29 chore: update contributors (#5717)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-05-15 13:59:07 +00:00
homarr-renovate[bot] 220f361b69 chore(deps): update turbo monorepo to ^2.9.12 (#5712)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-14 10:24:19 +00:00
homarr-renovate[bot] a1d6457ced chore(deps): update semantic-release monorepo (#5709)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-14 07:50:20 +00:00
homarr-renovate[bot] f7ade3c676 chore(deps): update dependency @tabler/icons-react to ^3.44.0 (#5711)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-14 04:15:07 +00:00
homarr-renovate[bot] f656d9ca2d chore(deps): update turbo monorepo to ^2.9.11 (#5710)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-14 00:07:48 +00:00
homarr-renovate[bot] cefd90385d chore(deps): update dependency next-intl to v4.11.1 (#5707)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-13 15:51:14 +00:00
homarr-renovate[bot] e5021b1d78 chore(deps): update tiptap monorepo to v3.23.1 (#5708)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-13 12:16:00 +00:00
homarr-renovate[bot] f0bd5132a3 chore(deps): update dependency brace-expansion@>=2.0.0 <=2.0.1 to >=5.0.6 (#5706)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-13 09:52:26 +00:00
homarr-renovate[bot] d356fa41a0 chore(deps): update dependency brace-expansion@>=1.0.0 <=1.1.11 to >=5.0.6 (#5705)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-13 06:55:25 +00:00
homarr-renovate[bot] a9e9ac1cfa chore(deps): update dependency @types/node to ^24.12.3 (#5704)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-12 23:09:48 +00:00
homarr-renovate[bot] 1b3e720701 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.0.11 (#5701)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-12 21:28:14 +00:00
homarr-renovate[bot] 2461363d4f chore(deps): update dependency @next/eslint-plugin-next to v16.2.6 (#5703)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-12 20:02:16 +00:00
homarr-renovate[bot] f29e74b6f4 chore(deps): update turbo monorepo to ^2.9.10 (#5702)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-12 17:19:38 +00:00
homarr-renovate[bot] 89dfe82fe9 chore(deps): update dependency @tabler/icons-react to ^3.43.0 (#5700)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 23:02:13 +00:00
homarr-renovate[bot] 48a0f75c31 chore(deps): update react monorepo to v19.2.6 (#5698)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 21:25:44 +00:00
homarr-renovate[bot] 37103cf414 chore(deps): update dependency next to v16.2.6 [security] (#5699)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 19:56:51 +00:00
homarr-renovate[bot] dec34916b2 chore(deps): update nextjs monorepo to v16.2.5 (#5697)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 21:03:28 +02:00
homarr-renovate[bot] 64a4c196c0 chore(deps): update dependency tsdav to ^2.2.1 (#5696)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 17:33:08 +00:00
homarr-renovate[bot] 41d596a1c9 chore(deps): update pnpm to v10.33.4 (#5695)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 15:01:42 +00:00
homarr-renovate[bot] bac0bc7b84 chore(deps): update dependency jotai to ^2.20.0 (#5694)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-11 14:00:57 +00:00
homarr-renovate[bot] 1ac6774821 chore(deps): update pnpm to v10.33.3 (#5689)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-10 15:00:34 +00:00
homarr-renovate[bot] 7adb21ca59 chore(deps): update dependency @tabler/icons-react to ^3.42.0 (#5691)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-10 13:27:39 +00:00
homarr-renovate[bot] 4daf2729e5 chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.60.3 (#5688)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-10 11:59:24 +00:00
homarr-renovate[bot] 1942c32128 chore(deps): update turbo monorepo to ^2.9.9 (#5690)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-10 10:24:31 +00:00
homarr-renovate[bot] b061a59433 chore(deps): update dependency fast-xml-parser to ^5.7.3 (#5692)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-10 09:01:25 +00:00
Meier Lukas ab867a9e50 chore: remove temporary ts-expect-error 2026-05-10 10:40:29 +02:00
homarr-crowdin[bot] 968338aa97 chore(lang): updated translations from crowdin (#5685)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-05-10 10:38:59 +02:00
Releases Homarr b34102a973 chore(release): 1.61.1-beta.1 [skip ci] 2026-05-08 21:55:01 +00:00
homarr-releases[bot] 378d1e1f1f chore(release): release v1.61.1-beta 2026-05-08 21:53:57 +00:00
Meier Lukas 3a8abe4415 chore(docs): update sitemap 2026-05-08 23:48:31 +02:00
Meier Lukas 6feb37dfd4 Merge branch 'main' into next 2026-05-08 23:46:21 +02:00
Meier Lukas 8042a18dc1 Merge branch 'main' into dev 2026-05-08 22:20:35 +02:00
homarr-releases[bot] c78bf05fc3 chore: update bug report template (#5682)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-05-08 20:17:34 +00:00
Releases Homarr 4635c51fe1 chore(release): 1.61.0 [skip ci] 2026-05-08 20:06:52 +00:00
homarr-releases[bot] 5cc0f97aa2 chore(release): automatic release v1.61.0 2026-05-08 20:05:49 +00:00
homarr-renovate[bot] b581f9b11a chore(deps): update dependency @extractus/feed-extractor to v7.2.1 (#5670)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-08 19:26:12 +00:00
Meier Lukas 756e8d1724 feat(qbittorrent): add api-key support (#5677) 2026-05-08 20:08:37 +02:00
homarr-renovate[bot] 7c64de5cbc chore(deps): update turbo monorepo to ^2.9.8 (#5669)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-08 17:25:39 +00:00
Ashton BattyandMeier Lukas 56af6bcaf0 feat(docker): Podman compatibility, multi-socket support, per-host error isolation (#5425)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-08 17:31:56 +02:00
homarr-renovate[bot] 84b269a80d chore(deps): update tanstack-query monorepo to ^5.100.9 (#5678)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-08 15:30:26 +00:00
Borislaw SotnikowandMeier Lukas 4d30a2e8c4 feat(installations): support secrets file for all environment variables (#5125)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-08 17:11:08 +02:00
Meier Lukas 2d15b3d407 chore(deps): update dependency zod to v4.4.3 (#5675) 2026-05-08 17:10:22 +02:00
Manuel 44a2973b1a revert(deps): downgrade pnpm to v10 (#5673) 2026-05-08 16:03:12 +02:00
Manuel 00a055283e fix(qbittorrent): support version 5.2 (#5672) 2026-05-08 15:44:04 +02:00
Manuel a474dcd961 chore: exclude renovate managed files from codeowners (#5676) 2026-05-08 15:37:41 +02:00
homarr-update-contributors[bot] 69fe180968 chore: update contributors (#5674)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-05-08 13:25:53 +00:00
036f6aa185 chore(deps): update dependency recharts to v3 (#4302)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-08 15:02:52 +02:00
homarr-crowdin[bot]andMeier Lukas 12a1bd7e2e chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-08 13:54:36 +02:00
homarr-renovate[bot] c4ec45a9d0 chore(deps): update pnpm to v11.0.4 (#5668)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-08 03:04:44 +00:00
homarr-renovate[bot]andMeier Lukas b8e0123893 chore(deps): update pnpm to v11 (#5660)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-07 22:46:55 +02:00
homarr-renovate[bot] df953771a7 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.16.0 (#5665)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-07 20:22:00 +00:00
44bd16d677 fix(calendar): keep working integrations when one fails (#5629)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-07 22:02:21 +02:00
sharpsalatandsharpsalat 277a509f49 feat: add pwa support (#5530)
* feat: add pwa support

* fix: deepsource warning

* fix: format warning

* fix: lint error

---------

Co-authored-by: sharpsalat <deniska.xrenov23@yandex.ru>
2026-05-07 21:53:17 +02:00
homarr-crowdin[bot]andMeier Lukas a9ad991892 chore(lang): updated translations from crowdin (#5623)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-07 21:04:10 +02:00
Meier Lukas 3183914a15 fix(apps): creation on the fly broken (#5605) 2026-05-07 20:59:12 +02:00
Meier Lukas 62be81ea60 fix(lang): portuguese is linked to brazilian portuguese on crowdin (#5622) 2026-05-07 20:54:11 +02:00
Meier Lukas ffc292b22a ci: increase timeout of asset extraction due job cancellation
https://github.com/homarr-labs/homarr/actions/runs/25509168979/attempts/1
2026-05-07 18:48:13 +02:00
Releases Homarr c83fdb2a6e chore(release): 1.60.1-beta.2 [skip ci] 2026-05-07 16:39:41 +00:00
ThomasandMeier Lukas b5c316b825 feat(spotlight): rework !bang search UX (#4813)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-07 18:38:40 +02:00
homarr-releases[bot] 81fdd90e7a chore(release): release v1.60.1-beta 2026-05-07 16:38:37 +00:00
KyleandThomas 831b6221fd feat: add auto/system appearance mode + fix dark mode system resources charts (#5492)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-05-07 16:28:37 +00:00
homarr-update-contributors[bot] 37edfb2de2 chore: update integrations list readme (#5662)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-05-07 16:11:16 +00:00
43cc68bdd4 feat: add Umami Analytics integration and widget (#5362)
Co-authored-by: Josh G <febrile42@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-05-07 17:50:58 +02:00
Meier Lukas 0937555d49 feat(entity-state): add data attributes for custom-css styling (#5655) 2026-05-07 16:54:57 +02:00
homarr-renovate[bot] 663fe980bf chore(deps): update dependency yaml to v2.8.4 (#5659)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-07 09:39:58 +00:00
homarr-renovate[bot] 9e2c487389 chore(deps): update dependency isomorphic-dompurify to ^3.12.0 (#5658)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-07 06:19:31 +00:00
homarr-renovate[bot] 698e551790 chore(deps): update tanstack-query monorepo to ^5.100.8 (#5657)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-07 06:18:42 +00:00
homarr-renovate[bot] 71e4526ad5 chore(deps): update dependency zod to ^4.4.2 (#5656)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-06 22:04:29 +00:00
Thomas 5ee84f8cf4 perf: (next branch) reduce idle memory on startup (#5637) 2026-05-06 21:39:26 +00:00
Meier Lukas b07a4a0f3e fix(update-checker): hide prereleases on non-prerelease versions (#5645) 2026-05-06 21:23:14 +02:00
homarr-renovate[bot] 62a476a949 chore(deps): update dependency eslint to ^10.3.0 (#5652)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-06 16:35:25 +00:00
homarr-renovate[bot] 6671ac48f9 chore(deps): update tanstack-query monorepo to ^5.100.7 (#5651)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-06 09:30:45 +00:00
homarr-renovate[bot] 3c7a7615ba chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.11 (#5648)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-06 06:13:36 +00:00
homarr-renovate[bot] 2f301bb56a chore(deps): update turbo monorepo to ^2.9.7 (#5649)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-06 03:01:59 +00:00
homarr-renovate[bot] e06abbc176 chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.10 (#5646)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-05 21:10:38 +00:00
homarr-renovate[bot] 04daeba089 chore(deps): update dependency jsdom to ^29.1.1 (#5643)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-05 10:55:08 +00:00
homarr-renovate[bot] 7c83d2cb6e chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.16 (#5642)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-05 10:54:29 +00:00
homarr-renovate[bot] 2829d145af chore(deps): update dependency zod to ^4.4.1 (#5641)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-05 02:56:52 +00:00
homarr-renovate[bot] 44bf36fff9 chore(deps): update dependency timezones-ical-library to ^2.2.0 (#5640)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-05 00:01:35 +00:00
homarr-renovate[bot] acd11c3fc1 chore(deps): update dependency isomorphic-dompurify to ^3.11.0 (#5638)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-04 16:53:37 +00:00
homarr-renovate[bot] b9c701a71b chore(deps): update dependency next-intl to v4.11.0 (#5635)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 19:08:04 +00:00
homarr-renovate[bot] e52ecd64ee chore(deps): update dependency next-intl to v4.10.1 (#5632)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 16:59:56 +00:00
homarr-renovate[bot] af77f729ec chore(deps): update tanstack-query monorepo to ^5.100.6 (#5633)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 16:59:30 +00:00
homarr-renovate[bot] 7c39e4de13 chore(deps): update dependency next-intl to v4.10.0 (#5631)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 15:56:23 +00:00
homarr-renovate[bot] 3e918e5d3d chore(deps): update dependency tsdav to ^2.2.0 (#5627)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 14:57:50 +00:00
homarr-renovate[bot] 9cd45f7832 chore(deps): update trpc monorepo to ^11.17.0 (#5630)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 13:19:32 +00:00
homarr-renovate[bot] 90ca3770a1 chore(deps): update tiptap monorepo to v3.22.5 (#5628)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-03 10:12:22 +00:00
homarr-renovate[bot]andMeier Lukas a183339063 chore(deps): update dependency eslint to v10 (#5038)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-02 21:41:16 +02:00
homarr-renovate[bot] 0a4e4c3b70 chore(deps): update dependency next-intl to v4.9.2 (#5625)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-02 19:07:29 +00:00
homarr-renovate[bot] b52e664d0f chore(deps): update dependency swagger-ui-react to ^5.32.5 (#5621)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-02 13:21:15 +00:00
homarr-renovate[bot] 9f4634a95d chore(deps): update dependency mysql2 to v3.22.3 (#5619)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-02 11:25:52 +00:00
homarr-renovate[bot] a6f6c2e945 chore(deps): update dependency jsdom to ^29.1.0 (#5620)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-05-02 02:53:35 +00:00
homarr-crowdin[bot]andMeier Lukas b6d23e7546 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-05-02 01:32:16 +00:00
Meier Lukas b0f2c94674 chore: update bug report template 2026-05-01 22:51:17 +02:00
Releases Homarr 80d18bb10d chore(release): 1.60.1-beta.1 [skip ci] 2026-05-01 20:44:34 +00:00
homarr-releases[bot] 2bb0f61da7 chore(release): release v1.60.1-beta 2026-05-01 20:43:32 +00:00
Meier Lukas 24386656fd Merge branch 'next' of https://github.com/homarr-labs/homarr into next 2026-05-01 22:42:07 +02:00
Meier Lukas 453360fe61 ci: disable package-manager cache 2026-05-01 22:42:02 +02:00
Releases Homarr beac1ed780 chore(release): 1.60.1-beta.1 [skip ci] 2026-05-01 20:41:02 +00:00
homarr-releases[bot] aa1b3810f1 chore(release): release v1.60.1-beta 2026-05-01 20:40:05 +00:00
Meier Lukas ce3c1b6cc1 chore: retrigger release v1.60.1-beta.1 2026-05-01 22:38:50 +02:00
Releases Homarr 3f6b830f9b chore(release): 1.60.1-beta.1 [skip ci] 2026-05-01 20:36:08 +00:00
homarr-releases[bot] f0cb79799a chore(release): release v1.60.1-beta 2026-05-01 20:35:08 +00:00
Meier Lukas 7561462594 Merge branch 'main' into next 2026-05-01 22:30:33 +02:00
homarr-releases[bot] 9cf0070442 chore: update bug report template (#5612)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-05-01 20:23:03 +00:00
Meier LukasandCopilot 647afbe2cb ci: add beta release workflow (#5601)
Co-authored-by: Copilot <copilot@github.com>
2026-05-01 22:20:58 +02:00
Meier LukasandCopilot bfa8566284 ci: add beta release workflow (#5601)
Co-authored-by: Copilot <copilot@github.com>
2026-05-01 22:18:45 +02:00
Releases Homarr e31555776e chore(release): 1.60.0 [skip ci] 2026-05-01 20:12:31 +00:00
homarr-releases[bot] 3c88ae9023 chore(release): automatic release v1.60.0 2026-05-01 20:11:31 +00:00
Meier Lukas 5200c17baf chore: retrigger release v1.60.0 2026-05-01 22:10:44 +02:00
Releases Homarr 97311b338a chore(release): 1.60.0 [skip ci] 2026-05-01 20:04:34 +00:00
homarr-releases[bot] a6b7e9df93 chore(release): automatic release v1.60.0 2026-05-01 20:03:37 +00:00
Meier LukasandCopilot a1aa5505a3 perf(memory): combine 3 node processes into one (#5600)
Co-authored-by: Copilot <copilot@github.com>
2026-05-01 16:35:11 +02:00
homarr-update-contributors[bot] e9289bd174 chore: update contributors (#5609)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-05-01 14:25:59 +00:00
Meier Lukas e8edc203b6 fix: migrate from node-cron to croner (#5407) 2026-05-01 16:21:38 +02:00
Meier Lukas ed6680ba6a perf(icons): updater takes 10 seconds to run with high cpu usage (#5608) 2026-05-01 16:21:22 +02:00
homarr-renovate[bot] 16c6929396 chore(deps): update tanstack-query monorepo to ^5.100.5 (#5606)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-30 19:30:50 +00:00
homarr-renovate[bot] dba9efc726 chore(deps): update tanstack-query monorepo to ^5.100.4 (#5603)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-30 17:53:27 +00:00
Meier Lukas ef012981db chore: replace app-id with client-id for actions/create-github-app-token due to deprecation (#5602) 2026-04-30 18:28:55 +02:00
Meier LukasandCopilot e77b9bb3d2 ci: support releases on next branch (#5599)
Co-authored-by: Copilot <copilot@github.com>
2026-04-30 18:28:32 +02:00
Meier Lukas c24f4954ea ci: fix minor issues (#5604) 2026-04-30 18:27:57 +02:00
Meier Lukas a268fd5a26 chore(issue-template): fix issues for integration and widget template 2026-04-30 17:49:24 +02:00
homarr-renovate[bot] 8094458b10 chore(deps): update dependency fast-xml-parser to ^5.7.2 (#5591)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-30 12:21:17 +00:00
homarr-crowdin[bot]andMeier Lukas b88a1a771e chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-30 12:20:44 +00:00
homarr-renovate[bot] b94d2f15bc chore(deps): update tanstack-query monorepo to ^5.100.2 (#5594)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-30 12:16:25 +00:00
Meier LukasandCopilot ce8e330c82 fix(openapi): openapi document empty (#5579)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 23:29:42 +02:00
homarr-renovate[bot] 0e7b9fa2d4 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.15 (#5589)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-29 19:33:19 +00:00
Meier Lukas 42b108964b ci: remove turbo cache (#5590) 2026-04-29 20:54:09 +02:00
homarr-renovate[bot] ff630ea647 chore(deps): update dependency dockerode to v5 (#5588)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-29 15:43:59 +02:00
homarr-renovate[bot] 45e5827872 chore(deps): update tanstack-query monorepo to ^5.100.1 (#5586)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-28 20:22:56 +00:00
homarr-renovate[bot] b20e8a1cd1 chore(deps): update pnpm to v10.33.2 (#5585)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-28 17:59:14 +00:00
homarr-renovate[bot] 712da837b1 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.10 (#5583)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-28 06:35:34 +00:00
homarr-crowdin[bot] 424999e13a chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-28 01:49:29 +00:00
homarr-renovate[bot] f8ce537dce chore(deps): update dependency fastify to ^5.8.5 (#5580)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-27 21:10:03 +00:00
Meier Lukas 3d202d409f feat(about): add opencollective contributors (#5481) 2026-04-27 22:35:42 +02:00
homarr-renovate[bot] 1c8fc989da chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.15.2 (#5575)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-27 19:53:12 +00:00
homarr-renovate[bot] a410ee7caf chore(deps): update tanstack-query monorepo to ^5.99.2 (#5563)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-27 19:52:57 +00:00
homarr-renovate[bot] 03f80e1ee2 chore(deps): update dependency isomorphic-dompurify to ^3.10.0 (#5578)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-27 19:52:11 +00:00
homarr-crowdin[bot] c04baab4e9 chore(lang): updated translations from crowdin (#5572)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-27 21:50:03 +02:00
homarr-renovate[bot] feb84c9a62 chore(deps): update pnpm to v10.33.1 (#5576)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-27 11:12:07 +00:00
homarr-renovate[bot] e097aa3ee9 chore(deps): update vitest monorepo to ^4.1.5 (#5574)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-26 17:45:38 +00:00
homarr-update-contributors[bot] a8bf9219a2 chore: update contributors (#5564)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-26 19:22:10 +02:00
homarr-renovate[bot] 9199a2b221 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.9 (#5569)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-26 05:46:37 +00:00
homarr-renovate[bot] fda64b36ae chore(deps): update dependency mysql2 to v3.22.2 (#5573)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-26 02:56:09 +00:00
homarr-releases[bot] ad19e4c954 chore: update bug report template (#5568)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-25 16:58:24 +00:00
Releases Homarr d91ca61ff0 chore(release): 1.59.3 [skip ci] 2026-04-24 21:11:59 +00:00
homarr-releases[bot] 05c260667a chore(release): automatic release v1.59.3 2026-04-24 21:11:01 +00:00
Meier Lukas fb5b882e28 chore: retrigger release v1.59.3 2026-04-24 23:09:56 +02:00
Releases Homarr c5e00c2543 chore(release): 1.59.3 [skip ci] 2026-04-24 19:52:38 +00:00
homarr-releases[bot] 33964fbfe8 chore(release): automatic release v1.59.3 2026-04-24 19:51:32 +00:00
homarr-renovate[bot] abd42de067 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.15.1 (#5565)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-24 18:00:22 +00:00
homarr-renovate[bot] 7852f1d987 fix(deps): update dependency better-sqlite3 to ^12.9.0 (#5531)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-24 15:38:24 +02:00
homarr-renovate[bot] e4dab4d1b4 chore(deps): update tanstack-query monorepo to ^5.99.1 (#5559)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-24 05:43:00 +00:00
homarr-renovate[bot] 346f57f3fa chore(deps): update dependency fast-xml-parser to ^5.7.1 (#5556)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-24 05:41:52 +00:00
homarr-renovate[bot] a4c8d67e92 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.60.2 (#5558)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-24 02:49:24 +00:00
homarr-crowdin[bot] e53cf65054 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-24 01:26:52 +00:00
homarr-renovate[bot] 320e88992a chore(deps): update tiptap monorepo to v3.22.4 (#5557)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-23 13:52:25 +00:00
homarr-renovate[bot] 678e151681 chore(deps): update dependency fast-xml-parser to ^5.7.0 (#5555)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-22 15:21:42 +00:00
homarr-renovate[bot] 5d1517bdf2 chore(deps): update nextjs monorepo to v16.2.4 (#5552)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-22 05:33:24 +00:00
homarr-renovate[bot] dc7b1b005e chore(deps): update dependency mysql2 to v3.22.1 (#5554)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-22 02:46:06 +00:00
homarr-crowdin[bot] 9bbc8b4db9 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-22 01:22:33 +00:00
homarr-renovate[bot] 0a32fe0967 chore(deps): update node.js (#5546)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-21 19:21:25 +00:00
homarr-renovate[bot] 0d0dc6aaa0 chore(deps): update dependency isomorphic-dompurify to ^3.9.0 (#5550)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 16:18:07 +00:00
homarr-renovate[bot] bd1a15af22 chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.9 (#5551)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 14:59:22 +00:00
homarr-renovate[bot] 841fdbdd78 chore(deps): update dependency swagger-ui-react to ^5.32.4 (#5549)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 10:56:48 +00:00
homarr-renovate[bot] de595b0623 chore(deps): update dependency prettier to ^3.8.3 (#5545)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 08:16:36 +00:00
homarr-renovate[bot] 9c52a33f3a chore(deps): update dependency fast-xml-parser to ^5.6.0 (#5548)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 05:50:18 +00:00
homarr-renovate[bot] 3b14cb0783 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.14 [security] (#5524)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-20 02:49:29 +00:00
homarr-renovate[bot] 4a2b221c25 chore(deps): update dependency next-auth to v5.0.0-beta.31 (#5543)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-19 22:44:37 +00:00
homarr-renovate[bot] 3bfa9d669e chore(deps): update dependency typescript-eslint to ^8.58.2 (#5537)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-19 20:49:49 +00:00
homarr-renovate[bot] 8fdfde4f2f chore(deps): update dependency swagger-ui-react to ^5.32.3 (#5539)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-19 20:47:33 +00:00
homarr-renovate[bot] 7873c175b2 chore(deps): update dependency @auth/drizzle-adapter to ^1.11.2 (#5542)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-19 20:47:06 +00:00
homarr-renovate[bot] e583328e87 chore(deps): update dependency @auth/core to ^0.41.2 (#5541)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-19 19:49:34 +00:00
homarr-renovate[bot] 4d024a2a02 chore(deps): update dependency fast-xml-parser to ^5.5.12 (#5534)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-18 16:49:40 +00:00
homarr-renovate[bot] 051e0c3dc0 chore(deps): update dependency @immich/sdk to ^2.7.5 (#5535)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-18 15:45:46 +00:00
homarr-renovate[bot] 5114bba2b1 chore(deps): update dependency dotenv to ^17.4.2 (#5529)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-17 20:52:17 +00:00
homarr-releases[bot] ec986147a2 chore: update bug report template (#5533)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-17 20:09:25 +00:00
Releases Homarr cf2e18d248 chore(release): 1.59.2 [skip ci] 2026-04-17 19:58:17 +00:00
homarr-releases[bot] 1efe97d8e6 chore(release): automatic release v1.59.2 2026-04-17 19:57:21 +00:00
homarr-update-contributors[bot] 469d7bf6fb chore: update contributors (#5528)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-17 13:11:09 +00:00
homarr-renovate[bot] 0082984f9c chore(deps): update tanstack-query monorepo to ^5.99.0 (#5527)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-17 10:39:11 +00:00
2e789795ed fix: preserve all-day event dates in Nextcloud calendar integration (#5523)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-17 10:53:43 +02:00
homarr-renovate[bot] 88591d973f chore(deps): update pnpm/action-setup action to v6 (#5521)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-17 10:17:39 +02:00
homarr-crowdin[bot] 6218a401de chore(lang): updated translations from crowdin (#5522)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-17 10:12:57 +02:00
homarr-renovate[bot] a787e3deca chore(deps): update tanstack-query monorepo to ^5.98.0 (#5525)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-16 17:22:33 +00:00
homarr-renovate[bot] d195d260aa chore(deps): update dependency prettier to ^3.8.2 (#5515)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-15 20:09:26 +00:00
homarr-renovate[bot] d0973a9290 chore(deps): update dependency @immich/sdk to ^2.7.4 (#5520)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-15 18:15:03 +00:00
homarr-renovate[bot] 6866735cc1 chore(deps): update dependency mysql2 to v3.22.0 (#5519)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-15 14:55:24 +00:00
homarr-renovate[bot] 481999af5c chore(deps): update turbo monorepo to ^2.9.6 (#5518)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-15 14:54:53 +00:00
homarr-renovate[bot] 6a45645354 chore(deps): update dependency mysql2 to v3.21.1 (#5514)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-15 02:44:06 +00:00
homarr-crowdin[bot] f22a763d96 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-15 01:21:25 +00:00
homarr-renovate[bot] 7dd579d3c0 chore(deps): update dependency @immich/sdk to ^2.7.3 (#5511)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-14 19:23:50 +00:00
homarr-renovate[bot] 13ea6d08a0 chore(deps): update tanstack-query monorepo to ^5.97.0 (#5509)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-14 09:28:43 +00:00
homarr-renovate[bot] 8c1e5c8947 chore(deps): update dependency mysql2 to v3.21.0 (#5508)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-14 09:27:46 +00:00
homarr-renovate[bot] b397ab72c9 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.8 (#5506)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-14 09:27:27 +00:00
homarr-renovate[bot] 792a8bd11e chore(deps): update vitest monorepo to ^4.1.4 (#5507)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-14 07:53:42 +00:00
homarr-crowdin[bot] f680fd170f chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-14 01:24:45 +00:00
homarr-renovate[bot] 916a261467 chore(deps): update dependency @next/eslint-plugin-next to v16.2.3 (#5501)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 21:58:44 +00:00
Meier Lukas 72d636c0f0 refactor(deps): migrate to pnpm catalog (#5464) 2026-04-13 22:45:24 +02:00
homarr-renovate[bot] c8a61a9df3 fix(deps): update react monorepo to v19.2.5 (#5502)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 20:14:59 +00:00
homarr-renovate[bot] 0731b7c5ef fix(deps): update dependency isomorphic-dompurify to ^3.8.0 (#5503)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 20:14:28 +00:00
homarr-renovate[bot] f2437c4731 fix(deps): update dependency next-intl to v4.9.1 [security] (#5470)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 18:04:13 +00:00
homarr-renovate[bot] 652ed1a5ed fix(deps): update dependency fast-xml-parser to ^5.5.11 (#5500)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 14:58:44 +00:00
Meier Lukas c48588c6a7 fix(about): add missing dependencies (#5480) 2026-04-13 16:51:38 +02:00
Meier Lukas ce0c72b008 ci: approve homarr-releases pull-requests automatically (#5468) 2026-04-13 16:46:10 +02:00
homarr-renovate[bot] 1057b478c3 fix(deps): update testcontainers-node monorepo to ^11.14.0 (#5498)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 14:43:08 +00:00
homarr-crowdin[bot] 4d53193fcc chore(lang): updated translations from crowdin (#5483)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-13 16:39:22 +02:00
homarr-renovate[bot] 95abd2c9d3 fix(deps): update tiptap monorepo to v3.22.3 (#5499)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 12:11:25 +00:00
homarr-renovate[bot] f88984cda8 fix(deps): update dependency typescript-eslint to ^8.58.1 (#5496)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-13 02:54:07 +00:00
homarr-renovate[bot] 0927f45b25 fix(deps): update dependency @immich/sdk to ^2.7.2 (#5494)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 21:42:41 +00:00
homarr-renovate[bot] ed24ce6381 fix(deps): update turbo monorepo to ^2.9.5 (#5491)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 17:46:20 +00:00
homarr-renovate[bot] 24607e266b chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.7 (#5486)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 17:42:25 +00:00
homarr-renovate[bot] 9cdf2aa1f2 fix(deps): update dependency @immich/sdk to ^2.7.0 (#5490)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 16:49:27 +00:00
homarr-renovate[bot] 74611b75a7 fix(deps): update dependency @drizzle-team/brocli to ^0.12.0 (#5489)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 15:44:44 +00:00
homarr-renovate[bot] cf131c5c51 fix(deps): update dependency swagger-ui-react to ^5.32.2 (#5488)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 14:47:24 +00:00
homarr-renovate[bot] efd8a55b6e fix(deps): update dependency jotai to ^2.19.1 (#5487)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 14:46:52 +00:00
homarr-renovate[bot] c0c16be9e4 chore(deps): update vitest monorepo to ^4.1.3 (#5485)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 10:47:52 +00:00
homarr-renovate[bot] 327611dfc6 chore(deps): update dependency jsdom to ^29.0.2 (#5484)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-12 05:32:06 +00:00
homarr-renovate[bot] c5149d4262 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.5 (#5482)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-11 13:06:56 +00:00
homarr-renovate[bot] 3f039bf57d chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.4 (#5479)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-11 07:56:52 +00:00
homarr-renovate[bot] 053cb8bec3 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.12 [security] (#5438)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-11 06:04:19 +00:00
homarr-renovate[bot] 813f2fa5ef fix(deps): update turbo monorepo to ^2.9.4 (#5476)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-11 02:33:48 +00:00
homarr-crowdin[bot] 3d0c9fde0c chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-11 01:15:30 +00:00
homarr-releases[bot] c74367e564 chore: update bug report template (#5475)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-10 22:49:51 +00:00
Releases Homarr 5e2b693f6a chore(release): 1.59.1 [skip ci] 2026-04-10 22:39:52 +00:00
homarr-releases[bot] 8df71f7695 chore(release): automatic release v1.59.1 2026-04-10 22:39:07 +00:00
Meier Lukas db6bb870e3 fix(qbittorrent): requests fail (#5473) 2026-04-11 00:38:05 +02:00
Meier Lukas dc44fac42a revert(deps): downgrade undici from v8 to v7 (#5473) 2026-04-11 00:33:22 +02:00
Meier Lukas 26c2fa555b chore: update documentation sitemap 2026-04-10 23:33:03 +02:00
homarr-releases[bot] 3ce3d221cd chore: update bug report template (#5466)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-10 21:54:40 +02:00
Releases Homarr 62e9bb0c49 chore(release): 1.59.0 [skip ci] 2026-04-10 19:43:43 +00:00
homarr-releases[bot] b11eaff043 chore(release): automatic release v1.59.0 2026-04-10 19:42:45 +00:00
homarr-renovate[bot] 845f11526e fix(deps): update dependency dotenv to ^17.4.1 (#5462)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-10 19:22:28 +00:00
homarr-renovate[bot] 7982def853 fix(deps): update dependency next to v16.2.3 [security] (#5461)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-10 21:05:50 +02:00
homarr-renovate[bot] 13e13ba352 fix(deps): update dependency react-quill-new to ^3.8.3 (#5463)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-10 19:05:14 +00:00
homarr-renovate[bot] 55b743496b chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.15.0 [security] (#5453)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-10 20:57:19 +02:00
homarr-update-contributors[bot] 4a6aecceea chore: update integrations list readme (#5459)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-10 18:49:31 +00:00
Meier Lukas 2454cac7be fix(security): remove salt from database (#5412) 2026-04-10 20:48:20 +02:00
Meier Lukas 41a03ef4e0 fix(logs): missing fields from top level error with metadata in output (#5456) 2026-04-10 20:46:59 +02:00
7879b31630 feat: Anchor integration and Anchor Note widget (#5040)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-10 20:43:43 +02:00
homarr-update-contributors[bot] 57ea82f088 chore: update integrations list readme (#5457)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-10 17:21:07 +00:00
KyleandMeier Lukas 22d599c0cf feat: speedtest tracker integration (#5363)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-10 19:04:42 +02:00
homarr-crowdin[bot] c861878058 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-10 16:13:35 +00:00
homarr-update-contributors[bot] 9f5ae31222 chore: update contributors (#5455)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-10 16:13:04 +00:00
Meier Lukas 6ec058c835 feat(rss-feed): add optional poster (#5452) 2026-04-10 18:11:20 +02:00
Meier Lukas faffb818f9 feat(rss-feed): add periodic live updates (#5449) 2026-04-09 22:45:44 +02:00
Meier Lukas bdf9439e49 ci: disable skip-stability-days for forks (#5451) 2026-04-09 22:45:01 +02:00
homarr-renovate[bot] 75b36bb160 fix(deps): update dependency undici to v8.0.2 (#5446)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-09 16:01:05 +00:00
homarr-renovate[bot] 43587656bb chore(deps): update dependency proxmox-api&gt;undici to v8.0.2 (#5445)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-09 15:59:56 +00:00
homarr-renovate[bot] 8cab4b0333 fix(deps): update tiptap monorepo to v3.22.2 (#5448)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-09 14:14:48 +00:00
homarr-crowdin[bot] 154ad1e01e chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-09 01:09:23 +00:00
homarr-renovate[bot] cd2d0ba53f chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.28.0 (#5436)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 18:14:49 +02:00
homarr-renovate[bot] 37d7c8535f chore(deps): update dependency esbuild to ^0.28.0 (#5435)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 18:14:39 +02:00
homarr-renovate[bot] a0e0083b16 fix(deps): update dependency undici to v8.0.1 (#5440)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 12:06:50 +00:00
homarr-renovate[bot] 5317a25cfb chore(deps): update dependency @types/node to ^24.12.2 (#5443)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 12:05:47 +00:00
homarr-renovate[bot] b619ba1d46 fix(deps): update tanstack-query monorepo to ^5.96.2 (#5442)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 11:08:35 +00:00
homarr-renovate[bot] 2baab5402d fix(deps): update tiptap monorepo to v3.22.1 (#5434)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 11:08:05 +00:00
homarr-renovate[bot] 174851bd19 chore(deps): update dependency @types/node to ^24.12.1 (#5441)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 11:05:37 +00:00
homarr-renovate[bot] a7b195d2cb fix(deps): update dependency fast-xml-parser to ^5.5.10 (#5439)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 11:04:39 +00:00
homarr-renovate[bot] 0ab78142b3 chore(deps): update dependency proxmox-api&gt;undici to v8.0.1 (#5431)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-08 09:08:51 +00:00
homarr-renovate[bot] 3f73474d31 fix(deps): update dependency sass to ^1.99.0 (#5437)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-07 23:47:06 +00:00
homarr-renovate[bot]andMeier Lukas f2f5128cbd fix(deps): update dependency undici to v8 (#5432)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-07 16:52:42 +02:00
homarr-renovate[bot] 50bf7cdf19 fix(deps): update dependency dotenv to ^17.4.0 (#5427)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-07 14:24:59 +00:00
homarr-renovate[bot] ec15529ca4 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.10 (#5430)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-07 14:22:54 +00:00
homarr-renovate[bot] db8a3291da chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.27.5 (#5429)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-07 16:18:33 +02:00
homarr-renovate[bot] e2410f143c chore(deps): update dependency esbuild to ^0.27.5 (#5428)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-07 16:18:19 +02:00
homarr-crowdin[bot] cb2862bc79 chore(lang): updated translations from crowdin (#5417)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-07 16:12:15 +02:00
homarr-renovate[bot] dbe8d84202 fix(deps): update dependency adm-zip to v0.5.17 (#5422)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-06 21:45:44 +00:00
homarr-renovate[bot] 612038ad07 fix(deps): update dependency next-intl to v4.9.0 (#5426)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-06 20:46:21 +00:00
homarr-renovate[bot] 72ad935ddf fix(deps): update tanstack-query monorepo to ^5.96.1 (#5421)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-06 09:17:17 +00:00
homarr-renovate[bot] 859747037b fix(deps): update nextjs monorepo to v16.2.2 (#5418)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-06 02:45:18 +00:00
homarr-renovate[bot] dfa87939f2 fix(deps): update turbo monorepo to ^2.9.3 (#5419)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-06 02:44:53 +00:00
homarr-renovate[bot] 402d948fe7 fix(deps): update tiptap monorepo to v3.22.0 (#5414)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-05 21:39:56 +00:00
homarr-renovate[bot] d8480c05a6 fix(deps): update turbo monorepo to ^2.9.2 (#5415)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-05 20:44:00 +00:00
homarr-renovate[bot] 6047eb4477 fix(deps): update dependency next-intl to v4.8.4 (#5413)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-05 16:45:33 +00:00
homarr-renovate[bot] 8b1e3babc0 fix(deps): update tanstack-query monorepo to ^5.96.0 (#5410)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-05 11:37:24 +00:00
homarr-renovate[bot] 1e869f6a96 fix(deps): update dependency typescript-eslint to ^8.58.0 (#5409)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-04 21:40:41 +00:00
homarr-renovate[bot] 7607140f94 fix(deps): update turbo monorepo to ^2.9.1 (#5408)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-04 20:41:33 +00:00
homarr-renovate[bot] f037da5831 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.60.1 (#5402)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-04 11:36:15 +00:00
homarr-renovate[bot] f0ef4729bf fix(deps): update dependency @tabler/icons-react to ^3.41.1 (#5400)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-04 10:42:36 +00:00
homarr-releases[bot] ab1418514d chore: update bug report template (#5406)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-04 10:08:48 +00:00
Releases Homarr a5b6c120dd chore(release): 1.58.1 [skip ci] 2026-04-04 09:53:34 +00:00
homarr-releases[bot] 9e7d5ad629 chore(release): automatic release v1.58.1 2026-04-04 09:52:41 +00:00
Meier Lukas a86cdb9fb9 fix: arabic text in english translations 2026-04-04 11:50:19 +02:00
homarr-update-contributors[bot] 270fce6a0e chore: update integrations list readme (#5404)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-04 09:46:50 +00:00
homarr-crowdin[bot] 2680255f44 chore(lang): updated translations from crowdin (#5401)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-04-04 11:45:23 +02:00
Meier Lukas ddf87d278e chore: remove temporary ts-expect-error for search-ch documentation link 2026-04-04 11:41:25 +02:00
homarr-releases[bot] 822d2c194b chore: update bug report template (#5399)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-04-03 21:50:53 +02:00
Releases Homarr f5f7f3e433 chore(release): 1.58.0 [skip ci] 2026-04-03 19:38:14 +00:00
homarr-releases[bot] b1b3b79cd9 chore(release): automatic release v1.58.0 2026-04-03 19:37:17 +00:00
homarr-update-contributors[bot] 35d40d0dea chore: update integrations list readme (#5397)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-03 18:28:41 +00:00
Meier Lukas abb2dfc92a feat(integrations): add search.ch integration with timetable widget (#5180) 2026-04-03 20:23:15 +02:00
homarr-renovate[bot] 279d830b79 chore(deps): update dependency conventional-changelog-conventionalcommits to ^9.3.1 (#5396)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-03 17:25:35 +00:00
homarr-renovate[bot] 72a93e681f fix(deps): update trpc monorepo to ^11.16.0 (#5392)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-03 16:48:45 +00:00
Meier Lukas c977b84e2d fix: node-ical prevents many trpc queries from loading (#5395) 2026-04-03 18:26:52 +02:00
homarr-update-contributors[bot] e92c407beb chore: update contributors (#5394)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-03 13:24:06 +00:00
homarr-crowdin[bot]andMeier Lukas 05af82bc02 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-03 01:17:29 +00:00
homarr-crowdin[bot]andMeier Lukas 531bfd6fcf chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-02 18:53:29 +00:00
homarr-renovate[bot] fa40b6b647 fix(deps): update turbo monorepo to ^2.8.21 (#5391)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-02 18:50:09 +00:00
homarr-renovate[bot] c363cf8457 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.14.0 (#5389)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 19:09:32 +00:00
homarr-renovate[bot] 0a78e3f12a fix(deps): update dependency drizzle-orm to ^0.45.2 (#5388)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 19:02:54 +00:00
homarr-crowdin[bot]andMeier Lukas f380b383a3 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-01 18:13:33 +02:00
homarr-renovate[bot] b30dbafbbb fix(deps): update tiptap monorepo to v3.21.0 (#5386)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 15:13:38 +00:00
homarr-renovate[bot] a9bcc9d04b fix(deps): update dependency @tabler/icons-react to ^3.41.0 (#5384)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 23:42:53 +00:00
homarr-renovate[bot] 451e68ab57 fix(deps): update trpc monorepo to ^11.15.1 (#5381)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 20:49:58 +00:00
Meier Lukas a7ed7a4592 ci(renovate): update prebuilt-debian lockfile automatically (#5379) 2026-03-31 22:11:08 +02:00
homarr-renovate[bot] 2dd3c906c4 fix(deps): update dependency @immich/sdk to ^2.6.3 (#5380)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 19:30:29 +00:00
Meier Lukas 6a200c39af fix(boards): unable to click on links at settings page (#5230) 2026-03-31 19:08:02 +02:00
homarr-renovate[bot] e88e3d17c0 fix(deps): update dependency @ctrl/deluge to v8 (#5367)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 18:52:38 +02:00
homarr-renovate[bot]andMeier Lukas 46db8b6ab3 fix(deps): update dependency better-sqlite3 to ^12.8.0 (#5266)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-31 18:43:59 +02:00
homarr-renovate[bot] 545dba0c8f chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.3 (#5377)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 16:07:34 +00:00
homarr-renovate[bot] cbb85159d6 chore(deps): update vitest monorepo to ^4.1.2 (#5378)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 16:06:32 +00:00
homarr-renovate[bot] ff07658212 chore(deps): update node.js (#5364)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 11:57:00 +00:00
homarr-renovate[bot] a9582f04ed fix(deps): update dependency zod to ^4.3.6 (#5376)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 21:50:50 +00:00
homarr-renovate[bot]andMeier Lukas 2b4c83dcca fix(deps): update dependency zod to ^4.3.5 - abandoned (#4803)
* fix(deps): update dependency zod to ^4.3.5

* fix: omit can not be used with refine

* fix: convert intersection to object due to trpc-openapi issue

* fix: remove omit after refine

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 23:11:14 +02:00
homarr-renovate[bot] 336c55b3e9 chore(deps): update docker/setup-buildx-action action to v4 (#5200)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:47 +02:00
homarr-renovate[bot] eb5dd3628a chore(deps): update docker/metadata-action action to v6 (#5207)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:37 +02:00
homarr-renovate[bot] c19766e53f chore(deps): update docker/build-push-action action to v7 (#5208)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:28 +02:00
homarr-renovate[bot] cc153fff10 chore(deps): update dependency jsdom to v29 (#5274)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:53:20 +02:00
homarr-renovate[bot] b71718be09 chore(deps): update dependency proxmox-api&gt;undici to v7.24.6 (#5373)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 20:52:12 +00:00
homarr-renovate[bot]andMeier Lukas b07c545f17 fix(deps): update dependency node-ical to ^0.23.1 - abandoned (#4856)
* fix(deps): update dependency node-ical to ^0.23.1

* fix: date from node-ical represents utc value

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 22:43:58 +02:00
homarr-renovate[bot] 4aff9819e5 chore(deps): update actions/create-github-app-token action to v3 (#5267)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:24:09 +02:00
homarr-renovate[bot] 9e0c3c0117 chore(deps): update pnpm to v10.33.0 (#5372)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:23:46 +02:00
homarr-crowdin[bot] 107ec7737a chore(lang): update translations from crowdin (#5350)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-30 22:23:03 +02:00
homarr-renovate[bot] d4177afc18 chore(deps): update pnpm/action-setup action to v5 (#5294)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:22:44 +02:00
Sascha BuehrleandMeier Lukas 7679553f16 fix: honor trusted certificates in Immich integration requests (#5275)
* fix: use trusted-cert fetch for immich sdk requests (fixes #5242)

* fix: use testing fetch for Immich connection check

* fix: typecheck issues

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 22:21:11 +02:00
homarr-renovate[bot] 4f2cc9af2a fix(deps): update dependency undici to v7.24.6 (#5374)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 17:06:11 +00:00
homarr-renovate[bot] 6820f8afea fix(deps): update tiptap monorepo to v3.20.5 (#5371)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 16:43:28 +00:00
homarr-renovate[bot] 4a5bafa790 fix(deps): update dependency @immich/sdk to ^2.6.2 (#5370)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 14:44:39 +00:00
homarr-renovate[bot] 8d1ff4065d fix(deps): update dependency isomorphic-dompurify to ^3.7.1 (#5369)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 10:41:25 +00:00
homarr-renovate[bot] 14d87178b5 fix(deps): update dependency jotai to ^2.19.0 (#5368)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 09:45:23 +00:00
homarr-renovate[bot] c2b54ddfaa fix(deps): update dependency isomorphic-dompurify to ^3.7.0 (#5366)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 09:40:05 +00:00
homarr-renovate[bot] cefbf0e271 fix(deps): update dependency typescript-eslint to ^8.57.2 (#5360)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 07:13:39 +00:00
homarr-renovate[bot] 189806b5a2 fix(deps): update dependency fast-xml-parser to ^5.5.9 (#5359)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 07:12:11 +00:00
homarr-renovate[bot] 9c134c5ceb fix(deps): update dependency @ctrl/deluge to ^7.5.3 (#5365)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 05:17:45 +00:00
homarr-renovate[bot] 682099189b fix(deps): update tanstack-query monorepo to ^5.95.2 (#5357)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 16:41:43 +00:00
homarr-renovate[bot] 32218beb7e chore(deps): update vitest monorepo to ^4.1.1 (#5356)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 15:40:16 +00:00
homarr-renovate[bot] 332b6b4f42 fix(deps): update tanstack-query monorepo to ^5.95.1 (#5355)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 14:39:28 +00:00
homarr-renovate[bot] aca893254c chore(deps): update dependency brace-expansion@&gt;&#x3d;2.0.0 &lt;&#x3d;2.0.1 to >=5.0.5 [security] (#5341)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 14:34:27 +00:00
homarr-renovate[bot] 2fb2981d2f fix(deps): update trpc monorepo to ^11.15.0 (#5354)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:06:24 +00:00
homarr-renovate[bot] 5d84888a33 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.2 (#5353)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:04:53 +00:00
homarr-renovate[bot] 728f7e489a fix(deps): update dependency fastify to ^5.8.4 (#5351)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:04:35 +00:00
homarr-renovate[bot] 0e3ac069d9 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.9 (#5352)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 11:35:24 +00:00
homarr-renovate[bot] 505a6b3a89 chore(deps): update dependency brace-expansion@&gt;&#x3d;1.0.0 &lt;&#x3d;1.1.11 to >=5.0.5 [security] (#5340)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 23:35:56 +00:00
homarr-releases[bot] 7c8adab061 chore: update bug report template (#5349)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-27 23:06:11 +01:00
homarr-renovate[bot] b11bf367d0 fix(deps): update tanstack-query monorepo to ^5.95.0 (#5325)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 21:43:25 +00:00
Releases Homarr 15c2037230 chore(release): 1.57.1 [skip ci] 2026-03-27 21:34:11 +00:00
homarr-releases[bot] 3735c998e4 chore(release): automatic release v1.57.1 2026-03-27 21:32:58 +00:00
Manuel 192a43a031 fix: dashdot gpu (#5347) 2026-03-27 22:32:13 +01:00
homarr-releases[bot] e9c908b317 chore: update bug report template (#5346)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-27 21:45:48 +01:00
homarr-renovate[bot] 49d9394cd7 fix(deps): update dependency @t3-oss/env-nextjs to ^0.13.11 (#5345)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 19:53:16 +00:00
homarr-renovate[bot] 798293813c fix(deps): update dependency fast-xml-parser to ^5.5.8 (#5324)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 19:52:59 +00:00
homarr-renovate[bot] 42a3791e6b fix(deps): update dependency dockerode to ^4.0.10 (#5327)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 19:52:19 +00:00
Releases Homarr 3d8e22574e chore(release): 1.57.0 [skip ci] 2026-03-27 19:41:40 +00:00
homarr-releases[bot] 531d87a83b chore(release): automatic release v1.57.0 2026-03-27 19:40:44 +00:00
homarr-crowdin[bot] 929a803965 chore(lang): update translations from crowdin (#5322)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-27 20:23:28 +01:00
Daniel 003dac4744 feat: add GPU monitoring support via Glances integration (#5255)
* feat: add GPU monitoring support via Glances integration

- Add GPU fields to SystemHealthMonitoring interface (gpuId, name,
  memoryUtilization, processorUtilization, temperature, fanSpeed)
- Extend Glances integration to parse GPU data from /api/4/all endpoint
  (supports NVIDIA, AMD, and Intel GPUs via Glances)
- Add GPU utilization ring to Health Monitoring widget with tooltip
  showing VRAM usage, temperature, and fan speed
- Add GPU time-series chart to System Resources widget (grape color)
- Add GPU toggle option to both widgets
- Add English translations for GPU labels
- Add gpu: [] to all other integrations (dashdot, truenas, unraid,
  openmediavault) for backward compatibility
- Add GPU mock data for development/testing

Closes community request from PR #5102 for GPU monitoring support.

* fix: implement GPU monitoring for dashdot integration and clarify OMV comment

- Add GPU load fetching via dashdot /load/gpu endpoint
- Add GPU info parsing from dashdot /info endpoint
- Add gpuLoadApi and gpu field to internalServerInfoApi schemas
- Add getCurrentGpuLoadAsync method to DashDotIntegration
- Map GPU load data to SystemHealthMonitoring gpu array
- Add explanatory comment for OMV's empty gpu array

Addresses review comments from manuel-rw on PR #5255

* Delete manifest.json
2026-03-27 20:22:41 +01:00
homarr-renovate[bot] d7f0596db8 fix(deps): update trpc monorepo to ^11.14.1 (#5328)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 19:13:22 +00:00
homarr-renovate[bot] dec490365a chore(deps): update dependency proxmox-api&gt;undici to v7.24.5 (#5321)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 19:12:08 +00:00
Matt Van HornandMatt Van Horn 1639da5779 fix: handle null ports in Docker container table (#5313)
When Docker API returns `null` for container ports (instead of `[]`),
the Docker management page crashes. Added optional chaining on
`ports?.length` to handle this case.

Fixes #5311

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-03-27 20:04:03 +01:00
homarr-renovate[bot] d53c922688 fix(deps): update dependency ws to ^8.20.0 (#5337)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:09:40 +00:00
homarr-renovate[bot] 22f5d3b959 fix(deps): update dependency trpc-to-openapi to ^3.2.0 (#5320)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:08:15 +00:00
homarr-renovate[bot] 8d64cb8a5e fix(deps): update dependency isomorphic-dompurify to ^3.6.0 (#5334)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:07:40 +00:00
homarr-renovate[bot] bbd42600aa chore(deps): update dependency @types/pg to ^8.20.0 (#5331)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:05:32 +00:00
homarr-renovate[bot] 8859923bb7 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.60.0 (#5332)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:04:25 +00:00
homarr-renovate[bot] e23a397a80 fix(deps): update nextjs monorepo to v16.2.1 (#5330)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:03:06 +00:00
f88bf9582e feat: add nextcloud notification provider (#5343)
* feat: add Nextcloud notification provider
fix: resolve hydration error in add-board modal

* Update packages/integrations/src/nextcloud/nextcloud.integration.ts

Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>

* Update nextcloud.integration.ts

* defined notificationSchema outside of class

---------

Co-authored-by: Lorinc936 <lorinc1231@gmail.com>
Co-authored-by: Lorinc936 <116025544+Lorinc936@users.noreply.github.com>
2026-03-27 18:51:43 +01:00
homarr-renovate[bot] 22bbef9d8e fix(deps): update dependency undici to v7.24.5 (#5338)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 18:22:06 +01:00
Manuel 4f2b8217f8 fix: warnings in code 2026-03-27 18:12:27 +01:00
Kelian Baert 2fefd9da01 feat: add "allowModals" option for iframe widget (#5339) 2026-03-27 18:05:15 +01:00
Manuel b3e9b3351b Merge commit from fork 2026-03-27 17:50:26 +01:00
homarr-renovate[bot] d4e4c9669d fix(deps): update dependency typescript-eslint to ^8.57.1 (#5286)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-25 02:24:41 +00:00
homarr-renovate[bot] 1db20df19c chore(deps): update dependency proxmox-api&gt;undici to v7.24.0 [security] (#5235)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 23:35:41 +00:00
homarr-renovate[bot] feaf022965 fix(deps): update dependency @immich/sdk to ^2.6.1 (#5319)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 21:47:26 +00:00
homarr-renovate[bot] 5b4e6fcc11 fix(deps): update turbo monorepo to ^2.8.20 (#5317)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 20:52:30 +00:00
homarr-renovate[bot] 20a9b3d556 fix(deps): update dependency @immich/sdk to ^2.6.0 (#5318)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 19:21:16 +00:00
homarr-renovate[bot] 47b2d36f7a fix(deps): update dependency ioredis to v5.10.1 (#5316)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 18:05:01 +00:00
homarr-renovate[bot] 2336f9ee5e fix(deps): update dependency fast-xml-parser to ^5.5.7 (#5315)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 18:04:14 +00:00
homarr-renovate[bot] 6fa6ba385e fix(deps): update dependency @tanstack/react-query to ^5.91.2 (#5314)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 11:02:14 +00:00
homarr-renovate[bot] dadf596a31 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.1 (#5312)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-24 05:00:20 +00:00
homarr-renovate[bot] 7537184a81 fix(deps): update turbo monorepo to ^2.8.19 (#5310)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-23 22:41:28 +00:00
homarr-update-contributors[bot] 5a81d035cf chore: update contributors (#5277)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-03-23 19:11:50 +00:00
homarr-crowdin[bot] b0b6fbd315 chore(lang): updated translations from crowdin (#5284)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-23 20:10:33 +01:00
homarr-renovate[bot] 883161357d fix(deps): update nextjs monorepo to v16.2.0 (#5309)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-23 19:06:43 +00:00
homarr-renovate[bot] 24694b9241 fix(deps): update turbo monorepo to ^2.8.18 (#5308)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-23 18:01:05 +00:00
homarr-renovate[bot] 13b99d021b fix(deps): update dependency @tanstack/react-query to ^5.91.0 (#5305)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-23 15:02:58 +00:00
homarr-renovate[bot] 94dfddf8bd fix(deps): update dependency isomorphic-dompurify to ^3.5.1 (#5300)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 23:35:49 +00:00
homarr-renovate[bot] b6b11c39d2 fix(deps): update dependency isomorphic-dompurify to ^3.5.0 (#5299)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 20:37:26 +00:00
homarr-renovate[bot] 369b4d4863 fix(deps): update tiptap monorepo to v3.20.4 (#5287)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 17:36:05 +00:00
homarr-renovate[bot] 6f8e62963a fix(deps): update mantine monorepo to ^8.3.18 (#5298)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 16:44:42 +00:00
homarr-renovate[bot] 991044ebd7 fix(deps): update dependency fast-xml-parser to ^5.5.6 (#5297)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 16:42:40 +00:00
homarr-renovate[bot] b3f012226b chore(deps): update dependency fast-xml-parser to v5.5.7 [security] (#5295)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 16:34:54 +00:00
homarr-renovate[bot] 63c55cec57 fix(deps): update dependency swagger-ui-react to ^5.32.1 (#5296)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 14:35:44 +00:00
homarr-renovate[bot] c009dd7de6 fix(deps): update testcontainers-node monorepo to ^11.13.0 (#5293)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 10:40:06 +00:00
homarr-renovate[bot] 62764499e7 fix(deps): update dependency isomorphic-dompurify to ^3.4.0 (#5292)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 09:42:54 +00:00
homarr-renovate[bot] 09227a3884 fix(deps): update dependency drizzle-kit to ^0.31.10 (#5291)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 09:41:50 +00:00
homarr-renovate[bot] 37bafaa71a chore(deps): update dependency @types/adm-zip to v0.5.8 (#5288)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 05:00:54 +00:00
homarr-renovate[bot] 237d11d2c0 fix(deps): update dependency @next/eslint-plugin-next to v16.1.7 (#5289)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-22 02:34:01 +00:00
homarr-renovate[bot] 4efe7496a5 chore(deps): update babel monorepo to >=7.29.2 (#5290)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-21 23:37:03 +00:00
homarr-renovate[bot] c3fa4e866c chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.7 (#5282)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-21 15:34:29 +00:00
Meier Lukas 1938798ba1 Merge commit from fork 2026-03-21 16:07:05 +01:00
homarr-renovate[bot] 4fb48c23a2 fix(deps): update dependency mysql2 to v3.20.0 (#5283)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-20 22:37:51 +00:00
homarr-releases[bot] e13afb721e chore: update bug report template (#5281)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-20 21:51:13 +01:00
Releases Homarr 3ed31d8f59 chore(release): 1.56.1 [skip ci] 2026-03-20 19:37:26 +00:00
homarr-releases[bot] 1d5b4fd7fb chore(release): automatic release v1.56.1 2026-03-20 19:36:28 +00:00
homarr-renovate[bot] 55cf3c72a3 fix(deps): update tiptap monorepo to v3.20.2 (#5279)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-20 18:59:21 +00:00
homarr-renovate[bot] 9648d9e99f fix(deps): update trpc monorepo to ^11.13.4 (#5278)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-20 14:49:03 +00:00
homarr-renovate[bot] 0e0b857905 fix(deps): update trpc monorepo to ^11.13.3 (#5276)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-20 11:40:40 +00:00
homarr-renovate[bot] 7d93b10032 fix(deps): update trpc monorepo to ^11.13.2 (#5273)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-20 10:45:12 +00:00
homarr-renovate[bot] 6fa9087a11 fix(deps): update mantine monorepo to ^8.3.17 (#5271)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-19 16:04:34 +00:00
homarr-renovate[bot] d2f438f294 fix(deps): update turbo monorepo to ^2.8.17 (#5265)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-19 15:01:48 +00:00
homarr-renovate[bot] 2423ecdce5 fix(deps): update dependency fast-xml-parser to ^5.5.5 (#5270)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-19 15:01:24 +00:00
homarr-renovate[bot] 7094110da2 chore(deps): update dependency fast-xml-parser to v5.5.6 [security] (#5269)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-19 14:52:36 +00:00
homarr-renovate[bot] 72ec253644 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.8 (#5268)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-19 09:51:42 +00:00
homarr-renovate[bot] 3bbde3cbb8 fix(deps): update dependency next to v16.1.7 [security] (#5263)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 21:41:38 +00:00
homarr-renovate[bot] 6f85b2161f chore(deps): update vitest monorepo to ^4.1.0 (#5259)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 19:27:32 +00:00
homarr-renovate[bot] c85910d24f chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to v8 (#5261)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 19:55:32 +01:00
homarr-renovate[bot] 397c1d3dbc chore(deps): update dependency esbuild to ^0.27.4 (#5256)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 19:53:21 +01:00
homarr-renovate[bot] 078fb6de93 chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.27.4 (#5257)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 19:53:15 +01:00
homarr-crowdin[bot] 8159b4f146 chore(lang): update translations from crowdin (#5248)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-17 19:49:40 +01:00
homarr-renovate[bot] e8f6876d24 fix(deps): update dependency isomorphic-dompurify to ^3.3.0 (#5252)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 16:22:11 +00:00
homarr-renovate[bot] 615062c6b1 fix(deps): update dependency dayjs to ^1.11.20 (#5253)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 16:20:32 +00:00
homarr-renovate[bot] d9294ed4bf chore(deps): update dependency @vitejs/plugin-react to ^5.2.0 (#5258)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-17 15:06:30 +00:00
homarr-renovate[bot] d1d8659f95 fix(deps): update dependency fast-xml-parser to ^5.5.3 (#5251)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-16 17:09:31 +00:00
homarr-renovate[bot] ac80008ce0 fix(deps): update turbo monorepo to ^2.8.16 (#5250)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-16 02:42:50 +00:00
homarr-renovate[bot] 13853f2981 chore(deps): update pnpm to v10.32.1 (#5249)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-16 02:41:02 +00:00
homarr-renovate[bot] f995cba157 fix(deps): update dependency sass to ^1.98.0 (#5247)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-15 23:38:23 +00:00
homarr-renovate[bot] 91a9b6bec9 fix(deps): update dependency fast-xml-parser to ^5.5.1 (#5246)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-15 21:37:58 +00:00
homarr-crowdin[bot]andMeier Lukas 314ab3a28e chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-15 21:55:12 +01:00
homarr-renovate[bot] 7841b58f0f fix(deps): update dependency typescript-eslint to ^8.57.0 (#5241)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 23:37:16 +00:00
homarr-renovate[bot] 63f91b6352 fix(deps): update dependency isomorphic-dompurify to ^3.1.0 (#5239)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 23:36:12 +00:00
homarr-renovate[bot] 4fd8ac9118 chore(deps): update pnpm to v10.32.0 (#5244)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 22:36:25 +00:00
homarr-renovate[bot] 1bd924ea99 fix(deps): update turbo monorepo to ^2.8.15 (#5243)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 22:35:44 +00:00
homarr-renovate[bot] 1040ecac04 fix(deps): update dependency mysql2 to v3.19.1 (#5240)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 16:41:47 +00:00
homarr-renovate[bot] 729b49ffa9 fix(deps): update dependency jotai to ^2.18.1 (#5238)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-14 15:35:15 +00:00
homarr-crowdin[bot]andMeier Lukas dbf5b375f5 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-14 12:58:16 +00:00
Meier Lukas 7e8723ce57 chore(docs): remove temporary ts-expect-error for documentation-url 2026-03-14 13:53:07 +01:00
homarr-releases[bot] c5e83943a4 chore: update bug report template (#5234)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-14 08:54:18 +01:00
Releases Homarr d3424ec71b chore(release): 1.56.0 [skip ci] 2026-03-13 19:31:03 +00:00
homarr-releases[bot] 9bfcb04aa2 chore(release): automatic release v1.56.0 2026-03-13 19:30:11 +00:00
homarr-update-contributors[bot] ec24fe22f9 chore: update integrations list readme (#5232)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-03-13 19:35:57 +01:00
StrandedTurtle f68600150a feat: Tracearr integration (#5060) 2026-03-13 19:19:43 +01:00
Meier Lukas d3c2ad134f fix(releases): allow any icon url for repositories (#5229) 2026-03-13 17:49:30 +01:00
homarr-renovate[bot] 25f3b074ee chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.7 [security] (#5211)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-13 16:43:07 +00:00
Meier Lukas ba9c3f3e27 fix(boards): duplication of boards fails with postgresql / mysql (#5228) 2026-03-13 16:32:48 +01:00
homarr-update-contributors[bot] 3daadec358 chore: update contributors (#5227)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-03-13 13:10:58 +00:00
Meier Lukas 21225c979e fix: language changes when searching for some icons (#5224) 2026-03-13 14:09:51 +01:00
Meier Lukas 3091ed6358 fix(glances): unable to parse uptime of 1 day (#5226) 2026-03-13 14:09:42 +01:00
Meier Lukas 451602d1e6 fix(true-nas): ignore offline/exported pools (#5225) 2026-03-13 14:09:30 +01:00
homarr-crowdin[bot] 745121c4b4 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-13 08:58:55 +00:00
homarr-renovate[bot] 0bd3a8728a chore(deps): update pnpm to v10.31.0 (#5223)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-13 02:16:18 +00:00
homarr-renovate[bot] 8ee05e0d49 fix(deps): update turbo monorepo to ^2.8.14 (#5113)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-12 19:50:05 +00:00
homarr-renovate[bot]andMeier Lukas 5c4f394501 fix(deps): update dependency fastify to ^5.8.2 (#5221)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-12 20:33:57 +01:00
Sergio 85c460190c fix(system-disks): format absolute denominator with human-readable size (#5183) 2026-03-12 20:15:11 +01:00
Meier Lukas 7c10becc74 chore: reduce flakiness in e2e tests (#5217) 2026-03-12 20:03:54 +01:00
Meier Lukas 5723da21aa fix(security): harden hashing in image-proxy (#5188) 2026-03-12 20:03:08 +01:00
homarr-renovate[bot] a9085399c0 chore(deps): update dependency eslint to ^9.39.4 (#5218)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-12 07:57:01 +00:00
ManuelandMeier Lukas aa36ce3c47 feat: expand cli (#5159)
* feat: expand cli

* fix: lint issue

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-12 08:47:08 +01:00
homarr-renovate[bot] 12ad241406 fix(deps): update dependency @tabler/icons-react to ^3.40.0 (#5220)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-12 05:52:37 +00:00
homarr-renovate[bot] 22fd8e9b95 fix(deps): update dependency @tabler/icons-react to ^3.39.0 (#5209)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 20:46:03 +00:00
homarr-renovate[bot] c12f1bf2b3 chore(deps): update dependency @types/node to ^24.12.0 (#5212)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 20:43:35 +00:00
homarr-renovate[bot] 1c6e23a4b6 fix(deps): update dependency pg to ^8.20.0 (#5198)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 19:55:40 +00:00
homarr-renovate[bot] c7fa5cdbb3 fix(deps): update mantine monorepo to ^8.3.16 (#5201)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 19:53:29 +00:00
homarr-crowdin[bot] bece83eb8b chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-11 16:58:05 +00:00
homarr-renovate[bot] 00dd32bbe1 fix(deps): update trpc monorepo to ^11.12.0 (#5213)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 14:59:23 +00:00
homarr-renovate[bot] d941ed58fa fix(deps): update tiptap monorepo to v3.20.1 (#5215)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-11 13:10:26 +00:00
homarr-renovate[bot] 324bfd90d1 fix(deps): update dependency mysql2 to v3.19.0 (#5206)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-10 19:10:33 +00:00
homarr-renovate[bot] b02e8cc875 fix(deps): update dependency fastify to ^5.8.1 (#5202)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-10 19:08:25 +00:00
homarr-renovate[bot] 7b207cc83b chore(deps): update dependency tar-fs@&gt;&#x3d;2.0.0 &lt;2.1.3 to >=3.1.2 (#5196)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-10 19:07:20 +00:00
homarr-crowdin[bot] 4b8f8a1785 chore(lang): update translations from crowdin (#5193)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-10 20:02:47 +01:00
Sergio bbf0adf31a fix(stock-price): align sparkline color with displayed price change (#5181) 2026-03-10 20:01:53 +01:00
homarr-renovate[bot] 8b79e54245 chore(deps): update dependency prettier-plugin-packagejson to ^3.0.2 (#5204)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-10 13:10:34 +00:00
homarr-renovate[bot] 2c9b47bc0b chore(deps): update dependency tar-fs@&gt;&#x3d;3.0.0 &lt;3.0.9 to >=3.1.2 (#5197)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-09 23:36:13 +00:00
homarr-renovate[bot] 217a9fe864 chore(deps): update dependency conventional-changelog-conventionalcommits to ^9.3.0 (#5195)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-09 22:38:39 +00:00
homarr-renovate[bot] 4a2cfcd8b5 chore(deps): update dependency fastify to v5.8.1 [security] (#5191)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-08 22:27:31 +00:00
homarr-renovate[bot] a682cc430b chore(deps): update docker/login-action action to v4 (#5192)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-08 21:40:47 +01:00
Meier Lukas dabe96bd5e feat(boards): show supported integrations in widget selection (#5187)
* feat(boards): show supported integrations in widget selection

* fix: place supported integrations directly above add button
2026-03-08 19:05:23 +01:00
homarr-crowdin[bot] 04e7a202d0 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-08 17:42:03 +00:00
homarr-renovate[bot] 5159d9f0e7 fix(deps): update dependency fast-xml-parser to ^5.4.2 (#5190)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-08 17:40:22 +00:00
homarr-renovate[bot] 3778338e34 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.5 [security] (#5171)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-07 11:22:54 +00:00
SergioandMeier Lukas 9a878af414 fix(image-proxy): hash full URL and headers deterministically (#5184)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-07 10:38:47 +01:00
homarr-update-contributors[bot] 4eece0d607 chore: update integrations list readme (#5186)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-03-07 08:51:42 +00:00
Manuel cdd90b8aa0 feat: add immich integration and widget (#5165)
* feat: add immich integration and widget

* feat: use sdk
2026-03-07 09:39:31 +01:00
homarr-renovate[bot] 6aaaf31d8b fix(deps): update dependency @tabler/icons-react to ^3.38.0 (#5182)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-06 22:39:41 +00:00
homarr-renovate[bot] df08046cdf chore(deps): update dependency conventional-changelog-conventionalcommits to ^9.2.0 (#5179)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-06 22:37:37 +00:00
Meier Lukas 340399597b chore(docs): update sitemap 2026-03-06 23:30:00 +01:00
homarr-releases[bot] 671e5621ad chore: update bug report template (#5177)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-06 20:52:42 +01:00
Releases Homarr 4da8734ff3 chore(release): 1.55.0 [skip ci] 2026-03-06 19:35:05 +00:00
homarr-releases[bot] 9807afcec7 chore(release): automatic release v1.55.0 2026-03-06 19:33:44 +00:00
homarr-renovate[bot] c2f2f96bc7 chore(deps): update dependency @semantic-release/npm to ^13.1.5 (#5175)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-06 16:50:27 +00:00
homarr-renovate[bot] bc2a532a03 fix(deps): update trpc monorepo to ^11.11.0 (#5174)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-06 12:07:35 +00:00
homarr-renovate[bot] 905b7954ca chore(deps): update dependency brace-expansion@&gt;&#x3d;2.0.0 &lt;&#x3d;2.0.1 to >=5.0.4 (#5166)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-05 17:25:57 +00:00
homarr-renovate[bot] e248db4508 chore(deps): update peter-evans/create-pull-request action to v8 (#5053)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-05 17:29:49 +01:00
homarr-renovate[bot] 2893152377 chore(deps): update dependency @types/pg to ^8.18.0 (#5173)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-05 16:28:46 +00:00
homarr-renovate[bot] 751fb35f90 chore(deps): update dependency @types/node to ^24.11.0 (#5172)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-05 16:27:57 +00:00
homarr-renovate[bot] 58a90d3fc5 chore(deps): update dependency brace-expansion@&gt;&#x3d;1.0.0 &lt;&#x3d;1.1.11 to v5 (#5074)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 20:41:55 +01:00
homarr-renovate[bot] ff1159943c fix(deps): update dependency swagger-ui-react to ^5.32.0 (#5167)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 19:35:24 +00:00
homarr-renovate[bot] 663789c891 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.13.6 (#5170)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 19:34:20 +00:00
homarr-renovate[bot] 91015484bd chore(deps): update dependency @types/node to ^24.10.15 (#5163)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 19:32:02 +00:00
homarr-renovate[bot] ec45aacc63 fix(deps): update dependency ldapts to v8.1.7 (#5168)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 19:29:45 +00:00
homarr-renovate[bot] 1c06e562c3 fix(deps): update dependency ioredis to v5.10.0 (#5169)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-04 19:28:45 +00:00
homarr-renovate[bot] 4717a5724d fix(deps): update dependency mysql2 to v3.18.2 (#5164)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-03 22:38:04 +00:00
homarr-update-contributors[bot]andMeier Lukas 8f86944014 chore: update integrations list readme (#5162)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-03 22:41:25 +01:00
ManuelandMeier Lukas 0e29a2fce1 feat: add seerr integration (#5139)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-03 19:53:50 +01:00
homarr-crowdin[bot] 249a9264e6 chore(lang): update translations from crowdin (#5144)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-03 19:27:41 +01:00
homarr-renovate[bot] 4763908b5d chore(deps): update dependency @types/node to ^24.10.14 (#5155)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-03 13:07:37 +00:00
homarr-renovate[bot] 8bf2a43e06 fix(deps): update dependency mysql2 to v3.18.1 (#5152)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-03 11:40:38 +00:00
homarr-renovate[bot] bcb626600b chore(deps): update pnpm to v10.30.3 (#5157)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-03 10:47:47 +00:00
homarr-renovate[bot] be543dadb6 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.3 [security] (#5095)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 22:29:59 +00:00
homarr-renovate[bot] eb2f85f6f8 fix(deps): update dependency fast-xml-parser to ^5.4.1 (#5150)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 20:49:19 +00:00
homarr-renovate[bot] a10807a6c4 chore(deps): update node.js to v24.14.0 (#5151)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 19:47:26 +00:00
homarr-renovate[bot] 3df52f4013 chore(deps): update pnpm to v10.30.2 (#5146)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 18:59:12 +00:00
homarr-renovate[bot] 0b0fb1c579 fix(deps): update dependency pg to ^8.19.0 (#5153)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 17:54:46 +00:00
homarr-renovate[bot] 5971af91a8 chore(deps): update node.js (#5147)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-02 06:00:09 +00:00
homarr-renovate[bot] ac3b4e7de0 fix(deps): update dependency typescript-eslint to ^8.56.1 (#5142)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-02-28 18:42:59 +00:00
homarr-renovate[bot] 52792af186 fix(deps): update dependency mysql2 to v3.18.0 (#5141)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-02-28 17:34:47 +00:00
homarr-renovate[bot] 66ad67eced fix(deps): update dependency mysql2 to v3.17.5 (#5140)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-02-28 09:35:09 +00:00
660 changed files with 68132 additions and 19799 deletions

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
---
description: Comprehensive Homarr codebase architecture and conventions reference
alwaysApply: true
---
# Homarr Codebase Context
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
## Tech Stack
- **Monorepo**: pnpm workspaces + Turborepo
- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack
- **Language**: TypeScript throughout
- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi`
- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
- **UI**: Mantine v7 (not Tailwind) + Tabler icons
- **State**: Jotai (atoms), TanStack Query (server state via tRPC)
- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub
- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`)
- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie)
- **Testing**: Vitest + jsdom, Playwright for E2E
- **Package manager**: pnpm 10.33.2, Node >= 24.15.0
## Repository Structure
```
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
├── packages/
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
│ ├── auth/ # NextAuth config, providers, session, API keys
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
│ ├── core/ # Env validation, DB/Redis driver factories, logging
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
│ ├── redis/ # Redis pub/sub channels, caching abstractions
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
│ ├── ui/ # Shared Mantine components, theme, hooks
│ ├── validation/ # Shared zod schemas for API/forms
│ ├── common/ # Shared utilities, IDs, errors
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
│ ├── cron-jobs-core/ # Cron scheduling primitives
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
│ ├── cron-job-status/ # Cron status via Redis
│ ├── boards/ # Board context, edit mode, cache updater
│ ├── modals/ # Modal primitives on Mantine
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
│ ├── form/ # useZodForm (Mantine + zod resolver)
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
│ ├── spotlight/ # Command palette / search with multiple modes
│ ├── request-handler/ # Server request handlers (feeds, integrations)
│ ├── notifications/ # Mantine notifications wrapper
│ ├── docker/ # Dockerode-based Docker access
│ ├── icons/ # Icon DB/repo integration
│ ├── image-proxy/ # Image proxy + caching
│ ├── ping/ # Reachability / ping utilities
│ ├── analytics/ # Server-side analytics (Umami)
│ ├── server-settings/ # Server setting keys/types
│ ├── settings/ # User-facing settings UI context
│ ├── old-import/ # Legacy Homarr import
│ ├── old-schema/ # Legacy Homarr zod schemas
│ └── cli/ # Node CLI for ops (brocli)
├── tooling/
│ ├── eslint/ # Shared ESLint configs
│ ├── prettier/ # Shared Prettier config
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
```
## Three Runtime Services
| Service | Port | Role |
|---------|------|------|
| Next.js | 3000 | Main web app (SSR + API routes) |
| WebSocket | 3001 | tRPC subscriptions via `ws` |
| Tasks | 3002 | Cron job runner + management API via Fastify |
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
## Package Dependency Layers
1. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps
2. **Domain**: `definitions` → `common` → `core`
3. **Data**: `db` → `core`, `definitions`, `common`
4. **Auth**: `auth` → `db`, `definitions`, `core`, `validation`
5. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings`
6. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping`
7. **API surface**: `api` → pulls in auth, db, redis, integrations, etc.
8. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight`
9. **Composed features**: `widgets`, `modals-collection`, `forms-collection`
## Key Patterns
### tRPC
- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context
- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks
- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.)
- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure`
- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001
### Multi-Database Support
Drizzle schemas exist in three parallel implementations:
- `packages/db/schema/sqlite.ts`
- `packages/db/schema/mysql.ts`
- `packages/db/schema/postgresql.ts`
Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`.
### Widget System
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
- Loads dynamically via `next/dynamic`
- Fetches data via tRPC client hooks (backed by cron job caches)
### Integration System
Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods.
### Cron Job System
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
- Uses `createCronJob` with a cron expression
- Publishes results to Redis channels
- Widget subscriptions consume those channels via tRPC subscriptions
- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002
### Modals
Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`.
### Forms
`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`.
### Routing (App Router)
- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment
- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs
- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]`
- Locale driven by cookie (no URL prefix)
### Auth Providers
Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.
## Environment Variables
Key env vars (from `.env.example`):
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
- `DB_URL`: Database connection string
- `AUTH_SECRET`: NextAuth secret
- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets
- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`)
- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service
- `REDIS_*`: Redis connection (host, port, password)
- `ENABLE_KUBERNETES`: Optional Kubernetes integration
## Development Commands
- `pnpm dev` — runs all three services in parallel via Turborepo
- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers
- `pnpm db:push` — push schema to SQLite (dev)
- `pnpm db:studio` — Drizzle Studio
- `pnpm test` — Vitest unit tests
- `pnpm test:e2e` — Playwright E2E
- `pnpm lint` / `pnpm format` — ESLint + Prettier
- `pnpm typecheck` — tsc --noEmit across all packages
## Conventions
- Workspace packages use `@homarr/` scope
- All versions managed via pnpm `catalog:` in workspace
- Path alias `~/*` → `./src/*` in Next.js app
- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true`
- Icons from `@tabler/icons-react`
- Drag-and-drop via `@dnd-kit/*`
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
+3 -6
View File
@@ -3,9 +3,6 @@ version = 1
[[analyzers]]
name = "javascript"
[analyzers.meta]
plugins = ["react"]
environment = ["nodejs"]
[[transformers]]
name = "prettier"
[analyzers.meta]
plugins = ["react"]
environment = ["nodejs"]
+5 -1
View File
@@ -9,4 +9,8 @@ README.md
.git
dev
.build
e2e
e2e
apps/nextjs/.next
.env
.env.*
!.env.example
+14 -4
View File
@@ -12,10 +12,6 @@ AUTH_SECRET="supersecret"
# or starting the project without any (which will show a randomly generated one).
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# Cron job API key is generated every time the container starts as it is required for communication between nextjs-api and tasks-api
# export CRON_JOB_API_KEY=$(openssl rand -base64 32)
# CRON_JOB_API_KEY="your-generated-api-key"
LOG_LEVEL='info'
# This is how you can use the sqlite driver:
@@ -45,6 +41,20 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
TURBO_TELEMETRY_DISABLED=1
# Turborepo remote cache (optional, for faster CI/local builds)
# Uncomment and fill in to enable remote caching locally.
# CI uses GitHub secrets for these values automatically.
# TURBO_API=https://your-cache-worker.workers.dev
# TURBO_TEAM=team_your_team
# TURBO_TOKEN=your-turbo-token
# TURBO_REMOTE_CACHE_SIGNATURE_KEY=your-signature-key
# Comma-separated paths to Docker-compatible Unix sockets.
# Useful for connecting to multiple runtimes (Docker + Podman) simultaneously.
# When unset and no TCP hosts configured, defaults to /var/run/docker.sock.
# Example: DOCKER_SOCKET_PATHS=/var/run/docker.sock,/run/user/1000/podman/podman.sock
# DOCKER_SOCKET_PATHS=
# Enable kubernetes tool
# ENABLE_KUBERNETES=true
+16
View File
@@ -33,6 +33,22 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.64.0
- 1.62.0
- 1.61.0
- 1.60.1-beta.1
- 1.60.0
- 1.59.3
- 1.59.2
- 1.59.1
- 1.59.0
- 1.58.1
- 1.58.0
- 1.57.1
- 1.57.0
- 1.56.1
- 1.56.0
- 1.55.0
- 1.54.0
- 1.53.0
- 1.52.0
+5 -5
View File
@@ -25,16 +25,16 @@ body:
label: Public API available?
description: Is there a public API available, that we can consume in Homarr?
options:
- Yes, available on a website
- Yes, available in the application itself
- No
- "Yes, available on a website"
- "Yes, available in the application itself"
- "No"
validations:
required: true
- type: dropdown
attributes:
label: Are you willing to contribute this yourself?
options:
- Yes
- No
- "Yes"
- "No"
validations:
required: true
+2 -2
View File
@@ -24,7 +24,7 @@ body:
attributes:
label: Are you willing to contribute this yourself?
options:
- Yes
- No
- "Yes"
- "No"
validations:
required: true
+2 -2
View File
@@ -15,11 +15,11 @@ runs:
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Build docker image for ${{ inputs.architecture }}
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
push: false
load: true
+12
View File
@@ -11,6 +11,12 @@
matchPackageNames: ["tsx"],
enabled: false,
},
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
{
matchPackageNames: ["undici"],
matchNewValue: "8.*",
enabled: false,
},
{
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
automerge: true,
@@ -31,6 +37,12 @@
automerge: false,
},
],
// Updates lockfile for prebuilt-debian after if package.json is updated, otherwise only root lockfile is updated
postUpgradeTasks: {
commands: ["cd deployments/prebuilt-debian && pnpm install --ignore-workspace --lockfile-only"],
fileFilters: ["deployments/prebuilt-debian/pnpm-lock.yaml"],
executionMode: "update",
},
updateInternalDeps: true,
rangeStrategy: "bump",
automerge: false,
+8 -3
View File
@@ -9,16 +9,21 @@ jobs:
approve-automatic-prs:
runs-on: ubuntu-latest
timeout-minutes: 2
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
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175486441: homarr-releases[bot]
# Extracted from https://api.github.com/users/<username>
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to approve pull request
- name: Install GitHub CLI
+57 -10
View File
@@ -11,21 +11,58 @@ on:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
# You can leverage Vercel Remote Caching with Turbo to speed up your builds
# @link https://turborepo.org/docs/core-concepts/remote-caching#remote-caching-on-vercel-builds
env:
FORCE_COLOR: 3
SKIP_ENV_VALIDATION: true
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD~1' }}
TURBO_SCM_HEAD: ${{ github.sha }}
jobs:
lint-format-autofix:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Setup
uses: ./tooling/github/setup
- name: Get changed files
id: changed
run: |
git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
- name: Auto-fix lint + format
if: steps.changed.outputs.count != '0'
run: |
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
- name: Commit fixes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "style: auto-fix lint and format"
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
@@ -35,31 +72,35 @@ jobs:
run: cp .env.example .env
- name: Lint
run: pnpm lint && pnpm lint:ws
run: pnpm turbo lint --affected && pnpm lint:ws
format:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Format
run: pnpm format
run: pnpm turbo format --affected --continue
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Typecheck
run: turbo typecheck
run: pnpm turbo typecheck --affected
test:
runs-on: ubuntu-latest
@@ -82,20 +123,24 @@ jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Setup
uses: ./tooling/github/setup
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build docker image
id: build-docker-image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
platforms: linux/amd64
context: .
push: false
load: true
tags: homarr-e2e
network: host
cache-from: type=gha
cache-to: type=gha,mode=max
env:
SKIP_ENV_VALIDATION: true
- name: Install playwright browsers
@@ -109,10 +154,12 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Copy env
shell: bash
run: cp .env.example .env
- name: Build
run: pnpm build
run: pnpm turbo build --affected
@@ -19,10 +19,10 @@ jobs:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
app-id: ${{ secrets.CROWDIN_APP_ID }}
client-id: ${{ secrets.CROWDIN_APP_ID }}
permission-contents: write # required to commit to crowdin branch
permission-pull-requests: write # required to create pull request
@@ -0,0 +1,104 @@
name: "[Deployment] Beta Release"
on:
workflow_dispatch:
inputs:
send-notifications:
type: boolean
required: false
default: true
description: Send notifications
permissions:
contents: write
pull-requests: write
jobs:
create-and-merge-pr:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get Next Version
id: semver
uses: ietf-tools/semver-action@v1
with:
token: ${{ github.token }}
branch: next
- name: Create pull request
id: create-pull-request
run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge'
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }}-beta (new behaviour: ${{ steps.semver.outputs.bump }})"
- name: Obtain token
id: obtainApprovalToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write
- name: Approve PR
env:
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
run: |
gh pr review --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # write to beta branch (due to merge)
permission-pull-requests: write # merge pull request
- id: automerge
if: ${{ steps.semver.outputs.bump != 'major' }}
name: automerge
uses: "pascalgn/automerge-action@v0.16.4"
env:
GITHUB_TOKEN: ${{ steps.obtainMergeToken.outputs.token }}
MERGE_METHOD: merge # we prefer merge commits for merging to beta
MERGE_COMMIT_MESSAGE: "chore(release): release ${{ steps.semver.outputs.next }}-beta"
MERGE_DELETE_BRANCH: false # never set to true!
PULL_REQUEST: "${{ steps.create-pull-request.outputs.pr_number }}"
MERGE_RETRIES: 20 # 20 retries * MERGE_RETRY_SLEEP until step fails
MERGE_RETRY_SLEEP: 10000 # 10 seconds * MERGE_RETRIES until step fails
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
- name: Merged Discord notification
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Major Bump Discord notification
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Discord Fail Notification
if: failure() && github.event.inputs.send-notifications
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The beta release workflow [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) has failed"
+28 -26
View File
@@ -5,6 +5,7 @@ on:
branches:
- main
- beta
- next
- dev
workflow_dispatch:
inputs:
@@ -34,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' }}
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' || github.ref_name == 'next' }}
outputs:
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }}
@@ -50,23 +51,23 @@ jobs:
- name: Obtain token
if: env.SKIP_RELEASE == 'false'
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit package.json & changelog changes, merge them to dev and publish the release
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
- uses: actions/checkout@v6
if: env.SKIP_RELEASE == 'false'
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
if: env.SKIP_RELEASE == 'false'
- uses: actions/setup-node@v6
if: env.SKIP_RELEASE == 'false'
with:
node-version: 24.13.1
cache: "pnpm"
node-version: 24.16.0
package-manager-cache: false
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
- name: Install dependencies
@@ -95,20 +96,21 @@ jobs:
id: read-git-ref
run: |
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Update dev branch
- name: Update dev / next branch
if: env.SKIP_RELEASE == 'false'
continue-on-error: true # Prevent pipeline from failing when merge fails
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
UPSTREAM_BRANCH: ${{ github.ref_name == 'main' && 'dev' || 'next' }}
run: |
git config user.name "Releases Homarr"
git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ steps.obtainToken.outputs.token }}@github.com/${{ github.repository }}.git
git fetch origin dev
git checkout dev
git pull origin dev
git fetch origin ${{ env.UPSTREAM_BRANCH }}
git checkout ${{ env.UPSTREAM_BRANCH }}
git pull origin ${{ env.UPSTREAM_BRANCH }}
git rebase ${{ github.ref_name }}
git push origin dev
git push origin ${{ env.UPSTREAM_BRANCH }}
build-amd64:
name: Build docker image for amd64
needs: release
@@ -123,23 +125,23 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: "${{ env.GHCR_REPO }}"
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
network: host
@@ -162,23 +164,23 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: "${{ env.GHCR_REPO }}"
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
network: host
@@ -191,7 +193,7 @@ jobs:
name: Extract amd64 asset from docker image
needs: [release, build-amd64]
runs-on: ubuntu-latest
timeout-minutes: 2
timeout-minutes: 5
steps:
- name: Extract amd64
if: needs.release.outputs.skipped == 'false'
@@ -207,7 +209,7 @@ jobs:
name: Extract arm64 asset from docker image
needs: [release, build-arm64]
runs-on: ubuntu-24.04-arm
timeout-minutes: 2
timeout-minutes: 5
steps:
- name: Extract arm64
if: needs.release.outputs.skipped == 'false'
@@ -231,14 +233,14 @@ jobs:
- name: Obtain token
if: needs.release.outputs.skipped == 'false'
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to publish the release
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -274,6 +276,6 @@ jobs:
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
+16 -15
View File
@@ -21,10 +21,10 @@ jobs:
timeout-minutes: 2
steps:
- name: Discord notification
if: ${{ github.events.inputs.send-notifications }}
if: ${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name: Checkout repository
@@ -38,22 +38,23 @@ jobs:
token: ${{ github.token }}
branch: dev
- name: Create pull request
id: create-pull-request
run: 'gh pr create --title "chore(release): automatic release ${{ steps.semver.outputs.next }}" --body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" --base main --head dev --label automerge'
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Discord notification
if: ${{ github.events.inputs.send-notifications }}
if: ${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
- name: Obtain token
id: obtainApprovalToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write
- name: Approve PR
env:
@@ -62,10 +63,10 @@ jobs:
gh pr review --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # write to main branch (due to merge)
permission-pull-requests: write # merge pull request
- id: automerge
@@ -74,7 +75,7 @@ jobs:
uses: "pascalgn/automerge-action@v0.16.4"
env:
GITHUB_TOKEN: ${{ steps.obtainMergeToken.outputs.token }}
MERGE_METHOD: merge # we prefer merge commits for merging to master
MERGE_METHOD: merge # we prefer merge commits for merging to main
MERGE_COMMIT_MESSAGE: "chore(release): automatic release ${{ steps.semver.outputs.next }}"
MERGE_DELETE_BRANCH: false # never set to true!
PULL_REQUEST: "${{ steps.create-pull-request.outputs.pr_number }}"
@@ -83,23 +84,23 @@ jobs:
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
- name: Merged Discord notification
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.events.inputs.send-notifications }}
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
- name: Major Bump Discord notification
if: ${{ steps.semver.outputs.bump == 'major' && github.events.inputs.send-notifications }}
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
- name: Discord Fail Notification
if: failure() && github.events.inputs.send-notifications
if: failure() && github.event.inputs.send-notifications
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
uses: Ilshidur/action-discord@0.4.0
with:
args: "The automatic release workflow [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) has failed"
+5 -5
View File
@@ -12,10 +12,10 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
client-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
owner: homarr-labs
repositories: |
documentation
@@ -45,10 +45,10 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
@@ -64,7 +64,7 @@ jobs:
- name: Create Pull Request
id: create-pull-request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
commit-message: "chore: update bug report template"
@@ -7,7 +7,7 @@ on:
jobs:
skip-stability-days:
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
if: ${{ !startsWith(github.head_ref, 'renovate/') && github.event.pull_request.head.repo.full_name == github.repository }}
name: Skip Stability Days
runs-on: ubuntu-latest
timeout-minutes: 1
+3 -3
View File
@@ -21,10 +21,10 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
@@ -33,7 +33,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
@@ -21,10 +21,10 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
+5 -1
View File
@@ -67,4 +67,8 @@ e2e/shared/tmp
apps/nextjs/public/images/background.png
# next-intl
en.d.json.ts
en.d.json.ts
# personal agent config
AGENTS.md
.opencode
+1 -1
View File
@@ -1 +1 @@
24.13.1
24.16.0
+7
View File
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 120,
"endOfLine": "lf",
"sortPackageJson": true,
"ignorePatterns": ["**/*.cjs", "**/lang/**", "static-data/**", "CHANGELOG.md"]
}
+37
View File
@@ -0,0 +1,37 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "import", "jsx-a11y"],
"categories": {
"correctness": "error",
"suspicious": "warn",
"pedantic": "off",
"perf": "off",
"style": "off",
"restriction": "off",
"nursery": "off"
},
"rules": {
"eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
"typescript/consistent-type-imports": ["warn", { "prefer": "type-imports", "fixStyle": "separate-type-imports" }],
"typescript/no-non-null-assertion": "error",
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"unicorn/no-new-array": "warn",
"unicorn/no-useless-spread": "warn",
"unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-fallback-in-spread": "warn",
"jsx-a11y/prefer-tag-over-role": "warn",
"oxc/const-comparisons": "warn",
"react-hooks/exhaustive-deps": "warn",
"react/no-children-prop": "warn",
"jsx-a11y/no-autofocus": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"react/react-in-jsx-scope": "off"
},
"options": {
"reportUnusedDisableDirectives": "off"
},
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", "**/lang/*.d.json.ts", ".next/**"],
"settings": {
"react": { "version": "19" }
}
}
+1 -2
View File
@@ -31,6 +31,5 @@ scopeRangeRegex: "(?:const|let|var)\\s+t\\s*=\\s*(?:await\\s+)?(?:getScopedI18n|
# refactorTemplates:
# - i18n.get("$1")
# If set to true, only enables this custom framework (will disable all built-in frameworks)
monopoly: true
monopoly: true
+5 -15
View File
@@ -15,9 +15,7 @@
"request": "launch",
"command": "pnpm docker:dev",
"cwd": "${workspaceFolder}",
"skipFiles": [
"<node_internals>/**"
]
"skipFiles": ["<node_internals>/**"]
},
{
"name": "lint fix",
@@ -25,9 +23,7 @@
"request": "launch",
"command": "pnpm lint:fix",
"cwd": "${workspaceFolder}",
"skipFiles": [
"<node_internals>/**"
]
"skipFiles": ["<node_internals>/**"]
},
{
"name": "format fix",
@@ -35,9 +31,7 @@
"request": "launch",
"command": "pnpm format:fix",
"cwd": "${workspaceFolder}",
"skipFiles": [
"<node_internals>/**"
]
"skipFiles": ["<node_internals>/**"]
},
{
"name": "db push",
@@ -69,9 +63,7 @@
"request": "launch",
"command": "pnpm test",
"cwd": "${workspaceFolder}",
"skipFiles": [
"<node_internals>/**"
]
"skipFiles": ["<node_internals>/**"]
},
{
"name": "test ui",
@@ -79,9 +71,7 @@
"request": "launch",
"command": "pnpm test:ui",
"cwd": "${workspaceFolder}",
"skipFiles": [
"<node_internals>/**"
]
"skipFiles": ["<node_internals>/**"]
}
]
}
+3 -14
View File
@@ -1,13 +1,6 @@
{
"eslint.workingDirectories": [
{
"mode": "auto"
}
],
"eslint.experimental.useFlatConfig": true,
"typescript.tsdk": "node_modules\\typescript\\lib",
"js/ts.implicitProjectConfig.experimentalDecorators": true,
"prettier.configPath": "./tooling/prettier/index.mjs",
"cSpell.words": [
"ajnart",
"cqmin",
@@ -34,11 +27,7 @@
],
"i18n-ally.dirStructure": "auto",
"i18n-ally.displayLanguage": "en",
"i18n-ally.enabledFrameworks": [
"custom"
],
"i18n-ally.localesPaths": [
"packages/translation/src/lang",
],
"i18n-ally.keystyle": "nested",
"i18n-ally.enabledFrameworks": ["custom"],
"i18n-ally.localesPaths": ["packages/translation/src/lang"],
"i18n-ally.keystyle": "nested"
}
+4038 -3642
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -3,7 +3,11 @@
# Exempt Renovate‑managed files (no owners)
package.json
**/package.json
package-lock.json
**/package-lock.json
pnpm-lock.yaml
**/pnpm-lock.yaml
pnpm-workspace.yaml
Dockerfile
docker-compose.yml
+1 -3
View File
@@ -1,4 +1,4 @@
FROM node:24.13.1-alpine AS base
FROM node:24.16.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
@@ -43,8 +43,6 @@ RUN mkdir -p /var/cache/nginx && \
COPY --from=builder /app/apps/nextjs/next.config.ts .
COPY --from=builder /app/apps/nextjs/package.json .
COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
COPY --from=builder /app/packages/db/migrations ./db/migrations
-13
View File
@@ -1,13 +0,0 @@
import baseConfig from "@homarr/eslint-config/base";
import nextjsConfig from "@homarr/eslint-config/nextjs";
import reactConfig from "@homarr/eslint-config/react";
/** @type {import('typescript-eslint').Config} */
export default [
{
ignores: [".next/**"],
},
...baseConfig,
...reactConfig,
...nextjsConfig,
];
+11 -5
View File
@@ -6,7 +6,6 @@ import "@homarr/core/infrastructure/logs/env";
import "@homarr/docker/env";
import type { NextConfig } from "next";
import MillionLint from "@million/lint";
import createNextIntlPlugin from "next-intl/plugin";
// Package path does not work... so we need to use relative path
@@ -32,6 +31,8 @@ const nextConfig: NextConfig = {
experimental: {
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
turbopackFileSystemCacheForDev: true,
preloadEntriesOnStart: false,
webpackMemoryOptimizations: true,
},
transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"],
images: {
@@ -42,9 +43,18 @@ const nextConfig: NextConfig = {
},
],
},
// skipcq: JS-0116
// eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax
async headers() {
return [
{
source: "/sw.js",
headers: [
{ key: "Content-Type", value: "application/javascript; charset=utf-8" },
{ key: "Cache-Control", value: "no-cache, no-store, must-revalidate" },
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" },
],
},
{
source: "/(.*)", // Apply CSP to all routes
headers: [
@@ -74,8 +84,4 @@ const nextConfig: NextConfig = {
},
};
// Skip transform is used because of webpack loader, without it for example 'Tooltip.Floating' will not work and show an error
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const withMillionLint = MillionLint.next({ rsc: true, skipTransform: true, telemetry: false });
export default withNextIntl(nextConfig);
+59 -61
View File
@@ -7,18 +7,18 @@
"build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules",
"dev": "pnpm with-env next dev",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"format": "oxfmt .",
"lint": "oxlint",
"start": "pnpm with-env next start",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@dnd-kit/core": "catalog:",
"@dnd-kit/modifiers": "catalog:",
"@dnd-kit/sortable": "catalog:",
"@dnd-kit/utilities": "catalog:",
"@gfazioli/mantine-onboarding-tour": "catalog:",
"@homarr/analytics": "workspace:^0.1.0",
"@homarr/api": "workspace:^0.1.0",
"@homarr/auth": "workspace:^0.1.0",
@@ -31,7 +31,7 @@
"@homarr/docker": "workspace:^0.1.0",
"@homarr/form": "workspace:^0.1.0",
"@homarr/forms-collection": "workspace:^0.1.0",
"@homarr/gridstack": "^1.12.0",
"@homarr/gridstack": "catalog:",
"@homarr/icons": "workspace:^0.1.0",
"@homarr/image-proxy": "workspace:^0.1.0",
"@homarr/integrations": "workspace:^0.1.0",
@@ -44,65 +44,63 @@
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/settings": "workspace:^0.1.0",
"@homarr/spotlight": "workspace:^0.1.0",
"@homarr/tasks": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/websocket": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.3.15",
"@mantine/core": "^8.3.15",
"@mantine/dropzone": "^8.3.15",
"@mantine/hooks": "^8.3.15",
"@mantine/modals": "^8.3.15",
"@mantine/tiptap": "^8.3.15",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.37.1",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-query-next-experimental": "^5.91.0",
"@trpc/client": "^11.10.0",
"@trpc/next": "^11.10.0",
"@trpc/react-query": "^11.10.0",
"@trpc/server": "^11.10.0",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "^6.0.0",
"babel-plugin-react-compiler": "^1.0.0",
"chroma-js": "^3.2.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.19",
"dotenv": "^17.3.1",
"flag-icons": "^7.5.0",
"glob": "^13.0.6",
"isomorphic-dompurify": "^3.0.0",
"jotai": "^2.18.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "16.1.6",
"postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-error-boundary": "^6.1.1",
"react-simple-code-editor": "^0.14.1",
"sass": "^1.97.3",
"superjson": "2.2.6",
"swagger-ui-react": "^5.31.2",
"use-deep-compare-effect": "^1.8.1",
"zod": "^4.2.1"
"@mantine/colors-generator": "catalog:",
"@mantine/core": "catalog:",
"@mantine/dropzone": "catalog:",
"@mantine/hooks": "catalog:",
"@mantine/modals": "catalog:",
"@mantine/tiptap": "catalog:",
"@tabler/icons-react": "catalog:",
"@tanstack/react-query": "catalog:",
"@tanstack/react-query-devtools": "catalog:",
"@tanstack/react-query-next-experimental": "catalog:",
"@trpc/client": "catalog:",
"@trpc/next": "catalog:",
"@trpc/react-query": "catalog:",
"@trpc/server": "catalog:",
"@xterm/addon-canvas": "catalog:",
"@xterm/addon-fit": "catalog:",
"@xterm/xterm": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"chroma-js": "catalog:",
"clsx": "catalog:",
"dayjs": "catalog:",
"dotenv": "catalog:",
"flag-icons": "catalog:",
"glob": "catalog:",
"isomorphic-dompurify": "catalog:",
"jotai": "catalog:",
"mantine-react-table": "catalog:",
"next": "catalog:",
"postcss-preset-mantine": "catalog:",
"prismjs": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-error-boundary": "catalog:",
"react-simple-code-editor": "catalog:",
"sass": "catalog:",
"superjson": "catalog:",
"swagger-ui-react": "catalog:",
"use-deep-compare-effect": "catalog:",
"yaml": "catalog:",
"zod": "catalog:"
},
"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.2",
"@types/node": "^24.10.13",
"@types/prismjs": "^1.26.6",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.2.1",
"eslint": "^9.39.3",
"node-loader": "^2.1.0",
"prettier": "^3.8.1",
"typescript": "^5.9.3"
"@types/chroma-js": "catalog:",
"@types/node": "catalog:",
"@types/prismjs": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/swagger-ui-react": "catalog:",
"concurrently": "catalog:",
"node-loader": "catalog:",
"typescript": "catalog:"
}
}
+7
View File
@@ -0,0 +1,7 @@
self.addEventListener("install", () => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});
@@ -2,29 +2,27 @@
import type { PropsWithChildren } from "react";
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
import { createTheme, DirectionProvider, MantineProvider } from "@mantine/core";
import { DirectionProvider, MantineProvider, v8CssVariablesResolver } from "@mantine/core";
import dayjs from "dayjs";
import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { parseCookies, setClientCookie } from "@homarr/common";
import type { ColorScheme } from "@homarr/definitions";
import { colorSchemeCookieKey } from "@homarr/definitions";
import { theme } from "@homarr/ui";
export const CustomMantineProvider = ({
children,
defaultColorScheme,
}: PropsWithChildren<{ defaultColorScheme: ColorScheme }>) => {
}: PropsWithChildren<{ defaultColorScheme: MantineColorScheme }>) => {
const manager = useColorSchemeManager();
return (
<DirectionProvider>
<MantineProvider
defaultColorScheme={defaultColorScheme}
colorSchemeManager={manager}
theme={createTheme({
primaryColor: "red",
autoContrast: true,
})}
theme={theme}
cssVariablesResolver={v8CssVariablesResolver}
>
{children}
</MantineProvider>
@@ -35,7 +33,7 @@ export const CustomMantineProvider = ({
export function useColorSchemeManager(): MantineColorSchemeManager {
const { data: session } = useSession();
const updateCookieValue = (value: Exclude<MantineColorScheme, "auto">) => {
const updateCookieValue = (value: MantineColorScheme) => {
setClientCookie(colorSchemeCookieKey, value, { expires: dayjs().add(1, "year").toDate(), path: "/" });
};
@@ -60,7 +58,6 @@ export function useColorSchemeManager(): MantineColorSchemeManager {
},
set: (value) => {
if (value === "auto") return;
try {
if (session) {
mutateColorScheme({ colorScheme: value });
@@ -17,10 +17,13 @@ import {
import superjson from "superjson";
import type { SuperJSONResult } from "superjson";
import { TRPCClientError } from "@trpc/client";
import type { AppRouter } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared";
import { env } from "@homarr/common/env";
import { showWarningNotification } from "@homarr/notifications";
const getWebSocketProtocol = () => {
// window is not defined on server side
@@ -56,6 +59,20 @@ export function TRPCReactProvider(props: PropsWithChildren) {
queries: {
staleTime: 5 * 1000,
},
mutations: {
onError(error) {
if (
error instanceof TRPCClientError &&
error.data?.code === "FORBIDDEN" &&
error.message === "Mutations are disabled in demo mode"
) {
showWarningNotification({
title: "Demo mode",
message: "This action is disabled in demo mode.",
});
}
},
},
},
}),
);
@@ -59,7 +59,7 @@ export default async function InviteUsagePage(props: InviteUsagePageProps) {
{t("subtitle")}
</Text>
</Stack>
<Card withBorder w={64 * 6} maw="90vw">
<Card w={64 * 6} maw="90vw">
<RegistrationForm invite={invite} />
</Card>
<Text size="xs" c="gray.5" ta="center">
@@ -9,10 +9,11 @@ import { z } from "zod/v4";
import { signIn } from "@homarr/auth/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import type { useForm } from "@homarr/form";
import type { UseFormReturnType } from "@homarr/form";
import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { sanitizeRedirectionUrl } from "@homarr/validation/redirection-url";
import { userSignInSchema } from "@homarr/validation/user";
type Provider = "credentials" | "ldap" | "oidc";
@@ -70,7 +71,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
// Redirect to the callback URL if the response is defined and comes from a credentials provider (ldap or credentials). oidc is redirected automatically.
await revalidatePathActionAsync("/");
router.push(callbackUrl);
router.push(sanitizeRedirectionUrl(callbackUrl));
},
[t, router, callbackUrl],
);
@@ -161,7 +162,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
interface SubmitButtonProps {
isPending: boolean;
form: ReturnType<typeof useForm<FormType, (values: FormType) => FormType>>;
form: UseFormReturnType<FormType>;
provider: "credentials" | "ldap";
}
@@ -197,7 +198,7 @@ const PasswordForgottenCollapse = ({ username }: PasswordForgottenCollapseProps)
{tForgotPassword("label")}
</Anchor>
<Collapse in={visible}>
<Collapse expanded={visible}>
<Card>
<Stack gap="xs">
<Text size="sm">{tForgotPassword("description")}</Text>
@@ -1,9 +1,13 @@
import { redirect } from "next/navigation";
import { Card, Center, Stack, Text, Title } from "@mantine/core";
import { Alert, Card, Center, Code, Stack, Text, Title } from "@mantine/core";
import { IconLogin } from "@tabler/icons-react";
import { env } from "@homarr/auth/env";
import { auth } from "@homarr/auth/next";
import { getScopedI18n } from "@homarr/translation/server";
import { sanitizeRedirectionUrl } from "@homarr/validation/redirection-url";
import { env as appEnv } from "~/env";
import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo";
import { LoginForm } from "./_login-form";
@@ -19,7 +23,7 @@ export default async function Login(props: LoginProps) {
const session = await auth();
if (session) {
redirect(searchParams.callbackUrl ?? "/");
redirect(sanitizeRedirectionUrl(searchParams.callbackUrl));
}
const t = await getScopedI18n("user.page.login");
@@ -36,7 +40,14 @@ export default async function Login(props: LoginProps) {
{t("subtitle")}
</Text>
</Stack>
<Card withBorder w={64 * 6} maw="90vw">
{appEnv.DEMO_MODE && (
<Alert icon={<IconLogin size={18} />} color="blue" variant="light" w={64 * 6} maw="90vw">
<Text size="sm" fw={500}>
Demo mode is enabled. Sign in with username <Code>demo</Code> and password <Code>demo</Code>
</Text>
</Alert>
)}
<Card w={64 * 6} maw="90vw">
<LoginForm
providers={env.AUTH_PROVIDERS}
oidcClientName={env.AUTH_OIDC_CLIENT_NAME}
@@ -1,70 +1,27 @@
"use client";
import { useCallback, useRef } from "react";
import { Box, LoadingOverlay, Stack } from "@mantine/core";
import { Box, Stack } from "@mantine/core";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import { BoardCategorySection } from "~/components/board/sections/category-section";
import { BoardEmptySection } from "~/components/board/sections/empty-section";
import { BoardBackgroundVideo } from "~/components/layout/background";
import { fullHeightWithoutHeaderAndFooter } from "~/constants";
import { useIsBoardReady } from "./_ready-context";
let boardName: string | null = null;
export const updateBoardName = (name: string | null) => {
boardName = name;
};
type UpdateCallback = (prev: RouterOutputs["board"]["getHomeBoard"]) => RouterOutputs["board"]["getHomeBoard"];
export const useUpdateBoard = () => {
const utils = clientApi.useUtils();
const updateBoard = useCallback(
(updaterWithoutUndefined: UpdateCallback) => {
if (!boardName) {
throw new Error("Board name is not set");
}
utils.board.getBoardByName.setData({ name: boardName }, (previous) =>
previous ? updaterWithoutUndefined(previous) : previous,
);
},
[utils],
);
return {
updateBoard,
};
};
export const ClientBoard = () => {
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
const isReady = useIsBoardReady();
const fullWidthSortedSections = board.sections
.filter((section) => section.kind === "empty" || section.kind === "category")
.sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset);
const ref = useRef<HTMLDivElement>(null);
return (
<Box h="100%" pos="relative">
<BoardBackgroundVideo />
<LoadingOverlay
visible={!isReady}
transitionProps={{ duration: 500 }}
loaderProps={{ size: "lg" }}
h={fullHeightWithoutHeaderAndFooter}
/>
<Stack ref={ref} h="100%" style={{ visibility: isReady ? "visible" : "hidden" }}>
<Stack h="100%">
{fullWidthSortedSections.map((section) =>
section.kind === "empty" ? (
// Unique keys per layout to always reinitialize the gridstack
<BoardEmptySection key={`${currentLayoutId}-${section.id}`} section={section} />
) : (
<BoardCategorySection key={`${currentLayoutId}-${section.id}`} section={section} />
@@ -38,16 +38,15 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
layout: createBoardLayout({
headerActions: <BoardContentHeaderActions />,
getInitialBoardAsync: getInitialBoard,
withTour: true,
}),
// eslint-disable-next-line no-restricted-syntax
page: async ({ params }: { params: Promise<TParams> }) => {
const session = await auth();
const integrations = await getIntegrationsWithPermissionsAsync(session);
const board = await getInitialBoard(await params);
const resolvedParams = await params;
const queryClient = getQueryClient();
// Prefetch item data
const [board, session] = await Promise.all([getInitialBoard(resolvedParams), auth()]);
const itemsMap = board.items.reduce((acc, item) => {
const existing = acc.get(item.kind);
if (existing) {
@@ -58,17 +57,20 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
return acc;
}, new Map<WidgetKind, Item[]>());
for (const [kind, items] of itemsMap) {
await prefetchForKindAsync(kind, queryClient, items).catch((error) => {
logger.error(
new ErrorWithMetadata(
"Failed to prefetch widget",
{ widgetKind: kind, itemCount: items.length },
{ cause: error },
),
);
});
}
const [integrations] = await Promise.all([
getIntegrationsWithPermissionsAsync(session),
...Array.from(itemsMap).map(([kind, items]) =>
prefetchForKindAsync(kind, queryClient, items).catch((error) => {
logger.error(
new ErrorWithMetadata(
"Failed to prefetch widget",
{ widgetKind: kind, itemCount: items.length },
{ cause: error },
),
);
}),
),
]);
return (
<HydrationBoundary state={dehydrate(queryClient)}>
@@ -2,7 +2,8 @@
import { useCallback, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Group, Menu, ScrollArea } from "@mantine/core";
import { OnboardingTour } from "@gfazioli/mantine-onboarding-tour";
import { Box, Group, Menu, ScrollArea } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks";
import {
IconBox,
@@ -11,6 +12,7 @@ import {
IconLayoutBoard,
IconPencil,
IconPencilOff,
IconPlug,
IconPlus,
IconReplace,
IconResize,
@@ -36,14 +38,16 @@ import { useBoardPermissions } from "~/components/board/permissions/client";
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal";
import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions";
import { IntegrationSelectModal } from "~/components/integration/integration-select-modal";
import { HeaderButton } from "~/components/layout/header/button";
export const BoardContentHeaderActions = () => {
const [isEditMode] = useEditMode();
const board = useRequiredBoard();
const { hasChangeAccess } = useBoardPermissions(board);
const { data: demoMode, isLoading } = clientApi.info.isDemoMode.useQuery();
if (!hasChangeAccess) {
if (!hasChangeAccess || isLoading) {
return <SelectBoardsMenu />;
}
@@ -51,11 +55,15 @@ export const BoardContentHeaderActions = () => {
<>
{isEditMode && <AddMenu />}
<EditModeMenu />
<EditModeMenu demoMode={demoMode ?? false} />
<HeaderButton href={`/boards/${board.name}/settings`}>
<IconSettings stroke={1.5} />
</HeaderButton>
{!demoMode && (
<OnboardingTour.Target id="board-settings">
<HeaderButton href={`/boards/${board.name}/settings`}>
<IconSettings stroke={1.5} />
</HeaderButton>
</OnboardingTour.Target>
)}
<SelectBoardsMenu />
</>
@@ -67,6 +75,7 @@ const AddMenu = () => {
const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal);
const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal);
const { openModal: openAppSelectModal } = useModalAction(AppSelectModal);
const { openModal: openIntegrationSelectModal } = useModalAction(IntegrationSelectModal);
const { addCategoryToEnd } = useCategoryActions();
const { addDynamicSection } = useDynamicSectionActions();
const { createItem } = useItemActions();
@@ -108,8 +117,12 @@ const AddMenu = () => {
});
}, [openAppSelectModal, createItem]);
const handleAddIntegration = useCallback(() => {
openIntegrationSelectModal({});
}, [openIntegrationSelectModal]);
return (
<Menu position="bottom-end" withArrow>
<Menu position="bottom-end">
<Menu.Target>
<HeaderButton w="auto" px={4}>
<Group gap={4} wrap="nowrap">
@@ -127,6 +140,10 @@ const AddMenu = () => {
{t("app.action.add")}
</Menu.Item>
<Menu.Item leftSection={<IconPlug size={20} />} onClick={handleAddIntegration}>
{t("integration.action.create")}
</Menu.Item>
<Menu.Divider />
<Menu.Item leftSection={<IconBoxAlignTop size={20} />} onClick={handleAddCategory}>
@@ -141,7 +158,7 @@ const AddMenu = () => {
);
};
const EditModeMenu = () => {
const EditModeMenu = ({ demoMode }: { demoMode: boolean }) => {
const [isEditMode, { open, close }] = useEditMode();
const board = useRequiredBoard();
const utils = clientApi.useUtils();
@@ -164,18 +181,28 @@ const EditModeMenu = () => {
},
});
const discardDemoChanges = useCallback(() => {
void utils.board.getBoardByName.invalidate({ name: board.name });
close();
}, [utils, board.name, close]);
const toggle = useCallback(() => {
if (isEditMode) return saveBoard(board);
if (isEditMode) {
if (demoMode) return discardDemoChanges();
return saveBoard(board);
}
open();
}, [board, isEditMode, saveBoard, open]);
}, [board, isEditMode, demoMode, saveBoard, open, discardDemoChanges]);
useHotkeys([[hotkeys.toggleBoardEdit, toggle]]);
usePreventLeaveWithDirty(isEditMode);
return (
<HeaderButton onClick={toggle} loading={isPending}>
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
</HeaderButton>
<OnboardingTour.Target id="board-edit-mode">
<HeaderButton onClick={toggle} loading={isPending}>
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
</HeaderButton>
</OnboardingTour.Target>
);
};
@@ -183,27 +210,31 @@ const SelectBoardsMenu = () => {
const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();
return (
<Menu position="bottom-end" withArrow>
<Menu.Target>
<HeaderButton w="auto" px={4}>
<IconReplace stroke={1.5} />
</HeaderButton>
</Menu.Target>
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
<ScrollArea.Autosize mah={300}>
{boards.map((board) => (
<Menu.Item
key={board.id}
component={Link}
href={`/boards/${board.name}`}
leftSection={<IconLayoutBoard size={20} />}
>
{board.name}
</Menu.Item>
))}
</ScrollArea.Autosize>
</Menu.Dropdown>
</Menu>
<OnboardingTour.Target id="board-switcher">
<Box>
<Menu position="bottom-end">
<Menu.Target>
<HeaderButton w="auto" px={4}>
<IconReplace stroke={1.5} />
</HeaderButton>
</Menu.Target>
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
<ScrollArea.Autosize mah={300}>
{boards.map((board) => (
<Menu.Item
key={board.id}
component={Link}
href={`/boards/${board.name}`}
leftSection={<IconLayoutBoard size={20} />}
>
{board.name}
</Menu.Item>
))}
</ScrollArea.Autosize>
</Menu.Dropdown>
</Menu>
</Box>
</OnboardingTour.Target>
);
};
@@ -244,7 +275,7 @@ const usePreventLeaveWithDirty = (isDirty: boolean) => {
};
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (env.NODE_ENV === "development") return; // Allow to reload in development
if (env.NODE_ENV === "development") return;
event.preventDefault();
event.returnValue = true;
@@ -264,6 +295,5 @@ const usePreventLeaveWithDirty = (isDirty: boolean) => {
window.removeEventListener("popstate", handlePopState);
window.removeEventListener("beforeunload", handleBeforeUnload);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDirty]);
};
@@ -1,43 +1,19 @@
"use client";
import type { PropsWithChildren } from "react";
import { createContext, useCallback, useContext, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { createContext, useCallback, useContext } from "react";
const BoardReadyContext = createContext<{
isReady: boolean;
markAsReady: (id: string) => void;
} | null>(null);
export const BoardReadyProvider = ({ children }: PropsWithChildren) => {
const pathname = usePathname();
const utils = clientApi.useUtils();
const board = useRequiredBoard();
const [readySections, setReadySections] = useState<string[]>([]);
// Reset sections required for ready state
useEffect(() => {
return () => {
setReadySections([]);
};
}, [pathname, utils]);
useEffect(() => {
setReadySections((previous) => previous.filter((id) => board.sections.some((section) => section.id === id)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [board.sections.length, setReadySections]);
const markAsReady = useCallback((id: string) => {
setReadySections((previous) => (previous.includes(id) ? previous : [...previous, id]));
}, []);
// eslint-disable-next-line @typescript-eslint/no-empty-function
const markAsReady = useCallback((_id: string) => {}, []);
return (
<BoardReadyContext.Provider
value={{
isReady: board.sections.length === readySections.length,
markAsReady,
}}
>
@@ -55,13 +31,3 @@ export const useMarkSectionAsReady = () => {
return context.markAsReady;
};
export const useIsBoardReady = () => {
const context = useContext(BoardReadyContext);
if (!context) {
throw new Error("BoardReadyProvider is required");
}
return context.isReady;
};
@@ -2,7 +2,7 @@
import type { PropsWithChildren } from "react";
import type { MantineColorsTuple } from "@mantine/core";
import { colorsTuple, createTheme, darken, lighten, MantineProvider, rem } from "@mantine/core";
import { colorsTuple, createTheme, darken, lighten, MantineProvider, rem, v8CssVariablesResolver } from "@mantine/core";
import { useRequiredBoard } from "@homarr/boards/context";
import type { ColorScheme } from "@homarr/definitions";
@@ -24,6 +24,7 @@ export const BoardMantineProvider = ({
},
primaryColor: "primaryColor",
autoContrast: true,
defaultRadius: "sm",
fontSizes: {
"2xl": rem(24),
"3xl": rem(28),
@@ -32,7 +33,12 @@ export const BoardMantineProvider = ({
});
return (
<MantineProvider defaultColorScheme={defaultColorScheme} theme={theme} colorSchemeManager={colorSchemeManager}>
<MantineProvider
defaultColorScheme={defaultColorScheme}
theme={theme}
colorSchemeManager={colorSchemeManager}
cssVariablesResolver={v8CssVariablesResolver}
>
{children}
</MantineProvider>
);
@@ -83,7 +83,7 @@ export const ColorSettingsContent = ({ board }: Props) => {
<Anchor onClick={toggle}>{showPreview ? t("common.preview.hide") : t("common.preview.show")}</Anchor>
</Grid.Col>
<Grid.Col span={12}>
<Collapse in={showPreview}>
<Collapse expanded={showPreview}>
<Stack>
<ColorsPreview previewColor={form.values.primaryColor} />
<ColorsPreview previewColor={form.values.secondaryColor} />
@@ -2,7 +2,7 @@
import { useEffect, useRef } from "react";
import { Button, Grid, Group, Loader, Stack, TextInput } from "@mantine/core";
import { useDebouncedValue, useDocumentTitle, useFavicon } from "@mantine/hooks";
import { useDebouncedValue, useDocumentTitle } from "@mantine/hooks";
import { useUpdateBoard } from "@homarr/boards/updater";
import { useZodForm } from "@homarr/form";
@@ -10,6 +10,7 @@ import { IconPicker } from "@homarr/forms-collection";
import { useI18n } from "@homarr/translation/client";
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
import { createMetaTitle } from "~/metadata";
import type { Board } from "../../_types";
import { useSavePartialSettingsMutation } from "./_shared";
@@ -53,7 +54,6 @@ export const GeneralSettingsContent = ({ board }: Props) => {
);
useLogoPreview(form.values.logoImageUrl);
useFaviconPreview(form.values.faviconImageUrl);
const metaTitleStatus = useMetaTitlePreview(form.values.metaTitle);
// Cleanup for not applied changes of the page title and logo image URL
@@ -75,6 +75,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
pageTitle: values.pageTitle,
logoImageUrl: values.logoImageUrl,
};
updateFavicon(values.faviconImageUrl ?? homarrLogoPath);
savePartialSettings({
id: board.id,
...values,
@@ -146,7 +147,13 @@ const useMetaTitlePreview = (title: string | null) => {
};
};
const useFaviconPreview = (url: string | null) => {
const [faviconDebounced] = useDebouncedValue(url ?? "", 500);
useFavicon(faviconDebounced);
// Previously we used the useFavicon hook from Mantine
// However if the user tried to click on a link to a different page
// it caused an error "Cannot read properties of null (reading 'removeChild')"
// Probably because the head element was null (https://github.com/mantinedev/mantine/blob/b90d9b81031f45f5d15e75f25138ed6477f65bce/packages/%40mantine/hooks/src/use-favicon/use-favicon.ts#L21)
// See https://github.com/homarr-labs/homarr/issues/4905
const updateFavicon = (url: string) => {
const link: HTMLLinkElement | null = document.querySelector('link[rel="icon"]');
if (!link) return;
link.href = url;
};
@@ -11,6 +11,7 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
import { MainHeader } from "~/components/layout/header";
import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo";
import { ClientShell } from "~/components/layout/shell";
import { BoardTourProvider } from "~/components/onboarding/board-tour";
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
import type { Board } from "./_types";
import type { Params } from "./(content)/_creator";
@@ -20,14 +21,21 @@ import { BoardMantineProvider } from "./(content)/_theme";
const logger = createLogger({ module: "createBoardLayout" });
const BoardTourWrapper = ({ hasSession, children }: PropsWithChildren<{ hasSession: boolean }>) => {
if (!hasSession) return <>{children}</>;
return <BoardTourProvider>{children}</BoardTourProvider>;
};
interface CreateBoardLayoutProps<TParams extends Params> {
headerActions: JSX.Element;
getInitialBoardAsync: (params: TParams) => Promise<Board>;
withTour?: boolean;
}
export const createBoardLayout = <TParams extends Params>({
headerActions,
getInitialBoardAsync: getInitialBoard,
withTour = false,
}: CreateBoardLayoutProps<TParams>) => {
const Layout = async ({
params,
@@ -61,14 +69,16 @@ export const createBoardLayout = <TParams extends Params>({
<EditModeProvider>
<BoardMantineProvider defaultColorScheme={colorScheme}>
<CustomCss />
<ClientShell hasNavigation={false}>
<MainHeader
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
actions={headerActions}
hasNavigation={false}
/>
<AppShellMain>{children}</AppShellMain>
</ClientShell>
<BoardTourWrapper hasSession={withTour && !!session}>
<ClientShell hasNavigation={false}>
<MainHeader
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
actions={headerActions}
hasNavigation={false}
/>
<AppShellMain>{children}</AppShellMain>
</ClientShell>
</BoardTourWrapper>
</BoardMantineProvider>
</EditModeProvider>
</BoardReadyProvider>
@@ -0,0 +1,12 @@
import type { PropsWithChildren } from "react";
import { Card } from "@mantine/core";
interface InitStepCardProps {
withBorder?: boolean;
}
export const InitStepCard = ({ children, withBorder = true }: PropsWithChildren<InitStepCardProps>) => (
<Card w="100%" withBorder={withBorder}>
{children}
</Card>
);
@@ -0,0 +1,2 @@
export const onboardingContentWidth = 64 * 16;
export const onboardingMaxWidth = "90vw";
@@ -1,39 +1,27 @@
import type { MantineColor } from "@mantine/core";
import { Button, Card, Stack, Text } from "@mantine/core";
import { IconBook2, IconCategoryPlus, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react";
import { Button, Stack, Text } from "@mantine/core";
import { IconBook2, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react";
import { isProviderEnabled } from "@homarr/auth/server";
import { getMantineColor } from "@homarr/common";
import { db } from "@homarr/db";
import { createDocumentationLink } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import type { TablerIcon } from "@homarr/ui";
import { InitStepCard } from "../../_components/init-step-card";
export const InitFinish = async () => {
const firstBoard = await db.query.boards.findFirst({ columns: { name: true } });
const tFinish = await getScopedI18n("init.step.finish");
return (
<Card w={64 * 6} maw="90vw" withBorder>
<InitStepCard>
<Stack>
<Text>{tFinish("description")}</Text>
{firstBoard ? (
<InternalLinkButton
href={`/auth/login?callbackUrl=/boards/${firstBoard.name}`}
iconProps={{ icon: IconLayoutDashboard, color: "blue" }}
>
{tFinish("action.goToBoard", { name: firstBoard.name })}
</InternalLinkButton>
) : (
<InternalLinkButton
href="/auth/login?callbackUrl=/manage/boards"
iconProps={{ icon: IconCategoryPlus, color: "blue" }}
>
{tFinish("action.createBoard")}
</InternalLinkButton>
)}
<InternalLinkButton href="/auth/login?callbackUrl=/" iconProps={{ icon: IconLayoutDashboard, color: "blue" }}>
{tFinish("action.goToBoard", { name: "dashboard" })}
</InternalLinkButton>
{isProviderEnabled("credentials") && (
<InternalLinkButton
@@ -51,7 +39,7 @@ export const InitFinish = async () => {
{tFinish("action.docs")}
</ExternalLinkButton>
</Stack>
</Card>
</InitStepCard>
);
};
@@ -1,6 +1,6 @@
"use client";
import { Button, Card, Stack, TextInput } from "@mantine/core";
import { Button, Stack, TextInput } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react";
import type { z } from "zod/v4";
@@ -10,6 +10,8 @@ import { useZodForm } from "@homarr/form";
import { useI18n } from "@homarr/translation/client";
import { groupCreateSchema } from "@homarr/validation/group";
import { InitStepCard } from "../../_components/init-step-card";
export const InitGroup = () => {
const t = useI18n();
const { mutateAsync } = clientApi.group.createInitialExternalGroup.useMutation({
@@ -34,7 +36,7 @@ export const InitGroup = () => {
};
return (
<Card w={64 * 6} maw="90vw" withBorder>
<InitStepCard>
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
<Stack>
<TextInput
@@ -48,6 +50,6 @@ export const InitGroup = () => {
</Button>
</Stack>
</form>
</Card>
</InitStepCard>
);
};
@@ -1,10 +1,12 @@
import { ActionIcon, Button, Card, Group, Text } from "@mantine/core";
import { ActionIcon, Button, Group, Text } from "@mantine/core";
import type { FileWithPath } from "@mantine/dropzone";
import { IconPencil } from "@tabler/icons-react";
import { humanFileSize } from "@homarr/common";
import { useScopedI18n } from "@homarr/translation/client";
import { InitStepCard } from "../../_components/init-step-card";
interface FileInfoCardProps {
file: FileWithPath;
onRemove: () => void;
@@ -13,7 +15,7 @@ interface FileInfoCardProps {
export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
const tFileInfo = useScopedI18n("init.step.import.fileInfo");
return (
<Card w={64 * 12 + 8} maw="90vw">
<InitStepCard withBorder={false}>
<Group justify="space-between" align="center" wrap="nowrap">
<Group>
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
@@ -36,6 +38,6 @@ export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
<IconPencil size={16} stroke={1.5} />
</ActionIcon>
</Group>
</Card>
</InitStepCard>
);
};
@@ -1,13 +1,14 @@
"use client";
import { startTransition, useState } from "react";
import { Card, Stack } from "@mantine/core";
import { Stack } from "@mantine/core";
import type { FileWithPath } from "@mantine/dropzone";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { InitialOldmarrImport } from "@homarr/old-import/components";
import { InitStepCard } from "../../_components/init-step-card";
import { FileInfoCard } from "./file-info-card";
import { ImportDropZone } from "./import-dropzone";
@@ -20,7 +21,7 @@ export const InitImport = () => {
if (!file) {
return (
<Card w={64 * 12 + 8} maw="90vw" withBorder>
<InitStepCard>
<ImportDropZone
loading={isPending}
updateFile={(file) => {
@@ -40,12 +41,12 @@ export const InitImport = () => {
});
}}
/>
</Card>
</InitStepCard>
);
}
return (
<Stack mb="sm">
<Stack w="100%" mb="sm">
<FileInfoCard file={file} onRemove={() => setFile(null)} />
{analyseResult !== null && <InitialOldmarrImport file={file} analyseResult={analyseResult} />}
</Stack>
@@ -0,0 +1,209 @@
"use client";
import { useEffect, useRef, useState } from "react";
import {
Avatar,
Badge,
Card,
Collapse,
Group,
Loader,
Paper,
ScrollArea,
SimpleGrid,
Stack,
Text,
ThemeIcon,
UnstyledButton,
} from "@mantine/core";
import { IconBrandDocker, IconCheck, IconChevronDown } from "@tabler/icons-react";
import type { IntegrationKind } from "@homarr/definitions";
import { getIntegrationName } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { IntegrationAvatar } from "@homarr/ui";
export interface DiscoveredIntegration {
containerId: string;
containerName: string;
kind: IntegrationKind;
suggestedUrl: string;
publishedPort: number | null;
iconUrl: string | null;
}
export interface DiscoveredApp {
containerId: string;
containerName: string;
suggestedUrl: string;
publishedPort: number | null;
iconUrl: string | null;
}
interface DockerDiscoveryIndicatorProps {
integrations: DiscoveredIntegration[];
apps: DiscoveredApp[];
selectedAppIds: Set<string>;
onToggleApp: (containerId: string) => void;
isLoading: boolean;
}
const APP_CARD_HEIGHT = 80;
export const DockerDiscoveryIndicator = ({
integrations,
apps,
selectedAppIds,
onToggleApp,
isLoading,
}: DockerDiscoveryIndicatorProps) => {
const hasApps = apps.length > 0;
const [expanded, setExpanded] = useState(false);
const expandedOnce = useRef(false);
useEffect(() => {
if ((hasApps || integrations.length > 0) && !expandedOnce.current) {
expandedOnce.current = true;
setExpanded(true);
}
}, [hasApps, integrations.length]);
const t = useScopedI18n("init.step.integrations.docker");
const totalCount = integrations.length + apps.length;
if (!isLoading && totalCount === 0) return null;
const selectedAppCount = apps.filter((app) => selectedAppIds.has(app.containerId)).length;
if (isLoading) {
return (
<Paper
withBorder
p="sm"
radius="md"
style={{
borderColor: "var(--mantine-color-blue-4)",
background: "var(--mantine-color-blue-light)",
}}
>
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" color="blue" size="lg" radius="md">
<IconBrandDocker size={20} />
</ThemeIcon>
<Stack gap={0} style={{ flex: 1 }}>
<Text size="sm" fw={600}>
{t("scanning")}
</Text>
<Text size="xs" c="dimmed">
{t("scanningDescription")}
</Text>
</Stack>
<Loader size="sm" color="blue" type="dots" />
</Group>
</Paper>
);
}
return (
<Paper
withBorder
p="sm"
radius="md"
style={{
borderColor: "var(--mantine-color-blue-4)",
background: "var(--mantine-color-blue-light)",
}}
>
<UnstyledButton onClick={() => setExpanded((prev) => !prev)} w="100%">
<Group justify="space-between" wrap="nowrap">
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" color="blue" size="lg" radius="md">
<IconBrandDocker size={20} />
</ThemeIcon>
<Stack gap={0}>
<Text size="sm" fw={600}>
{t("detected", { count: String(totalCount) })}
</Text>
<Text size="xs" c="dimmed">
{t("detectedBreakdown", {
integrations: String(integrations.length),
apps: String(apps.length),
})}
</Text>
</Stack>
</Group>
<IconChevronDown
size={16}
color="var(--mantine-color-dimmed)"
style={{
transform: expanded ? "rotate(180deg)" : undefined,
transition: "transform 200ms ease",
}}
/>
</Group>
</UnstyledButton>
<Collapse expanded={expanded}>
<Stack gap="xs" mt="sm">
{integrations.length > 0 && (
<Stack gap={4}>
{integrations.map((integration) => (
<Group key={integration.containerId} gap="xs" wrap="nowrap" py={2}>
<IntegrationAvatar kind={integration.kind} size="xs" />
<Text size="xs" fw={500} lineClamp={1} style={{ flex: 1, minWidth: 0 }}>
{integration.containerName}
</Text>
<Badge size="xs" variant="light" color="blue">
{getIntegrationName(integration.kind)}
</Badge>
</Group>
))}
</Stack>
)}
{hasApps && (
<Stack gap="xs">
<Text size="xs" fw={500}>
{t("appSectionLabel", { count: String(selectedAppCount) })}
</Text>
<ScrollArea.Autosize mah="30vh">
<SimpleGrid cols={{ base: 3, xs: 4, sm: 5 }} spacing="xs">
{apps.map((app) => {
const isSelected = selectedAppIds.has(app.containerId);
return (
<Card
key={app.containerId}
h={APP_CARD_HEIGHT}
p="xs"
withBorder
style={{
cursor: "pointer",
borderColor: isSelected ? "var(--mantine-color-blue-6)" : undefined,
borderWidth: isSelected ? 2 : undefined,
opacity: isSelected ? 1 : 0.6,
}}
onClick={() => onToggleApp(app.containerId)}
>
<Stack justify="space-between" h="100%" gap={4} align="center">
<Group justify="space-between" w="100%" wrap="nowrap">
<Text size="xs" fw={500} lineClamp={1} style={{ flex: 1, minWidth: 0 }}>
{app.containerName}
</Text>
{isSelected && <IconCheck size={14} color="var(--mantine-color-blue-6)" />}
</Group>
<Avatar size="sm" radius="sm" src={app.iconUrl} styles={{ image: { objectFit: "contain" } }}>
{app.containerName.at(0)?.toUpperCase()}
</Avatar>
</Stack>
</Card>
);
})}
</SimpleGrid>
</ScrollArea.Autosize>
</Stack>
)}
</Stack>
</Collapse>
</Paper>
);
};
@@ -0,0 +1,483 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
ActionIcon,
Alert,
Avatar,
Button,
Card,
Center,
Collapse,
Group,
Loader,
Paper,
Progress,
SegmentedControl,
SimpleGrid,
Stack,
Text,
TextInput,
ThemeIcon,
Tooltip,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
IconAlertTriangle,
IconArrowRight,
IconBrandDocker,
IconCheck,
IconChevronDown,
IconSettings,
} from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import type { IntegrationKind, UrlTemplateMode } from "@homarr/definitions";
import { buildAppUrl, buildIntegrationUrl, getIntegrationName } from "@homarr/definitions";
import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { IntegrationAvatar } from "@homarr/ui";
import { NewIntegrationForm } from "~/app/[locale]/manage/integrations/new/_integration-new-form";
import { IntegrationMultiSelectGrid } from "~/components/integration/integration-multi-select-grid";
import { InitStepCard } from "../../_components/init-step-card";
type Phase = "select" | "configure" | "done";
interface IntegrationDraft {
kind: IntegrationKind;
initialUrl: string;
initialName?: string;
dockerPort?: number | null;
source: "manual" | "docker";
}
export const InitIntegrations = () => {
const [phase, setPhase] = useState<Phase>("select");
const [drafts, setDrafts] = useState<Map<IntegrationKind, IntegrationDraft>>(new Map());
const [selectedKinds, setSelectedKinds] = useState<IntegrationKind[]>([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [baseHost, setBaseHost] = useState("");
const [urlMode, setUrlMode] = useState<UrlTemplateMode>("subdomain");
const t = useScopedI18n("init.step.integrations");
const router = useRouter();
const { mutateAsync: setupIntegrations } = clientApi.onboard.setupIntegrations.useMutation();
const { mutateAsync: createApps } = clientApi.onboard.createAppsFromDiscovery.useMutation();
const { data: dockerData, isPending: isDockerLoading } = clientApi.onboard.discoverDockerServices.useQuery(
undefined,
{
retry: false,
refetchOnWindowFocus: false,
},
);
const dockerIntegrations = dockerData?.status === "success" ? dockerData.integrations : [];
const dockerApps = dockerData?.status === "success" ? dockerData.apps : [];
const totalDetected = dockerIntegrations.length + dockerApps.length;
const detectedKinds = useMemo(() => new Set(dockerIntegrations.map((i) => i.kind)), [dockerIntegrations]);
const [selectedAppIds, setSelectedAppIds] = useState<Set<string>>(new Set());
const [appsExpanded, setAppsExpanded] = useState(false);
const dockerApplied = useRef(false);
useEffect(() => {
if (dockerData?.status !== "success" || dockerApplied.current) return;
dockerApplied.current = true;
const newDrafts = new Map(drafts);
const newKinds = new Set(selectedKinds);
for (const item of dockerData.integrations) {
if (!newDrafts.has(item.kind)) {
newDrafts.set(item.kind, {
kind: item.kind,
initialUrl: item.suggestedUrl,
initialName: item.containerName,
dockerPort: item.publishedPort,
source: "docker",
});
}
newKinds.add(item.kind);
}
setDrafts(newDrafts);
setSelectedKinds(Array.from(newKinds));
setSelectedAppIds(new Set(dockerData.apps.map((app) => app.containerId)));
if (dockerData.apps.length > 0) {
setAppsExpanded(true);
}
}, [dockerData]);
const handleSelectionChange = useCallback((kinds: IntegrationKind[]) => {
setSelectedKinds(kinds);
}, []);
const handleToggleApp = useCallback((containerId: string) => {
setSelectedAppIds((prev) => {
const next = new Set(prev);
if (next.has(containerId)) {
next.delete(containerId);
} else {
next.add(containerId);
}
return next;
});
}, []);
const handleContinueToConfig = () => {
const newDrafts = new Map(drafts);
for (const kind of selectedKinds) {
const existing = newDrafts.get(kind);
if (baseHost) {
newDrafts.set(kind, {
kind,
initialUrl: buildIntegrationUrl(kind, baseHost, urlMode, existing?.dockerPort ?? undefined),
initialName: existing?.initialName,
dockerPort: existing?.dockerPort,
source: existing?.source ?? "manual",
});
} else if (!existing) {
newDrafts.set(kind, { kind, initialUrl: "", source: "manual" });
}
}
setDrafts(newDrafts);
setCurrentIndex(0);
setPhase("configure");
};
const finishSetupAsync = async () => {
setPhase("done");
try {
const appsToCreate = dockerApps.filter((app) => selectedAppIds.has(app.containerId));
if (appsToCreate.length > 0) {
await createApps(
appsToCreate.map((app) => {
const href = baseHost
? buildAppUrl(app.containerName, baseHost, urlMode, app.publishedPort ?? undefined)
: app.suggestedUrl;
return {
name: app.containerName,
href: href || null,
iconUrl: app.iconUrl,
};
}),
);
}
await setupIntegrations();
router.refresh();
} catch {
showErrorNotification({
title: t("configure.error.title"),
message: t("configure.error.message"),
});
setPhase("select");
}
};
const advanceOrFinish = () => {
if (currentIndex + 1 >= selectedKinds.length) {
void finishSetupAsync();
} else {
setCurrentIndex((i) => i + 1);
}
};
const backToSelect = () => {
setPhase("select");
setCurrentIndex(0);
};
if (phase === "done") {
return (
<InitStepCard>
<Center py="xl">
<Stack align="center" gap="md">
<Loader size="lg" />
<Text>{t("configure.finishing")}</Text>
</Stack>
</Center>
</InitStepCard>
);
}
if (phase === "configure") {
return (
<ConfigurePhase
selectedKinds={selectedKinds}
currentIndex={currentIndex}
drafts={drafts}
baseHost={baseHost}
setBaseHost={setBaseHost}
urlMode={urlMode}
setUrlMode={setUrlMode}
onSuccess={advanceOrFinish}
onCancel={backToSelect}
onSkip={advanceOrFinish}
/>
);
}
const selectedAppCount = dockerApps.filter((app) => selectedAppIds.has(app.containerId)).length;
return (
<InitStepCard>
<Stack>
<Text>{t("description")}</Text>
<Alert variant="outline" color="orange" icon={<IconAlertTriangle />} title={t("urlTemplate.label")}>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t("urlTemplate.warning")}
</Text>
<Group gap="sm" grow>
<TextInput
placeholder={t("urlTemplate.placeholder")}
value={baseHost}
onChange={(e) => setBaseHost(e.currentTarget.value)}
data-autofocus
/>
<SegmentedControl
value={urlMode}
onChange={(value) => setUrlMode(value as UrlTemplateMode)}
data={[
{ label: t("urlTemplate.mode.subdomain"), value: "subdomain" },
{ label: t("urlTemplate.mode.hostPort"), value: "hostPort" },
]}
style={{ flex: "0 0 auto" }}
/>
</Group>
{baseHost && (
<Text size="xs" c="dimmed">
{t("urlTemplate.preview", { example: buildIntegrationUrl("sonarr", baseHost, urlMode) })}
</Text>
)}
</Stack>
</Alert>
{isDockerLoading && (
<Paper withBorder p="sm" radius="md">
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" color="blue" size="lg" radius="md">
<IconBrandDocker size={20} />
</ThemeIcon>
<Text size="sm" fw={500}>
{t("docker.scanning")}
</Text>
<Loader size="sm" color="blue" type="dots" />
</Group>
</Paper>
)}
{!isDockerLoading && totalDetected > 0 && (
<Paper withBorder p="sm" radius="md" style={{ borderColor: "var(--mantine-color-teal-4)" }}>
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" color="teal" size="lg" radius="md">
<IconBrandDocker size={20} />
</ThemeIcon>
<Text size="sm">
{t("docker.summary", {
apps: String(totalDetected),
integrations: String(dockerIntegrations.length),
})}
</Text>
</Group>
</Paper>
)}
<IntegrationMultiSelectGrid
selectedKinds={selectedKinds}
onSelectionChange={handleSelectionChange}
detectedKinds={detectedKinds}
onboarding
/>
{dockerApps.length > 0 && (
<Paper withBorder p="sm" radius="md">
<Stack gap="xs">
<Group
justify="space-between"
wrap="nowrap"
style={{ cursor: "pointer" }}
onClick={() => setAppsExpanded((prev) => !prev)}
>
<Text size="sm" fw={500}>
{t("docker.appSectionLabel", { count: String(selectedAppCount) })}
</Text>
<IconChevronDown
size={16}
color="var(--mantine-color-dimmed)"
style={{
transform: appsExpanded ? "rotate(180deg)" : undefined,
transition: "transform 200ms ease",
}}
/>
</Group>
<Collapse expanded={appsExpanded}>
<SimpleGrid cols={{ base: 3, xs: 4, sm: 5 }} spacing="xs">
{dockerApps.map((app) => {
const isSelected = selectedAppIds.has(app.containerId);
return (
<Card
key={app.containerId}
h={80}
p="xs"
withBorder
style={{
cursor: "pointer",
borderColor: isSelected ? "var(--mantine-color-blue-6)" : undefined,
borderWidth: isSelected ? 2 : undefined,
opacity: isSelected ? 1 : 0.6,
}}
onClick={() => handleToggleApp(app.containerId)}
>
<Stack justify="space-between" h="100%" gap={4} align="center">
<Group justify="space-between" w="100%" wrap="nowrap">
<Text size="xs" fw={500} lineClamp={1} style={{ flex: 1, minWidth: 0 }}>
{app.containerName}
</Text>
{isSelected && <IconCheck size={14} color="var(--mantine-color-blue-6)" />}
</Group>
<Avatar size="sm" radius="sm" src={app.iconUrl} styles={{ image: { objectFit: "contain" } }}>
{app.containerName.at(0)?.toUpperCase()}
</Avatar>
</Stack>
</Card>
);
})}
</SimpleGrid>
</Collapse>
</Stack>
</Paper>
)}
<Group justify="space-between">
<Button variant="subtle" onClick={() => void finishSetupAsync()}>
{t("action.skip")}
</Button>
<Button
onClick={handleContinueToConfig}
disabled={selectedKinds.length === 0 || undefined}
rightSection={<IconArrowRight size={16} stroke={1.5} />}
suppressHydrationWarning
>
{t("action.continue")} {selectedKinds.length > 0 && `(${selectedKinds.length})`}
</Button>
</Group>
</Stack>
</InitStepCard>
);
};
interface ConfigurePhaseProps {
selectedKinds: IntegrationKind[];
currentIndex: number;
drafts: Map<IntegrationKind, IntegrationDraft>;
baseHost: string;
setBaseHost: (value: string) => void;
urlMode: UrlTemplateMode;
setUrlMode: (value: UrlTemplateMode) => void;
onSuccess: () => void;
onCancel: () => void;
onSkip: () => void;
}
const ConfigurePhase = ({
selectedKinds,
currentIndex,
drafts,
baseHost,
setBaseHost,
urlMode,
setUrlMode,
onSuccess,
onCancel,
onSkip,
}: ConfigurePhaseProps) => {
const t = useScopedI18n("init.step.integrations");
const [helpersOpen, { toggle: toggleHelpers }] = useDisclosure(false);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- currentIndex is always within bounds of selectedKinds
const currentKind = selectedKinds[currentIndex]!;
const draft = drafts.get(currentKind);
const progress = ((currentIndex + 1) / selectedKinds.length) * 100;
const computedUrl = baseHost
? buildIntegrationUrl(currentKind, baseHost, urlMode, draft?.dockerPort ?? undefined)
: draft?.initialUrl;
return (
<InitStepCard>
<Stack>
<Group justify="space-between" align="center">
<Group gap="xs">
<IntegrationAvatar kind={currentKind} size="sm" />
<Text fw={500}>{getIntegrationName(currentKind)}</Text>
</Group>
<Tooltip label={t("configure.helpers.tooltip")}>
<ActionIcon variant={helpersOpen ? "light" : "subtle"} color="gray" onClick={toggleHelpers}>
<IconSettings size={18} stroke={1.5} />
</ActionIcon>
</Tooltip>
</Group>
<Stack gap={4}>
<Progress value={progress} size="sm" />
<Text size="xs" c="dimmed" ta="center">
{t("configure.progress", { current: String(currentIndex + 1), total: String(selectedKinds.length) })}
</Text>
</Stack>
<Collapse expanded={helpersOpen}>
<Stack
gap="xs"
p="xs"
style={{ background: "var(--mantine-color-default-hover)", borderRadius: "var(--mantine-radius-sm)" }}
>
<Text size="xs" c="dimmed">
{t("configure.helpers.description")}
</Text>
<Group gap="sm" grow>
<TextInput
size="xs"
placeholder={t("urlTemplate.placeholder")}
value={baseHost}
onChange={(e) => setBaseHost(e.currentTarget.value)}
/>
<SegmentedControl
size="xs"
value={urlMode}
onChange={(value) => setUrlMode(value as UrlTemplateMode)}
data={[
{ label: t("urlTemplate.mode.subdomain"), value: "subdomain" },
{ label: t("urlTemplate.mode.hostPort"), value: "hostPort" },
]}
style={{ flex: "0 0 auto" }}
/>
</Group>
{baseHost && (
<Text size="xs" c="dimmed">
{t("urlTemplate.preview", {
example: buildIntegrationUrl(currentKind, baseHost, urlMode, draft?.dockerPort ?? undefined),
})}
</Text>
)}
</Stack>
</Collapse>
<NewIntegrationForm
key={currentKind}
kind={currentKind}
initialUrl={computedUrl}
initialName={draft?.initialName}
onSuccess={onSuccess}
onCancel={onCancel}
onSkip={onSkip}
isOnboarding
/>
</Stack>
</InitStepCard>
);
};
@@ -1,6 +1,6 @@
"use client";
import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core";
import { Button, Group, Stack, Switch, Text } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react";
import type { z } from "zod/v4";
@@ -13,6 +13,8 @@ import type { TranslationObject } from "@homarr/translation";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { settingsInitSchema } from "@homarr/validation/settings";
import { InitStepCard } from "../../_components/init-step-card";
export const InitSettings = () => {
const tSection = useScopedI18n("management.page.settings.section");
const t = useI18n();
@@ -29,8 +31,8 @@ export const InitSettings = () => {
return (
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
<Stack>
<Card w={64 * 12 + 8} maw="90vw" withBorder>
<Stack w="100%">
<InitStepCard>
<Stack gap="sm">
<Text fw={500}>{tSection("analytics.title")}</Text>
@@ -38,8 +40,8 @@ export const InitSettings = () => {
<AnalyticsRow kind="general" {...form.getInputProps("analytics.enableGeneral", { type: "checkbox" })} />
</Stack>
</Stack>
</Card>
<Card w={64 * 12 + 8} maw="90vw" withBorder>
</InitStepCard>
<InitStepCard>
<Stack gap="sm">
<Text fw={500}>{tSection("crawlingAndIndexing.title")}</Text>
@@ -62,7 +64,7 @@ export const InitSettings = () => {
/>
</Stack>
</Stack>
</Card>
</InitStepCard>
<Button type="submit" loading={form.submitting} rightSection={<IconArrowRight size={16} stroke={1.5} />}>
{t("common.action.continue")}
@@ -1,16 +1,17 @@
import { Card, Stack, Text } from "@mantine/core";
import { Stack, Text } from "@mantine/core";
import { IconFileImport, IconPlayerPlay } from "@tabler/icons-react";
import { getMantineColor } from "@homarr/common";
import { getScopedI18n } from "@homarr/translation/server";
import { InitStepCard } from "../../_components/init-step-card";
import { InitStartButton } from "./next-button";
export const InitStart = async () => {
const tStart = await getScopedI18n("init.step.start");
return (
<Card w={64 * 6} maw="90vw" withBorder>
<InitStepCard>
<Stack>
<Text>{tStart("description")}</Text>
@@ -27,6 +28,6 @@ export const InitStart = async () => {
{tStart("action.importOldmarr")}
</InitStartButton>
</Stack>
</Card>
</InitStepCard>
);
};
@@ -1,15 +1,15 @@
"use client";
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
import { Button, Stack, TextInput } from "@mantine/core";
import type { z } from "zod/v4";
import { clientApi } from "@homarr/api/client";
import { signIn } from "@homarr/auth/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { useZodForm } from "@homarr/form";
import { UserCreatePasswordFields } from "@homarr/forms-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { CustomPasswordInput } from "@homarr/ui";
import { userInitSchema } from "@homarr/validation/user";
export const InitUserForm = () => {
@@ -59,12 +59,10 @@ export const InitUserForm = () => {
>
<Stack gap="lg">
<TextInput label={t("field.username.label")} {...form.getInputProps("username")} />
<CustomPasswordInput
withPasswordRequirements
label={t("field.password.label")}
{...form.getInputProps("password")}
<UserCreatePasswordFields
passwordInputProps={form.getInputProps("password")}
confirmPasswordInputProps={form.getInputProps("confirmPassword")}
/>
<PasswordInput label={t("field.passwordConfirm.label")} {...form.getInputProps("confirmPassword")} />
<Button type="submit" fullWidth loading={isPending}>
{t("action.create")}
</Button>
@@ -1,11 +1,10 @@
import { Card } from "@mantine/core";
import { InitStepCard } from "../../_components/init-step-card";
import { InitUserForm } from "./init-user-form";
export const InitUser = () => {
return (
<Card w={64 * 6} maw="90vw" withBorder>
<InitStepCard>
<InitUserForm />
</Card>
</InitStepCard>
);
};
+4 -1
View File
@@ -9,10 +9,12 @@ import { getScopedI18n } from "@homarr/translation/server";
import { CurrentColorSchemeCombobox } from "~/components/color-scheme/current-color-scheme-combobox";
import { CurrentLanguageCombobox } from "~/components/language/current-language-combobox";
import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo";
import { onboardingContentWidth, onboardingMaxWidth } from "./_constants";
import { BackToStart } from "./_steps/back";
import { InitFinish } from "./_steps/finish/init-finish";
import { InitGroup } from "./_steps/group/init-group";
import { InitImport } from "./_steps/import/init-import";
import { InitIntegrations } from "./_steps/integrations/init-integrations";
import { InitSettings } from "./_steps/settings/init-settings";
import { InitStart } from "./_steps/start/init-start";
import { InitUser } from "./_steps/user/init-user";
@@ -23,6 +25,7 @@ const stepComponents: Record<OnboardingStep, null | (() => MaybePromise<JSX.Elem
user: InitUser,
group: InitGroup,
settings: InitSettings,
integrations: InitIntegrations,
finish: InitFinish,
};
@@ -35,7 +38,7 @@ export default async function InitPage() {
return (
<Box mih="100dvh">
<Center>
<Stack align="center" mt="xl">
<Stack align="center" mt="xl" w={onboardingContentWidth} maw={onboardingMaxWidth}>
<HomarrLogoWithTitle size="lg" />
<Stack gap={6} align="center">
<Title order={3} fw={400} ta="center">
+10 -5
View File
@@ -1,9 +1,11 @@
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import "@gfazioli/mantine-onboarding-tour/styles.css";
import "@homarr/notifications/styles.css";
import "@homarr/spotlight/styles.css";
import "@homarr/ui/styles.css";
import "~/styles/color-scheme.scss";
import "~/styles/scroll-area.scss";
import { notFound } from "next/navigation";
@@ -24,7 +26,9 @@ import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
import { Analytics } from "~/components/layout/analytics";
import { CrowdinLiveTranslation } from "~/components/layout/crowdin-live-translation";
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
import { ServiceWorkerRegistration } from "~/components/layout/service-worker-registration";
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
import { DayJsLoader } from "./_client-providers/dayjs-loader";
import { JotaiProvider } from "./_client-providers/jotai";
@@ -42,13 +46,13 @@ const fontSans = Inter({
export const generateMetadata = async (): Promise<Metadata> => ({
title: "Homarr",
description:
"Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
"A self-hosted dashboard for the *arr stack and your entire homelab. Integrates with 50+ services, real-time widgets, no config files.",
openGraph: {
title: "Homarr Dashboard",
description:
"Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
"A self-hosted dashboard for the *arr stack and your entire homelab. Integrates with 50+ services, real-time widgets, no config files.",
url: "https://homarr.dev",
siteName: "Homarr Documentation",
siteName: "Homarr",
},
icons: {
icon: "/logo/logo.png",
@@ -129,7 +133,7 @@ export default async function Layout(props: {
dir={direction}
data-mantine-color-scheme={colorScheme}
style={{
backgroundColor: colorScheme === "dark" ? "#242424" : "#fff",
backgroundColor: colorScheme === "dark" ? "#242424" : colorScheme === "auto" ? undefined : "#fff",
}}
suppressHydrationWarning
>
@@ -140,7 +144,8 @@ export default async function Layout(props: {
</head>
<body className={["font-sans", fontSans.variable].join(" ")}>
<StackedProvider>
<Notifications />
<Notifications pauseResetOnHover="notification" />
<ServiceWorkerRegistration />
{props.children}
</StackedProvider>
</body>
@@ -45,7 +45,6 @@ export const HeroBanner = async () => {
<Image
key={`grid-column-${columnIndex}-scroll-1-${index}`}
src={icon}
radius="md"
fit={"contain"}
w={50}
h={50}
@@ -57,7 +56,6 @@ export const HeroBanner = async () => {
<Image
key={`grid-column-${columnIndex}-scroll-2-${index}`}
src={icon}
radius="md"
fit={"contain"}
w={50}
h={50}
@@ -1,3 +1,7 @@
.contributorCard {
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
}
.dependencyCard:hover {
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
}
@@ -12,8 +12,7 @@ import {
Flex,
Group,
Kbd,
List,
ListItem,
SimpleGrid,
Stack,
Table,
TableTbody,
@@ -22,9 +21,18 @@ import {
TableThead,
TableTr,
Text,
ThemeIcon,
Title,
UnstyledButton,
} from "@mantine/core";
import { IconKeyboard, IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react";
import {
IconKeyboard,
IconLanguage,
IconLibrary,
IconMoneybagHeart,
IconPackage,
IconUsers,
} from "@tabler/icons-react";
import { capitalize, objectEntries } from "@homarr/common";
import { hotkeys } from "@homarr/definitions";
@@ -33,8 +41,10 @@ import { getScopedI18n } from "@homarr/translation/server";
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { createMetaTitle } from "~/metadata";
import { getPackageAttributesAsync } from "~/versions/package-reader";
import type { PackageJsonDependencies } from "~/versions/package-reader";
import { getPackageVersion } from "~/versions/package-reader";
import type githubContributorsJson from "../../../../../../../static-data/contributors.json";
import type openCollectiveContributorsJson from "../../../../../../../static-data/opencollective-contributors.json";
import type crowdinContributorsJson from "../../../../../../../static-data/translators.json";
import classes from "./about.module.css";
@@ -57,7 +67,10 @@ const getHostAsync = async () => {
export default async function AboutPage() {
const baseServerUrl = `http://${await getHostAsync()}`;
const t = await getScopedI18n("management.page.about");
const attributes = await getPackageAttributesAsync();
const version = getPackageVersion();
const dependencies = (await fetch(`${baseServerUrl}/api/about/dependencies`).then((res) =>
res.json(),
)) as PackageJsonDependencies;
const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>
res.json(),
)) as typeof githubContributorsJson;
@@ -66,6 +79,10 @@ export default async function AboutPage() {
res.json(),
)) as typeof crowdinContributorsJson;
const openCollectiveContributors = (await fetch(`${baseServerUrl}/api/about/contributors/opencollective`).then(
(res) => res.json(),
)) as typeof openCollectiveContributorsJson;
return (
<div>
<DynamicBreadcrumb />
@@ -73,10 +90,8 @@ export default async function AboutPage() {
<Group py="lg">
<Image src={homarrLogoPath} width={100} height={100} alt="" />
<Stack gap={0}>
<Title order={1} tt="uppercase">
Homarr
</Title>
<Title order={2}>{t("version", { version: attributes.version })}</Title>
<Title order={1}>Homarr</Title>
<Title order={2}>{t("version", { version })}</Title>
</Stack>
</Group>
</Center>
@@ -131,31 +146,72 @@ export default async function AboutPage() {
</Flex>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="sponsors">
<AccordionControl icon={<IconMoneybagHeart size="1rem" />}>
<Stack gap={0}>
<Text>{t("accordion.sponsors.title")}</Text>
<Text size="sm" c="dimmed">
{t("accordion.sponsors.subtitle", {
count: String(openCollectiveContributors.length),
})}
</Text>
</Stack>
</AccordionControl>
<AccordionPanel>
<Flex wrap="wrap" gap="xs">
{openCollectiveContributors.map((sponsor) => (
<GenericContributorLinkCard
key={sponsor.slug}
link={`https://opencollective.com/${sponsor.slug}`}
image={sponsor.imageUrl}
name={sponsor.name}
/>
))}
</Flex>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="libraries">
<AccordionControl icon={<IconLibrary size="1rem" />}>
<Stack gap={0}>
<Text>{t("accordion.libraries.title")}</Text>
<Text size="sm" c="dimmed">
{t("accordion.libraries.subtitle", {
count: String(Object.keys(attributes.dependencies).length),
count: String(Object.keys(dependencies).length),
})}
</Text>
</Stack>
</AccordionControl>
<AccordionPanel>
<List>
{Object.entries(attributes.dependencies)
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing="md">
{Object.entries(dependencies)
.filter(([, value]) => !value.includes("workspace:"))
.sort(([key1], [key2]) => key1.localeCompare(key2))
.map(([key, value]) => (
<ListItem key={key}>
{value.includes("workspace:") ? (
<Text>{key}</Text>
) : (
<a href={`https://www.npmjs.com/package/${key}`}>{key}</a>
)}
</ListItem>
.map(([name, version]) => (
<UnstyledButton
key={name}
component="a"
href={`https://www.npmjs.com/package/${name}`}
target="_blank"
>
<Card radius="md" p="xs" className={classes.dependencyCard}>
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" size="lg">
<IconPackage size="1.5rem" stroke={1.5} />
</ThemeIcon>
<Stack gap={0}>
<Text size="sm" fw="bold" lineClamp={1} title={name}>
{name}
</Text>
<Text size="sm" c="dimmed">
v{version.replace("^", "").replace("~", "")}
</Text>
</Stack>
</Group>
</Card>
</UnstyledButton>
))}
</List>
</SimpleGrid>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="hotkeys">
@@ -1,6 +1,6 @@
import { Fragment } from "react";
import { redirect } from "next/navigation";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text } from "@mantine/core";
import { IconBox, IconPencil } from "@tabler/icons-react";
import { z } from "zod/v4";
@@ -11,9 +11,9 @@ import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { Link, SearchInput, TablePagination } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container";
import { TourTarget } from "~/components/layout/header/tour-target";
import { ManagePageLayout } from "~/components/manage/manage-page-layout";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results";
import { AppDeleteButton } from "./_app-delete-button";
@@ -39,34 +39,35 @@ export default async function AppsPage(props: AppsPageProps) {
const { items: apps, totalCount } = await api.app.getPaginated(searchParams);
const t = await getScopedI18n("app");
const canCreate = session.user.permissions.includes("app-create");
return (
<ManageContainer>
<DynamicBreadcrumb />
<Stack>
<Title>{t("page.list.title")}</Title>
<Group justify="space-between" align="center">
<SearchInput placeholder={`${t("search")}...`} defaultValue={searchParams.search} flexExpand />
{session.user.permissions.includes("app-create") && (
<ManagePageLayout
title={t("page.list.title")}
primaryAction={
canCreate ? (
<TourTarget id="manage-apps-create">
<MobileAffixButton component={Link} href="/manage/apps/new">
{t("page.create.title")}
</MobileAffixButton>
)}
</Group>
{apps.length === 0 && <AppNoResults />}
{apps.length > 0 && (
</TourTarget>
) : undefined
}
toolbar={<SearchInput placeholder={`${t("search")}...`} defaultValue={searchParams.search} flexExpand />}
footer={<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />}
floatingPrimaryAction={canCreate}
>
{apps.length === 0 && <AppNoResults />}
{apps.length > 0 && (
<TourTarget id="manage-apps-list">
<Stack gap="sm">
{apps.map((app) => (
<AppCard key={app.id} app={app} />
))}
</Stack>
)}
{/* Added margin to not hide pagination behind affix-button */}
<Group justify="end" mb={48}>
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
</Group>
</Stack>
</ManageContainer>
</TourTarget>
)}
</ManagePageLayout>
);
}
@@ -79,7 +80,7 @@ const AppCard = async ({ app }: AppCardProps) => {
const session = await auth();
return (
<Card withBorder>
<Card>
<Group justify="space-between" wrap="nowrap">
<Group align="top" justify="start" wrap="nowrap" style={{ flex: "1" }}>
<Avatar
@@ -1,43 +1,38 @@
"use client";
import { Affix, Button, Menu } from "@mantine/core";
import { Button, Menu } from "@mantine/core";
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
import { useModalAction } from "@homarr/modals";
import { AddBoardModal, ImportBoardModal } from "@homarr/modals-collection";
import { useI18n } from "@homarr/translation/client";
import { ManageMobilePrimaryAction } from "~/components/manage/manage-mobile-primary-action";
export const CreateBoardButton = () => {
const t = useI18n();
const { openModal: openAddModal } = useModalAction(AddBoardModal);
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
const buttonGroupContent = (
<>
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={openAddModal}>
{t("management.page.board.action.new.label")}
</Button>
<Menu position="bottom-end">
<Menu.Target>
<Button px="xs" ms={1}>
<IconChevronDown size="1rem" />
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={openImportModal} leftSection={<IconFileImport size="1rem" />}>
{t("board.action.oldImport.label")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
</>
);
return (
<>
<Button.Group visibleFrom="md">{buttonGroupContent}</Button.Group>
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
<Button.Group>{buttonGroupContent}</Button.Group>
</Affix>
</>
<ManageMobilePrimaryAction>
<Button.Group>
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={openAddModal}>
{t("management.page.board.action.new.label")}
</Button>
<Menu position="bottom-end">
<Menu.Target>
<Button px="xs" ms={1}>
<IconChevronDown size="1rem" />
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={openImportModal} leftSection={<IconFileImport size="1rem" />}>
{t("board.action.oldImport.label")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Button.Group>
</ManageMobilePrimaryAction>
);
};
@@ -9,12 +9,17 @@ import {
Group,
Menu,
MenuTarget,
Stack,
Text,
Title,
Tooltip,
} from "@mantine/core";
import { IconDeviceMobile, IconDotsVertical, IconHomeFilled, IconLock, IconWorld } from "@tabler/icons-react";
import {
IconDeviceMobile,
IconDotsVertical,
IconHomeFilled,
IconLayoutDashboard,
IconLock,
IconWorld,
} from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { api } from "@homarr/api/server";
@@ -23,8 +28,9 @@ import { getScopedI18n } from "@homarr/translation/server";
import { Link, UserAvatar } from "@homarr/ui";
import { getBoardPermissionsAsync } from "~/components/board/permissions/server";
import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { TourTarget } from "~/components/layout/header/tour-target";
import { ManagePageLayout } from "~/components/manage/manage-page-layout";
import { NoResults } from "~/components/no-results";
import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown";
import { CreateBoardButton } from "./_components/create-board-button";
@@ -35,23 +41,30 @@ export default async function ManageBoardsPage() {
const canCreateBoards = session?.user.permissions.includes("board-create");
return (
<ManageContainer>
<DynamicBreadcrumb />
<Stack>
<Group justify="space-between">
<Title mb="md">{t("title")}</Title>
{canCreateBoards && <CreateBoardButton />}
</Group>
<Grid mb={{ base: "xl", md: 0 }}>
{boards.map((board) => (
<GridCol span={{ base: 12, md: 6 }} key={board.id}>
<BoardCard board={board} />
</GridCol>
))}
</Grid>
</Stack>
</ManageContainer>
<ManagePageLayout
title={t("title")}
primaryAction={
canCreateBoards ? (
<TourTarget id="manage-boards-create">
<CreateBoardButton />
</TourTarget>
) : undefined
}
floatingPrimaryAction={canCreateBoards}
>
{boards.length === 0 && <NoResults icon={IconLayoutDashboard} title={t("noResults.title")} />}
{boards.length > 0 && (
<TourTarget id="manage-boards-list">
<Grid>
{boards.map((board) => (
<GridCol span={{ base: 12, md: 6 }} key={board.id}>
<BoardCard board={board} />
</GridCol>
))}
</Grid>
</TourTarget>
)}
</ManagePageLayout>
);
}
@@ -66,16 +79,14 @@ const BoardCard = async ({ board }: BoardCardProps) => {
const VisibilityIcon = board.isPublic ? IconWorld : IconLock;
return (
<Card radius="lg" withBorder>
<Card>
<CardSection p="sm" withBorder>
<Group justify="space-between" align="center">
<Group gap="sm">
<Tooltip label={t(`visibility.${visibility}`)}>
<VisibilityIcon size={20} stroke={1.5} />
</Tooltip>
<Text fw="bolder" tt="uppercase">
{board.name}
</Text>
<Text fw={700}>{board.name}</Text>
</Group>
<Group>
@@ -123,6 +134,7 @@ const BoardCard = async ({ board }: BoardCardProps) => {
style={{ borderTop: "none", borderBottom: "none", borderRight: "none", borderRadius: 0 }}
variant="default"
size="lg"
aria-label={t("action.settings.label")}
>
<IconDotsVertical size={16} stroke={1.5} />
</ActionIcon>
@@ -33,7 +33,7 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
const KindIcon = integrationSecretIcons[secret.kind];
return (
<Card withBorder>
<Card>
<Stack>
<Group justify="space-between">
<Group>
@@ -58,7 +58,12 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
</Tooltip>
)}
{isPublic ? (
<ActionIcon color="gray" variant="subtle" onClick={togglePublicSecretDisplay}>
<ActionIcon
color="gray"
variant="subtle"
onClick={togglePublicSecretDisplay}
aria-label={t(publicSecretDisplayOpened ? "common.action.hide" : "common.action.show")}
>
<DisplayIcon size={16} stroke={1.5} />
</ActionIcon>
) : null}
@@ -79,7 +84,7 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
</Button>
</Group>
</Group>
<Collapse in={editMode}>{children}</Collapse>
<Collapse expanded={editMode}>{children}</Collapse>
</Stack>
</Card>
);
@@ -12,7 +12,7 @@ interface FormType {
interface SecretKindsSegmentedControlProps<TFormType extends FormType> {
defaultKinds?: IntegrationSecretKind[];
secretKinds: IntegrationSecretKind[][];
form: UseFormReturnType<TFormType, (values: TFormType) => TFormType>;
form: UseFormReturnType<TFormType, TFormType>;
}
export const SecretKindsSegmentedControl = <TFormType extends FormType>({
@@ -18,7 +18,7 @@ export const IntegrationTestConnectionError = ({ error, url }: IntegrationTestCo
const causeArray = useMemo(() => toCauseArray(error.cause), [error.cause]);
return (
<Card withBorder style={{ borderColor: getMantineColor("red", 8) }}>
<Card style={{ borderColor: getMantineColor("red", 8) }}>
<Stack>
<Stack gap="sm">
<Text size="lg" fw={500} c="red.8">
@@ -216,7 +216,7 @@ export const CertificateDetailsCard = ({ certificate }: CertificateDetailsProps)
const tCertificateField = useScopedI18n("certificate.field");
return (
<Card withBorder>
<Card>
<Text fw={500}>{tDetails("title")}</Text>
<Group justify="space-between">
<Text size="sm" c="dimmed">
@@ -1,59 +0,0 @@
"use client";
import type { ChangeEvent } from "react";
import React, { useMemo, useState } from "react";
import { Flex, Group, Menu, ScrollArea, Text, TextInput } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
import { IntegrationAvatar, Link } from "@homarr/ui";
interface IntegrationCreateDropdownContentProps {
enableMockIntegration: boolean;
}
export const IntegrationCreateDropdownContent = ({ enableMockIntegration }: IntegrationCreateDropdownContentProps) => {
const t = useI18n();
const [search, setSearch] = useState("");
const filteredKinds = useMemo(() => {
return integrationKinds
.filter((kind) => enableMockIntegration || kind !== "mock")
.filter((kind) => getIntegrationName(kind).toLowerCase().includes(search.toLowerCase().trim()))
.sort((kindA, kindB) => getIntegrationName(kindA).localeCompare(getIntegrationName(kindB)));
}, [search, enableMockIntegration]);
const handleSearch = React.useCallback(
(event: ChangeEvent<HTMLInputElement>) => setSearch(event.target.value),
[setSearch],
);
return (
<Flex direction={{ base: "column-reverse", md: "column" }} gap="sm">
<TextInput
leftSection={<IconSearch stroke={1.5} size={20} />}
placeholder={t("integration.page.list.search")}
value={search}
data-autofocus
onChange={handleSearch}
variant="filled"
/>
{filteredKinds.length > 0 ? (
<ScrollArea.Autosize mah={384}>
{filteredKinds.map((kind) => (
<Menu.Item component={Link} href={`/manage/integrations/new?kind=${kind}`} key={kind}>
<Group>
<IntegrationAvatar kind={kind} size="sm" />
<Text size="sm">{getIntegrationName(kind)}</Text>
</Group>
</Menu.Item>
))}
</ScrollArea.Autosize>
) : (
<Menu.Item disabled>{t("common.noResults")}</Menu.Item>
)}
</Flex>
);
};
@@ -0,0 +1,26 @@
"use client";
import { useRouter } from "next/navigation";
import type { IntegrationKind } from "@homarr/definitions";
import { NewIntegrationForm } from "./_integration-new-form";
interface IntegrationNewFormWrapperProps {
kind: IntegrationKind;
initialUrl?: string;
initialName?: string;
}
export const IntegrationNewFormWrapper = ({ kind, initialUrl, initialName }: IntegrationNewFormWrapperProps) => {
const router = useRouter();
return (
<NewIntegrationForm
kind={kind}
initialUrl={initialUrl}
initialName={initialName}
onSuccess={() => router.push("/manage/integrations")}
/>
);
};
@@ -1,9 +1,9 @@
"use client";
import { startTransition, useState } from "react";
import { useRouter } from "next/navigation";
import {
Alert,
Anchor,
Button,
Checkbox,
Collapse,
@@ -16,7 +16,7 @@ import {
Text,
TextInput,
} from "@mantine/core";
import { IconCheck, IconInfoCircle } from "@tabler/icons-react";
import { IconCheck, IconExternalLink, IconInfoCircle, IconKey } from "@tabler/icons-react";
import { z } from "zod/v4";
import { clientApi } from "@homarr/api/client";
@@ -27,6 +27,7 @@ import type { IntegrationKind } from "@homarr/definitions";
import {
getAllSecretKindOptions,
getIconUrl,
getIntegrationApiKeyUrl,
getIntegrationDefaultUrl,
getIntegrationName,
integrationDefs,
@@ -45,9 +46,13 @@ import { IntegrationTestConnectionError } from "../_components/test-connection/i
import type { AnyMappedTestConnectionError } from "../_components/test-connection/types";
interface NewIntegrationFormProps {
searchParams: Partial<z.infer<typeof integrationCreateSchema>> & {
kind: IntegrationKind;
};
kind: IntegrationKind;
initialUrl?: string;
initialName?: string;
onSuccess: () => void;
onCancel?: () => void;
onSkip?: () => void;
isOnboarding?: boolean;
}
const formSchema = integrationCreateSchema.omit({ kind: true, app: true }).and(
@@ -58,39 +63,48 @@ const formSchema = integrationCreateSchema.omit({ kind: true, app: true }).and(
}),
);
export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => {
export const NewIntegrationForm = ({
kind,
initialUrl,
initialName,
onSuccess,
onCancel,
onSkip,
isOnboarding = false,
}: NewIntegrationFormProps) => {
const t = useI18n();
const secretKinds = getAllSecretKindOptions(searchParams.kind);
const secretKinds = getAllSecretKindOptions(kind);
const hasUrlSecret = secretKinds.some((kinds) => kinds.includes("url"));
const router = useRouter();
const { data: session } = useSession();
const canCreateApps = session?.user.permissions.includes("app-create") ?? false;
const canCreateApps = !isOnboarding && (session?.user.permissions.includes("app-create") ?? false);
let url = searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "";
let url = initialUrl ?? getIntegrationDefaultUrl(kind) ?? "";
if (hasUrlSecret) {
// Placeholder Url, replaced with origin of the secret Url on submit
url = "http://localhost";
}
const form = useZodForm(formSchema, {
initialValues: {
name: searchParams.name ?? getIntegrationName(searchParams.kind),
name: initialName ?? getIntegrationName(kind),
url,
secrets: secretKinds[0].map((kind) => ({
kind,
value: "",
})),
attemptSearchEngineCreation: true,
hasApp: false,
appHref: url,
attemptSearchEngineCreation: !isOnboarding,
hasApp: !isOnboarding,
appHref: isOnboarding ? null : url,
appId: null,
},
});
const { mutateAsync: createIntegrationAsync, isPending } = clientApi.integration.create.useMutation({
const { mutateAsync: createIntegrationAsync, isPending: isCreatePending } = clientApi.integration.create.useMutation({
async onSuccess() {
await revalidatePathActionAsync("/manage/integrations");
},
});
const { mutateAsync: createOnboardingIntegrationAsync, isPending: isOnboardingCreatePending } =
clientApi.onboard.createIntegration.useMutation();
const isPending = isCreatePending || isOnboardingCreatePending;
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
const handleSubmitAsync = async ({ appId, appHref, hasApp, ...values }: FormType) => {
@@ -98,6 +112,39 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin
: values.url;
const onMutationSuccess = (data: { error?: AnyMappedTestConnectionError } | undefined | void) => {
if (data && "error" in data && 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"),
});
onSuccess();
};
const onMutationError = () => {
showErrorNotification({
title: t("integration.page.create.notification.error.title"),
message: t("integration.page.create.notification.error.message"),
});
};
if (isOnboarding) {
await createOnboardingIntegrationAsync(
{ kind, name: values.name, url, secrets: values.secrets },
{ onSuccess: onMutationSuccess, onError: onMutationError },
);
return;
}
const hasCustomHref = appHref !== null && appHref.trim().length >= 1;
const app = hasApp
@@ -106,49 +153,21 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
: {
name: values.name,
href: hasCustomHref ? appHref : url,
iconUrl: getIconUrl(searchParams.kind),
iconUrl: getIconUrl(kind),
description: null,
pingUrl: url,
}
: undefined;
await createIntegrationAsync(
{
kind: searchParams.kind,
...values,
url,
app,
},
{
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"),
});
router.push("/manage/integrations");
},
onError: () => {
showErrorNotification({
title: t("integration.page.create.notification.error.title"),
message: t("integration.page.create.notification.error.message"),
});
},
},
{ kind, ...values, url, app },
{ onSuccess: onMutationSuccess, onError: onMutationError },
);
};
const supportsSearchEngine = integrationDefs[searchParams.kind].category.flat().includes("search");
const integrationCategories = integrationDefs[kind].category.flat();
const supportsSearchEngine =
integrationCategories.includes("search") && !integrationCategories.includes("mediaSearch");
return (
<form onSubmit={form.onSubmit((value) => void handleSubmitAsync(value))}>
@@ -175,27 +194,39 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
<Text c={"blue"}>{t("integration.secrets.noSecretsRequired.text")}</Text>
</Alert>
)}
<ApiKeySettingsLink kind={kind} url={form.values.url} />
</Stack>
</Fieldset>
{error !== null && <IntegrationTestConnectionError error={error} url={form.values.url} />}
{supportsSearchEngine && (
{!isOnboarding && supportsSearchEngine && (
<Checkbox
label={t("integration.field.attemptSearchEngineCreation.label")}
description={t("integration.field.attemptSearchEngineCreation.description", {
kind: getIntegrationName(searchParams.kind),
kind: getIntegrationName(kind),
})}
{...form.getInputProps("attemptSearchEngineCreation", { type: "checkbox" })}
/>
)}
<AppForm form={form} canCreateApps={canCreateApps} />
{!isOnboarding && <AppForm form={form} canCreateApps={canCreateApps} />}
<Group justify="end" align="center">
<Button variant="default" component={Link} href="/manage/integrations">
{t("common.action.backToOverview")}
</Button>
{onCancel ? (
<Button variant="default" onClick={onCancel}>
{t("common.action.backToOverview")}
</Button>
) : (
<Button variant="default" component={Link} href="/manage/integrations">
{t("common.action.backToOverview")}
</Button>
)}
{onSkip && (
<Button variant="default" onClick={onSkip}>
{t("common.action.skip")}
</Button>
)}
<Button type="submit" loading={isPending}>
{t("integration.testConnection.action.create")}
</Button>
@@ -226,7 +257,7 @@ const AppForm = ({ form, canCreateApps }: { form: UseFormReturnType<FormType>; c
description={t("integration.field.createApp.description")}
/>
<Collapse in={form.values.hasApp}>
<Collapse expanded={form.values.hasApp}>
<Fieldset legend={t("integration.field.app.sectionTitle")}>
<Stack gap="sm">
{canCreateApps && (
@@ -266,6 +297,40 @@ const AppForm = ({ form, canCreateApps }: { form: UseFormReturnType<FormType>; c
);
};
const normalizeUrl = (raw: string): string | null => {
try {
return new URL(raw).href;
} catch {
// Bare IP / hostname — prepend http:// and retry
}
try {
return new URL(`http://${raw}`).href;
} catch {
return null;
}
};
const ApiKeySettingsLink = ({ kind, url }: { kind: IntegrationKind; url: string }) => {
const t = useI18n();
const apiKeyUrl = getIntegrationApiKeyUrl(url, kind);
if (!apiKeyUrl) return null;
const resolved = normalizeUrl(url);
if (!resolved) return null;
const fullApiKeyUrl = getIntegrationApiKeyUrl(resolved, kind);
return (
<Anchor href={fullApiKeyUrl ?? apiKeyUrl} target="_blank" rel="noopener noreferrer" size="sm">
<Group gap={4}>
<IconKey size={14} stroke={1.5} />
<Text size="sm">{t("integration.field.apiKeySettings.label")}</Text>
<IconExternalLink size={14} stroke={1.5} />
</Group>
</Anchor>
);
};
type IntegrationAppSelectProps = Modify<
GetInputPropsReturnType,
{
@@ -0,0 +1,21 @@
"use client";
import { useRouter } from "next/navigation";
import type { IntegrationKind } from "@homarr/definitions";
import { IntegrationSelectGrid } from "~/components/integration/integration-select-grid";
interface IntegrationNewSelectGridProps {
enableMockIntegration: boolean;
}
export const IntegrationNewSelectGrid = ({ enableMockIntegration }: IntegrationNewSelectGridProps) => {
const router = useRouter();
const handleSelect = (kind: IntegrationKind) => {
router.push(`/manage/integrations/new?kind=${kind}`);
};
return <IntegrationSelectGrid onSelect={handleSelect} enableMockIntegration={enableMockIntegration} />;
};
@@ -3,21 +3,21 @@ import { Container, Group, Stack, Title } from "@mantine/core";
import { z } from "zod/v4";
import { auth } from "@homarr/auth/next";
import type { IntegrationKind } from "@homarr/definitions";
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { IntegrationAvatar } from "@homarr/ui";
import type { integrationCreateSchema } from "@homarr/validation/integration";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NewIntegrationForm } from "./_integration-new-form";
import { env } from "~/env";
import { IntegrationNewFormWrapper } from "./_integration-new-form-wrapper";
import { IntegrationNewSelectGrid } from "./_integration-new-select-grid";
interface NewIntegrationPageProps {
searchParams: Promise<
Partial<z.infer<typeof integrationCreateSchema>> & {
kind: IntegrationKind;
}
>;
searchParams: Promise<{
kind?: string;
url?: string;
name?: string;
}>;
}
export default async function IntegrationsNewPage(props: NewIntegrationPageProps) {
@@ -28,8 +28,20 @@ export default async function IntegrationsNewPage(props: NewIntegrationPageProps
}
const result = z.enum(integrationKinds).safeParse(searchParams.kind);
if (!result.success) {
notFound();
const t = await getScopedI18n("integration");
return (
<>
<DynamicBreadcrumb />
<Container>
<Stack>
<Title>{t("action.create")}</Title>
<IntegrationNewSelectGrid enableMockIntegration={env.UNSAFE_ENABLE_MOCK_INTEGRATION} />
</Stack>
</Container>
</>
);
}
const tCreate = await getScopedI18n("integration.page.create");
@@ -45,7 +57,7 @@ export default async function IntegrationsNewPage(props: NewIntegrationPageProps
<IntegrationAvatar kind={currentKind} size="md" />
<Title>{tCreate("title", { name: getIntegrationName(currentKind) })}</Title>
</Group>
<NewIntegrationForm searchParams={searchParams} />
<IntegrationNewFormWrapper kind={currentKind} initialUrl={searchParams.url} initialName={searchParams.name} />
</Stack>
</Container>
</>
@@ -8,7 +8,7 @@
border: 1px solid transparent;
position: relative;
z-index: 0;
transition: transform 150ms ease;
transition: background-color 150ms ease;
overflow: hidden;
&[data-first="true"] {
@@ -20,11 +20,9 @@
}
&[data-active] {
transform: scale(1.01);
z-index: 1;
background-color: var(--mantine-color-body);
border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
box-shadow: var(--mantine-shadow-md);
border-radius: var(--mantine-radius-lg);
}
}
@@ -1,5 +1,4 @@
import { Fragment } from "react";
import type { PropsWithChildren } from "react";
import { redirect } from "next/navigation";
import {
AccordionControl,
@@ -7,15 +6,9 @@ import {
AccordionPanel,
ActionIcon,
ActionIconGroup,
Affix,
Anchor,
Box,
Button,
Divider,
Group,
Menu,
MenuDropdown,
MenuTarget,
Stack,
Table,
TableTbody,
@@ -24,9 +17,8 @@ import {
TableThead,
TableTr,
Text,
Title,
} from "@mantine/core";
import { IconChevronDown, IconChevronUp, IconPencil, IconPlugX } from "@tabler/icons-react";
import { IconPencil, IconPlugX } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { api } from "@homarr/api/server";
@@ -37,13 +29,12 @@ import { getIntegrationName } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { CountBadge, IntegrationAvatar, Link } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { TourTarget } from "~/components/layout/header/tour-target";
import { ManagePageLayout } from "~/components/manage/manage-page-layout";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
import { NoResults } from "~/components/no-results";
import { env } from "~/env";
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
import { DeleteIntegrationActionButton } from "./_integration-buttons";
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
import classes from "./page.module.css";
interface IntegrationsPageProps {
@@ -66,60 +57,26 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) {
const canCreateIntegrations = session.user.permissions.includes("integration-create");
return (
<ManageContainer>
<DynamicBreadcrumb />
<Stack>
<Group justify="space-between" align="center">
<Title>{t("page.list.title")}</Title>
{canCreateIntegrations && (
<>
<Box>
<IntegrationSelectMenu>
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
<MenuTarget>
<Button rightSection={<IconChevronUp size={16} stroke={1.5} />}>{t("action.create")}</Button>
</MenuTarget>
</Affix>
</IntegrationSelectMenu>
</Box>
<Box visibleFrom="md">
<IntegrationSelectMenu>
<MenuTarget>
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
</MenuTarget>
</IntegrationSelectMenu>
</Box>
</>
)}
</Group>
<ManagePageLayout
title={t("page.list.title")}
primaryAction={
canCreateIntegrations ? (
<TourTarget id="manage-integrations-create">
<MobileAffixButton component={Link} href="/manage/integrations/new">
{t("action.create")}
</MobileAffixButton>
</TourTarget>
) : undefined
}
floatingPrimaryAction={canCreateIntegrations}
>
<TourTarget id="manage-integrations-list">
<IntegrationList integrations={integrations} activeTab={searchParams.tab} />
</Stack>
</ManageContainer>
</TourTarget>
</ManagePageLayout>
);
}
const IntegrationSelectMenu = ({ children }: PropsWithChildren) => {
return (
<Menu
width={256}
trapFocus
position="bottom-end"
withinPortal
shadow="md"
keepMounted={false}
withInitialFocusPlaceholder={false}
>
{children}
<MenuDropdown>
<IntegrationCreateDropdownContent enableMockIntegration={env.UNSAFE_ENABLE_MOCK_INTEGRATION} />
</MenuDropdown>
</Menu>
);
};
interface IntegrationListProps {
integrations: RouterOutputs["integration"]["all"];
activeTab?: IntegrationKind;
@@ -152,12 +109,12 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
return (
<ActiveTabAccordion defaultValue={activeTab} radius="lg" classNames={classes}>
{entries.map(([kind, integrations], index) => (
{entries.map(([kind, kindIntegrations], index) => (
<AccordionItem key={kind} value={kind} data-first={index === 0} data-last={index === entries.length - 1}>
<AccordionControl icon={<IntegrationAvatar size="sm" kind={kind} radius="sm" />}>
<Group>
<Text>{getIntegrationName(kind)}</Text>
<CountBadge count={integrations.length} />
<CountBadge count={kindIntegrations.length} />
</Group>
</AccordionControl>
<AccordionPanel>
@@ -170,7 +127,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
</TableTr>
</TableThead>
<TableTbody>
{integrations.map((integration) => (
{kindIntegrations.map((integration) => (
<TableTr key={integration.id}>
<TableTd>{integration.name}</TableTd>
<TableTd>
@@ -191,7 +148,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
>
<IconPencil size={16} stroke={1.5} />
</ActionIcon>
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
<DeleteIntegrationActionButton integration={integration} count={kindIntegrations.length} />
</ActionIconGroup>
)}
</Group>
@@ -202,9 +159,9 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
</Table>
<Stack gap="xs" hiddenFrom="md">
{integrations.map((integration, index) => (
{kindIntegrations.map((integration, integrationIndex) => (
<Fragment key={integration.id}>
{index !== 0 && <Divider />}
{integrationIndex !== 0 && <Divider />}
<Stack gap={0}>
<Group justify="space-between" align="center" wrap="nowrap">
<Text>{integration.name}</Text>
@@ -219,7 +176,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
>
<IconPencil size={16} stroke={1.5} />
</ActionIcon>
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
<DeleteIntegrationActionButton integration={integration} count={kindIntegrations.length} />
</ActionIconGroup>
)}
</Group>
+16 -1
View File
@@ -36,6 +36,7 @@ import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
import type { NavigationLink } from "~/components/layout/navigation";
import { MainNavigation } from "~/components/layout/navigation";
import { ClientShell } from "~/components/layout/shell";
import { ManageTourProvider } from "~/components/onboarding/manage-tour";
export default async function ManageLayout({ children }: PropsWithChildren) {
const t = await getScopedI18n("management.navbar");
@@ -45,11 +46,13 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
label: t("items.home"),
icon: IconHomeFilled,
href: "/manage",
"data-onboarding-tour-id": "manage-welcome",
},
{
icon: IconLayoutDashboardFilled,
href: "/manage/boards",
label: t("items.boards"),
"data-onboarding-tour-id": "manage-boards",
},
{
icon: IconBox,
@@ -59,12 +62,14 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
iconProps: {
strokeWidth: 2.5,
},
"data-onboarding-tour-id": "manage-apps",
},
{
icon: IconAffiliateFilled,
href: "/manage/integrations",
label: t("items.integrations"),
hidden: !session,
"data-onboarding-tour-id": "manage-integrations",
},
{
icon: IconSearch,
@@ -74,17 +79,20 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
iconProps: {
strokeWidth: 2.5,
},
"data-onboarding-tour-id": "manage-search-engines",
},
{
icon: IconPhotoFilled,
href: "/manage/medias",
label: t("items.medias"),
hidden: !session,
"data-onboarding-tour-id": "manage-medias",
},
{
icon: IconUserFilled,
label: t("items.users.label"),
hidden: !session?.user.permissions.includes("admin"),
"data-onboarding-tour-id": "manage-users",
items: [
{
label: t("items.users.items.manage"),
@@ -153,6 +161,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
href: "/manage/settings",
icon: IconSettingsFilled,
hidden: !session?.user.permissions.includes("admin"),
"data-onboarding-tour-id": "manage-settings",
},
{
label: t("items.help.label"),
@@ -191,11 +200,17 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
},
];
return (
const isAdmin = session?.user.permissions.includes("admin") ?? false;
const shell = (
<ClientShell hasNavigation>
<MainHeader></MainHeader>
<MainNavigation links={navigationLinks}></MainNavigation>
<AppShellMain>{children}</AppShellMain>
</ClientShell>
);
if (!session) return shell;
return <ManageTourProvider isAdmin={isAdmin}>{shell}</ManageTourProvider>;
}
@@ -4,17 +4,15 @@ import {
Anchor,
Group,
Image,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Title,
Tooltip,
} from "@mantine/core";
import { IconExternalLink } from "@tabler/icons-react";
import { IconExternalLink, IconPhoto } from "@tabler/icons-react";
import { z } from "zod/v4";
import type { RouterOutputs } from "@homarr/api";
@@ -26,8 +24,9 @@ import { createLocalImageUrl } from "@homarr/icons/local";
import { getI18n } from "@homarr/translation/server";
import { Link, SearchInput, TablePagination, UserAvatar } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { ManageMobilePrimaryAction } from "~/components/manage/manage-mobile-primary-action";
import { ManagePageLayout } from "~/components/manage/manage-page-layout";
import { NoResults } from "~/components/no-results";
import { CopyMedia } from "./_actions/copy-media";
import { DeleteMedia } from "./_actions/delete-media";
import { IncludeFromAllUsersSwitch } from "./_actions/show-all";
@@ -48,7 +47,7 @@ interface MediaListPageProps {
searchParams: Promise<inferSearchParamsFromSchema<typeof searchParamsSchema>>;
}
export default async function GroupsListPage(props: MediaListPageProps) {
export default async function MediaListPage(props: MediaListPageProps) {
const session = await auth();
if (!session) {
@@ -58,22 +57,31 @@ export default async function GroupsListPage(props: MediaListPageProps) {
const t = await getI18n();
const searchParams = searchParamsSchema.parse(await props.searchParams);
const { items: medias, totalCount } = await api.media.getPaginated(searchParams);
const canUpload = session.user.permissions.includes("media-upload");
return (
<ManageContainer>
<DynamicBreadcrumb />
<Stack>
<Title>{t("media.plural")}</Title>
<Group justify="space-between">
<Group>
<SearchInput placeholder={`${t("media.search")}...`} defaultValue={searchParams.search} />
{session.user.permissions.includes("media-view-all") && (
<IncludeFromAllUsersSwitch defaultChecked={searchParams.includeFromAllUsers} />
)}
</Group>
{session.user.permissions.includes("media-upload") && <UploadMediaButton />}
<ManagePageLayout
title={t("media.plural")}
primaryAction={
canUpload ? (
<ManageMobilePrimaryAction>
<UploadMediaButton />
</ManageMobilePrimaryAction>
) : undefined
}
toolbar={
<Group>
<SearchInput placeholder={`${t("media.search")}...`} defaultValue={searchParams.search} />
{session.user.permissions.includes("media-view-all") && (
<IncludeFromAllUsersSwitch defaultChecked={searchParams.includeFromAllUsers} />
)}
</Group>
}
footer={<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />}
floatingPrimaryAction={canUpload}
>
{medias.length === 0 && <NoResults icon={IconPhoto} title={t("media.noResults.title")} />}
{medias.length > 0 && (
<Table striped highlightOnHover>
<TableThead>
<TableTr>
@@ -90,13 +98,8 @@ export default async function GroupsListPage(props: MediaListPageProps) {
))}
</TableTbody>
</Table>
{/* Added margin to not hide pagination behind affix-button */}
<Group justify="end" mb={48}>
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
</Group>
</Stack>
</ManageContainer>
)}
</ManagePageLayout>
);
}
@@ -7,9 +7,7 @@ export default async function NotFound() {
return (
<Center h="100%">
<Stack align="center">
<Title order={1} tt="uppercase">
{t("title")}
</Title>
<Title order={1}>{t("title")}</Title>
<Text>{t("text")}</Text>
</Stack>
</Center>
+5 -5
View File
@@ -29,20 +29,20 @@ export default async function ManagementPage() {
<Space h="md" />
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
{statistics.map((statistic) => (
<Card component={Link} href={statistic.path} key={statistic.path} radius="lg">
<Card component={Link} href={statistic.path} key={statistic.path}>
<Group justify="space-between" wrap="nowrap">
<Group wrap="nowrap">
<Text size="2.4rem" fw="bolder">
<Text size="xl" fw={700}>
{statistic.count}
</Text>
<Stack gap={0}>
<Text c="red" size="xs">
<Text c="dimmed" size="xs">
{t(`statisticLabel.${statistic.subtitleKey}`)}
</Text>
<Text fw="bold">{t(`statistic.${statistic.titleKey}`)}</Text>
<Text fw={600}>{t(`statistic.${statistic.titleKey}`)}</Text>
</Stack>
</Group>
<IconArrowRight />
<IconArrowRight size={16} stroke={1.5} color="var(--mantine-color-dimmed)" />
</Group>
</Card>
))}
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text } from "@mantine/core";
import { IconPencil, IconSearch } from "@tabler/icons-react";
import { z } from "zod/v4";
@@ -10,9 +10,8 @@ import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { Link, SearchInput, TablePagination } from "@homarr/ui";
import { ManageContainer } from "~/components/manage/manage-container";
import { ManagePageLayout } from "~/components/manage/manage-page-layout";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results";
import { SearchEngineDeleteButton } from "./_search-engine-delete-button";
@@ -38,34 +37,31 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
const tEngine = await getScopedI18n("search.engine");
return (
<ManageContainer>
<DynamicBreadcrumb />
<Stack>
<Title>{tEngine("page.list.title")}</Title>
<Group justify="space-between" align="center">
<SearchInput placeholder={`${tEngine("search")}...`} defaultValue={searchParams.search} flexExpand />
{session.user.permissions.includes("search-engine-create") && (
<MobileAffixButton component={Link} href="/manage/search-engines/new">
{tEngine("page.create.title")}
</MobileAffixButton>
)}
</Group>
{searchEngines.length === 0 && <SearchEngineNoResults />}
{searchEngines.length > 0 && (
<Stack gap="sm">
{searchEngines.map((searchEngine) => (
<SearchEngineCard key={searchEngine.id} searchEngine={searchEngine} />
))}
</Stack>
)}
const canCreate = session.user.permissions.includes("search-engine-create");
{/* Added margin to not hide pagination behind affix-button */}
<Group justify="end" mb={48}>
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
</Group>
</Stack>
</ManageContainer>
return (
<ManagePageLayout
title={tEngine("page.list.title")}
primaryAction={
canCreate ? (
<MobileAffixButton component={Link} href="/manage/search-engines/new">
{tEngine("page.create.title")}
</MobileAffixButton>
) : undefined
}
toolbar={<SearchInput placeholder={`${tEngine("search")}...`} defaultValue={searchParams.search} flexExpand />}
footer={<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />}
floatingPrimaryAction={canCreate}
>
{searchEngines.length === 0 && <SearchEngineNoResults />}
{searchEngines.length > 0 && (
<Stack gap="sm">
{searchEngines.map((searchEngine) => (
<SearchEngineCard key={searchEngine.id} searchEngine={searchEngine} />
))}
</Stack>
)}
</ManagePageLayout>
);
}
@@ -78,7 +74,7 @@ const SearchEngineCard = async ({ searchEngine }: SearchEngineCardProps) => {
const session = await auth();
return (
<Card withBorder>
<Card>
<Group justify="space-between" wrap="nowrap">
<Group align="top" justify="start" wrap="nowrap" style={{ flex: 1 }}>
<Avatar
@@ -41,8 +41,8 @@ export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
<>
<Title order={2}>{t("title")}</Title>
<Card pos="relative" withBorder>
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
<Card pos="relative">
<LoadingOverlay visible={isPending} />
<Stack>
<SwitchSetting form={form} formKey="enableGeneral" title={t("general.title")} text={t("general.text")} />
</Stack>
@@ -1,7 +1,7 @@
"use client";
import { Group, Text } from "@mantine/core";
import { IconMoon, IconSun } from "@tabler/icons-react";
import { IconDeviceDesktop, IconMoon, IconSun } from "@tabler/icons-react";
import type { ColorScheme } from "@homarr/definitions";
import { colorSchemes } from "@homarr/definitions";
@@ -34,6 +34,7 @@ export const AppearanceSettingsForm = ({ defaultValues }: { defaultValues: Serve
};
const appearanceIcons = {
auto: IconDeviceDesktop,
light: IconSun,
dark: IconMoon,
};
@@ -41,11 +41,11 @@ export const CrawlingAndIndexingSettings = ({ initialData }: CrawlingAndIndexing
<>
<Title order={2}>{t("title")}</Title>
<Card pos="relative" withBorder>
<Card pos="relative">
<Text c={"dimmed"} mb={"lg"}>
{t("warning")}
</Text>
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
<LoadingOverlay visible={isPending} />
<Stack>
<SwitchSetting form={form} formKey="noIndex" title={t("noIndex.title")} text={t("noIndex.text")} />
<SwitchSetting form={form} formKey="noFollow" title={t("noFollow.title")} text={t("noFollow.text")} />
@@ -15,7 +15,7 @@ export const SwitchSetting = <TFormValue extends Record<string, boolean>>({
disabled,
}: {
form: Modify<
UseFormReturnType<TFormValue, () => TFormValue>,
UseFormReturnType<TFormValue, TFormValue>,
{
setFieldValue: (key: keyof TFormValue, value: (previous: boolean) => boolean) => void;
}
@@ -2,7 +2,7 @@ import { headers } from "next/headers";
import { notFound } from "next/navigation";
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from "@mantine/core";
import { openApiDocument } from "@homarr/api";
import { openApiDocument } from "@homarr/api/open-api";
import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next";
import { extractBaseUrlFromHeaders } from "@homarr/common";
@@ -14,7 +14,7 @@ import { ApiKeysManagement } from "./components/api-keys";
export async function generateMetadata() {
const session = await auth();
if (!session?.user || !session.user.permissions.includes("admin")) {
if (!session?.user.permissions.includes("admin")) {
return {};
}
@@ -27,7 +27,7 @@ export async function generateMetadata() {
export default async function ApiPage() {
const session = await auth();
if (!session?.user || !session.user.permissions.includes("admin")) {
if (!session?.user.permissions.includes("admin")) {
notFound();
}
const document = openApiDocument(extractBaseUrlFromHeaders(await headers()));
@@ -79,7 +79,7 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
<SimpleGrid cols={{ sm: 1, lg: 2, xl: 3 }} spacing="lg">
{x509Certificates.map((cert) => (
<Card key={cert.fileName} withBorder>
<Card key={cert.fileName}>
<Group wrap="nowrap">
{cert.isError ? (
<IconAlertTriangle
@@ -1,23 +1,26 @@
"use client";
import { useRouter } from "next/navigation";
import type { MantineColor } from "@mantine/core";
import { Avatar, Badge, Box, Button, Group, Text } from "@mantine/core";
import { ActionIcon, Avatar, Badge, Box, Button, Group, Menu, Text, Tooltip } from "@mantine/core";
import {
IconCategoryPlus,
IconDots,
IconFileText,
IconPlayerPlay,
IconPlayerStop,
IconRefresh,
IconRotateClockwise,
IconTrash,
} from "@tabler/icons-react";
import type { MRT_ColumnDef } from "mantine-react-table";
import type { MRT_ColumnDef, MRT_Row } from "mantine-react-table";
import { MantineReactTable } from "mantine-react-table";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useTimeAgo } from "@homarr/common";
import { humanFileSize, useTimeAgo } from "@homarr/common";
import type { ContainerState } from "@homarr/docker";
import { containerStateColorMap } from "@homarr/docker/shared";
import { containerStateColorMap, cpuUsageColor, memoryUsageColor, safeValue } from "@homarr/docker/shared";
import { useModalAction } from "@homarr/modals";
import { AddDockerAppToHomarr } from "@homarr/modals-collection";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
@@ -27,25 +30,30 @@ import type { TablerIcon } from "@homarr/ui";
import { OverflowBadge } from "@homarr/ui";
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
const createColumns = (
t: TranslationFunction,
): MRT_ColumnDef<RouterOutputs["docker"]["getContainers"]["containers"][number]>[] => [
type DockerContainer = RouterOutputs["docker"]["getContainers"]["containers"][number];
const createContainerLogsPath = (container: Pick<DockerContainer, "id" | "name">) =>
`/manage/tools/docker/logs/${container.id}?name=${encodeURIComponent(container.name)}`;
const createColumns = (t: TranslationFunction): MRT_ColumnDef<DockerContainer>[] => [
{
accessorKey: "name",
header: t("docker.field.name.label"),
Cell({ renderedCellValue, row }) {
return (
<Group gap="xs">
<Group gap="xs" wrap="nowrap">
<Avatar
variant="outline"
radius="lg"
radius="sm"
size="md"
styles={{ image: { objectFit: "contain" } }}
src={row.original.iconUrl}
>
{row.original.name.at(0)?.toUpperCase()}
</Avatar>
<Text>{renderedCellValue}</Text>
<Text truncate="end" maw={500}>
{renderedCellValue}
</Text>
</Group>
);
},
@@ -58,17 +66,29 @@ const createColumns = (
return <ContainerStateBadge state={cell.row.original.state} />;
},
},
{
accessorKey: "host",
header: t("docker.field.host.label"),
size: 140,
Cell({ row }) {
return (
<Text truncate="end" title={row.original.host}>
{row.original.host}
</Text>
);
},
},
{
accessorKey: "image",
header: t("docker.field.containerImage.label"),
maxSize: 200,
Cell({ renderedCellValue, cell }) {
return (
<Box maw={200}>
<Text truncate="end" title={cell.row.original.image}>
{renderedCellValue}
</Text>
</Box>
<Tooltip label={cell.row.original.image} multiline maw={400}>
<Box maw={200}>
<Text truncate="end">{renderedCellValue}</Text>
</Box>
</Tooltip>
);
},
},
@@ -76,24 +96,86 @@ const createColumns = (
accessorKey: "ports",
header: t("docker.field.ports.label"),
Cell({ cell }) {
if (!cell.row.original.ports) return null;
if (!cell.row.original.ports.length) return null;
return (
<OverflowBadge overflowCount={1} data={cell.row.original.ports.map((port) => port.PrivatePort.toString())} />
);
},
},
{
id: "cpuUsage",
accessorKey: "cpuUsage",
size: 120,
header: t("docker.field.stats.cpu.label"),
enableHiding: true,
sortingFn: (rowA, rowB) => {
const cpuUsageA = safeValue(rowA.original.cpuUsage);
const cpuUsageB = safeValue(rowB.original.cpuUsage);
return cpuUsageA - cpuUsageB;
},
Cell({ row }) {
const cpuUsage = safeValue(row.original.cpuUsage);
return (
<Text size="sm" c={cpuUsageColor(cpuUsage, row.original.state)}>
{cpuUsage.toFixed(2)}%
</Text>
);
},
},
{
id: "memoryUsage",
accessorKey: "memoryUsage",
size: 140,
header: t("docker.field.stats.memory.label"),
enableHiding: true,
sortingFn: (rowA, rowB) => {
const memoryUsageA = safeValue(rowA.original.memoryUsage);
const memoryUsageB = safeValue(rowB.original.memoryUsage);
return memoryUsageA - memoryUsageB;
},
Cell({ row }) {
const bytesUsage = safeValue(row.original.memoryUsage);
return (
<Text size="sm" c={memoryUsageColor(bytesUsage, row.original.state)}>
{humanFileSize(bytesUsage)}
</Text>
);
},
},
];
export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"]) {
export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["getContainers"]) {
const t = useI18n();
const tDocker = useScopedI18n("docker");
const { data } = clientApi.docker.getContainers.useQuery(undefined, {
initialData,
initialData: { containers, timestamp },
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
});
const relativeTime = useTimeAgo(data.timestamp);
const utils = clientApi.useUtils();
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
async onSuccess() {
await utils.docker.getContainers.invalidate();
showSuccessNotification({
title: tDocker("action.refresh.notification.success.title"),
message: tDocker("action.refresh.notification.success.message"),
});
},
onError() {
showErrorNotification({
title: tDocker("action.refresh.notification.error.title"),
message: tDocker("action.refresh.notification.error.message"),
});
},
});
const table = useTranslatedMantineReactTable({
data: data.containers,
enableDensityToggle: false,
@@ -101,6 +183,8 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
enableColumnFilters: false,
enablePagination: false,
enableRowSelection: true,
enableRowActions: true,
positionActionsColumn: "last",
positionToolbarAlertBanner: "top",
enableTableFooter: false,
enableBottomToolbar: false,
@@ -111,36 +195,15 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
autoFocus: true,
},
initialState: { density: "xs", showGlobalFilter: true },
renderTopToolbarCustomActions: () => {
const utils = clientApi.useUtils();
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
async onSuccess() {
await utils.docker.getContainers.invalidate();
showSuccessNotification({
title: tDocker("action.refresh.notification.success.title"),
message: tDocker("action.refresh.notification.success.message"),
});
},
onError() {
showErrorNotification({
title: tDocker("action.refresh.notification.error.title"),
message: tDocker("action.refresh.notification.error.message"),
});
},
});
return (
<Button
variant="default"
rightSection={<IconRefresh size="1rem" />}
onClick={() => mutate()}
loading={isPending}
>
{tDocker("action.refresh.label")}
</Button>
);
},
initialState: { density: "xs", showGlobalFilter: true, columnVisibility: { cpuUsage: false, memoryUsage: false } },
mantineTableBodyCellProps: { style: { padding: "4px 8px" } },
mantineTableHeadCellProps: { style: { padding: "4px 8px" } },
renderRowActions: ({ row }: { row: MRT_Row<DockerContainer> }) => <ContainerRowMenu container={row.original} />,
renderTopToolbarCustomActions: () => (
<Button variant="default" rightSection={<IconRefresh size="1rem" />} onClick={() => mutate()} loading={isPending}>
{tDocker("action.refresh.label")}
</Button>
),
renderToolbarAlertBannerContent: ({ groupedAlert, table }) => {
const dockerContainers = table.getSelectedRowModel().rows.map((row) => row.original);
return (
@@ -168,28 +231,104 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
);
}
const containerActions: { action: "start" | "stop" | "restart" | "remove"; icon: TablerIcon; color: MantineColor }[] = [
{ action: "start", icon: IconPlayerPlay, color: "green" },
{ action: "stop", icon: IconPlayerStop, color: "red" },
{ action: "restart", icon: IconRotateClockwise, color: "orange" },
{ action: "remove", icon: IconTrash, color: "red" },
];
const ContainerRowMenu = ({ container }: { container: DockerContainer }) => {
const t = useScopedI18n("docker.action");
const router = useRouter();
const utils = clientApi.useUtils();
const { openModal } = useModalAction(AddDockerAppToHomarr);
const useContainerAction = (action: "start" | "stop" | "restart" | "remove") =>
clientApi.docker[`${action}All`].useMutation({
async onSettled() {
await utils.docker.getContainers.invalidate();
},
onSuccess() {
showSuccessNotification({
title: t(`${action}.notification.success.title`),
message: t(`${action}.notification.success.message`),
});
},
onError() {
showErrorNotification({
title: t(`${action}.notification.error.title`),
message: t(`${action}.notification.error.message`),
});
},
});
const mutations = {
start: useContainerAction("start"),
stop: useContainerAction("stop"),
restart: useContainerAction("restart"),
remove: useContainerAction("remove"),
};
return (
<Menu withinPortal position="bottom-end" shadow="sm">
<Menu.Target>
<ActionIcon variant="subtle" color="gray" onClick={(e) => e.stopPropagation()}>
<IconDots size="1rem" />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
leftSection={<IconFileText size="1rem" />}
onClick={() => router.push(createContainerLogsPath(container))}
>
{t("logs.label")}
</Menu.Item>
<Menu.Divider />
{containerActions.map(({ action, icon: Icon, color }) => (
<Menu.Item
key={action}
leftSection={<Icon size="1rem" />}
color={color}
onClick={() => mutations[action].mutate({ ids: [container.id] })}
>
{t(`${action}.label`)}
</Menu.Item>
))}
<Menu.Divider />
<Menu.Item
leftSection={<IconCategoryPlus size="1rem" />}
color="red"
onClick={() => openModal({ selectedContainers: [container] })}
>
{t("addToHomarr.label")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
};
interface ContainerActionBarProps {
selectedContainers: RouterOutputs["docker"]["getContainers"]["containers"];
selectedContainers: DockerContainer[];
}
const ContainerActionBar = ({ selectedContainers }: ContainerActionBarProps) => {
const t = useScopedI18n("docker.action");
const { openModal } = useModalAction(AddDockerAppToHomarr);
const handleClick = () => {
openModal({
selectedContainers,
});
};
const selectedIds = selectedContainers.map((container) => container.id);
return (
<Group gap="xs">
<ContainerActionBarButton icon={IconPlayerPlay} color="green" action="start" selectedIds={selectedIds} />
<ContainerActionBarButton icon={IconPlayerStop} color="red" action="stop" selectedIds={selectedIds} />
<ContainerActionBarButton icon={IconRotateClockwise} color="orange" action="restart" selectedIds={selectedIds} />
<ContainerActionBarButton icon={IconTrash} color="red" action="remove" selectedIds={selectedIds} />
<Button leftSection={<IconCategoryPlus />} color={"red"} onClick={handleClick} variant="light" radius="md">
{containerActions.map(({ action, icon, color }) => (
<ContainerActionBarButton key={action} icon={icon} color={color} action={action} selectedIds={selectedIds} />
))}
<Button
leftSection={<IconCategoryPlus />}
color="red"
onClick={() => openModal({ selectedContainers })}
variant="light"
>
{t("addToHomarr.label")}
</Button>
</Group>
@@ -240,7 +379,6 @@ const ContainerActionBarButton = (props: ContainerActionBarButtonProps) => {
onClick={handleClickAsync}
loading={isPending}
variant="light"
radius="md"
>
{t(`${props.action}.label`)}
</Button>
@@ -0,0 +1,10 @@
"use client";
import dynamic from "next/dynamic";
export const ClientSideDockerLogsTerminal = dynamic(
() => import("./terminal").then(({ DockerLogsTerminal }) => DockerLogsTerminal),
{
ssr: false,
},
);
@@ -0,0 +1,47 @@
import { notFound } from "next/navigation";
import { Box, Group } from "@mantine/core";
import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next";
import { env } from "@homarr/docker/env";
import "@xterm/xterm/css/xterm.css";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { fullHeightWithoutHeaderAndFooter } from "~/constants";
import { ClientSideDockerLogsTerminal } from "./client";
interface DockerContainerLogsPageProps {
params: Promise<{ containerId: string }>;
searchParams: Promise<{ name?: string }>;
}
export default async function DockerContainerLogsPage({ params, searchParams }: DockerContainerLogsPageProps) {
const session = await auth();
if (!(session?.user.permissions.includes("admin") && env.ENABLE_DOCKER)) {
notFound();
}
const { containerId } = await params;
const { name } = await searchParams;
try {
await api.docker.logs({ id: containerId, tail: 1 });
} catch {
notFound();
}
return (
<>
<Group justify="space-between" align="center" wrap="nowrap">
<DynamicBreadcrumb
dynamicMappings={new Map([[containerId, name ?? containerId]])}
nonInteractable={[containerId]}
/>
</Group>
<Box style={{ borderRadius: 6 }} h={fullHeightWithoutHeaderAndFooter} p="md" bg="black">
<ClientSideDockerLogsTerminal containerId={containerId} />
</Box>
</>
);
}
@@ -0,0 +1,3 @@
.outerTerminal > div {
height: 100%;
}
@@ -0,0 +1,81 @@
"use client";
import { useCallback, useEffect, useRef } from "react";
import { Box } from "@mantine/core";
import { CanvasAddon } from "@xterm/addon-canvas";
import { FitAddon } from "@xterm/addon-fit";
import { Terminal } from "@xterm/xterm";
import { clientApi } from "@homarr/api/client";
import classes from "./terminal.module.css";
interface DockerLogsTerminalProps {
containerId: string;
}
export const DockerLogsTerminal = ({ containerId }: DockerLogsTerminalProps) => {
const ref = useRef<HTMLDivElement>(null);
const terminalRef = useRef<Terminal | null>(null);
const pendingLogsRef = useRef<string[]>([]);
const isTerminalReadyRef = useRef(false);
const writeToTerminal = useCallback((data: string) => {
if (!isTerminalReadyRef.current || !terminalRef.current) {
pendingLogsRef.current.push(data);
return;
}
terminalRef.current.write(data);
terminalRef.current.refresh(0, terminalRef.current.rows - 1);
}, []);
clientApi.docker.subscribeLogs.useSubscription(
{ id: containerId, tail: 200 },
{
onData: writeToTerminal,
onError(err) {
writeToTerminal(`\r\n\x1b[31m${err.message}\x1b[0m`);
},
},
);
useEffect(() => {
if (!ref.current) {
return () => undefined;
}
const canvasAddon = new CanvasAddon();
terminalRef.current = new Terminal({
cursorBlink: false,
disableStdin: true,
convertEol: true,
fontSize: 14,
});
terminalRef.current.open(ref.current);
terminalRef.current.loadAddon(canvasAddon);
isTerminalReadyRef.current = true;
const terminal = terminalRef.current;
pendingLogsRef.current.forEach((data) => terminal.write(data));
pendingLogsRef.current = [];
terminal.refresh(0, terminal.rows - 1);
setTimeout(() => {
const fitAddon = new FitAddon();
terminalRef.current?.loadAddon(fitAddon);
fitAddon.fit();
});
return () => {
isTerminalReadyRef.current = false;
pendingLogsRef.current = [];
canvasAddon.dispose();
terminalRef.current?.dispose();
terminalRef.current = null;
};
}, [writeToTerminal]);
return <Box ref={ref} className={classes.outerTerminal} h="100%" />;
};
@@ -4,28 +4,16 @@
overflow: hidden;
padding: var(--mantine-spacing-xs);
padding-left: calc(var(--mantine-spacing-xs) * 2);
@mixin hover {
box-shadow: var(--mantine-shadow-md);
}
&::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 6px;
background-image: linear-gradient(0, var(--mantine-color-blue-4), var(--mantine-color-blue-9));
}
border-left: 1px solid var(--mantine-color-blue-6);
background-color: light-dark(
var(--mantine-color-blue-0),
color-mix(in oklch, var(--mantine-color-blue-9), transparent 85%)
);
@mixin rtl {
padding-left: var(--mantine-spacing-xs);
padding-right: calc(var(--mantine-spacing-xs) * 2);
&::before {
left: unset;
right: 0;
}
border-left: none;
border-right: 1px solid var(--mantine-color-blue-6);
}
}
@@ -23,7 +23,6 @@ export function HeaderCard(props: HeaderCardProps) {
<Flex align="center" justify={isRTL ? "space-between" : "flex-start"} gap="md" direction={"row"}>
<ThemeIcon
size="xl"
radius="md"
variant="gradient"
gradient={{
deg: 0,
@@ -8,6 +8,8 @@ import { useCurrentLocale, useI18n } from "@homarr/translation/client";
import { ResourceIcon } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-gauge/resource-icon";
import classes from "./resource-gauge.module.css";
const KUBERNETES_BRAND_COLOR = "#326ce5";
interface KubernetesResourceGaugeProps {
kubernetesCapacity: KubernetesCapacity;
}
@@ -18,8 +20,8 @@ export function ResourceGauge(props: KubernetesResourceGaugeProps) {
const isRTL = Boolean(isLocaleRTL(currentLocale));
return (
<Paper radius="md" withBorder className={classes.paper} mt={20}>
<ThemeIcon className={classes.icon} size={60} radius={60} bg={"#326ce5"}>
<Paper className={classes.paper} mt={20}>
<ThemeIcon className={classes.icon} size={60} radius={60} bg={KUBERNETES_BRAND_COLOR}>
<ResourceIcon type={props.kubernetesCapacity.type} />
</ThemeIcon>
@@ -17,12 +17,7 @@ interface ResourceTileProps {
export function ResourceTile(props: ResourceTileProps) {
const t = useI18n();
return (
<Card
withBorder
component={Link}
href={`/manage/tools/kubernetes/${props.label}`}
className={classes.cardContainer}
>
<Card component={Link} href={`/manage/tools/kubernetes/${props.label}`} className={classes.cardContainer}>
<Group justify="space-between" wrap="nowrap">
<Image src={`/images/kubernetes/${props.label}.svg`} alt={props.label} width={64} height={64} />
<Group gap="xs">
@@ -0,0 +1,29 @@
"use client";
import { Group, Slider, Text } from "@mantine/core";
import { IconTextSize } from "@tabler/icons-react";
import { useLogContext } from "./log-context";
export const LogFontSizeSlider = () => {
const { fontSize, setFontSize } = useLogContext();
return (
<Group gap="xs" wrap="nowrap" w={160}>
<IconTextSize size={18} style={{ flexShrink: 0 }} />
<Slider
min={10}
max={24}
step={1}
value={fontSize}
onChange={setFontSize}
label={(val) => `${val}px`}
style={{ flex: 1 }}
size="xs"
/>
<Text size="xs" c="dimmed" w={32} ta="right">
{fontSize}px
</Text>
</Group>
);
};
@@ -2,12 +2,14 @@
import { Select } from "@mantine/core";
import type { LogLevel } from "@homarr/core/infrastructure/logs/constants";
import { logLevelConfiguration, logLevels } from "@homarr/core/infrastructure/logs/constants";
import type { LogLevel } from "@homarr/core/infrastructure/logs/constants";
import { useI18n } from "@homarr/translation/client";
import { useLogContext } from "./log-context";
const levelByValue = Object.fromEntries(logLevels.map((logLevel) => [logLevel, logLevel])) as Record<string, LogLevel>;
export const LogLevelSelection = () => {
const { level, setLevel } = useLogContext();
const t = useI18n();
@@ -19,7 +21,12 @@ export const LogLevelSelection = () => {
label: `${logLevelConfiguration[level].prefix} ${t(`log.level.option.${level}`)}`,
}))}
value={level}
onChange={(value) => setLevel(value as LogLevel)}
onChange={(value) => {
const nextLevel = value && levelByValue[value];
if (nextLevel) {
setLevel(nextLevel);
}
}}
checkIconPosition="right"
/>
);
@@ -6,10 +6,23 @@ import { createContext, useContext, useMemo, useState } from "react";
import type { LogLevel } from "@homarr/core/infrastructure/logs/constants";
import { logLevels } from "@homarr/core/infrastructure/logs/constants";
const FONT_SIZE_KEY = "homarr-log-font-size";
const DEFAULT_FONT_SIZE = 14;
const readStoredFontSize = () => {
if (typeof window === "undefined") return DEFAULT_FONT_SIZE;
const stored = localStorage.getItem(FONT_SIZE_KEY);
if (!stored) return DEFAULT_FONT_SIZE;
const parsed = Number(stored);
return Number.isFinite(parsed) ? Math.min(Math.max(parsed, 10), 24) : DEFAULT_FONT_SIZE;
};
const LogContext = createContext<{
level: LogLevel;
setLevel: (level: LogLevel) => void;
activeLevels: LogLevel[];
fontSize: number;
setFontSize: (size: number) => void;
} | null>(null);
interface LogContextProviderProps extends PropsWithChildren {
@@ -18,9 +31,19 @@ interface LogContextProviderProps extends PropsWithChildren {
export const LogContextProvider = ({ defaultLevel, children }: LogContextProviderProps) => {
const [level, setLevel] = useState(defaultLevel);
const [fontSize, setFontSizeRaw] = useState(readStoredFontSize);
const activeLevels = useMemo(() => logLevels.slice(0, logLevels.indexOf(level) + 1), [level]);
return <LogContext.Provider value={{ level, setLevel, activeLevels }}>{children}</LogContext.Provider>;
const setFontSize = (size: number) => {
setFontSizeRaw(size);
localStorage.setItem(FONT_SIZE_KEY, String(size));
};
return (
<LogContext.Provider value={{ level, setLevel, activeLevels, fontSize, setFontSize }}>
{children}
</LogContext.Provider>
);
};
export const useLogContext = () => {
Loaded 100 of 660 files, more files were not shown because too many files have changed in this diff. Show more