Compare commits

...
Author SHA1 Message Date
Thomas Camlong 825ecaf1fd fix db lint after v2 merge 2026-07-15 12:25:12 +02:00
Thomas Camlong ff1fb409a9 Merge v2 into custom widgets 2026-07-15 12:23:28 +02:00
Thomasandhomarr-renovate[bot] 0a0b3a12c9 bring dev changes to v2 (#6353)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-15 12:16:16 +02:00
Thomas ba263dbe47 Delete .opencode directory 2026-07-15 12:14:46 +02:00
Thomasandcopilot-swe-agent[bot] c69e33ac8c feat: community workshop for custom widgets and CSS (#5984)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-15 11:53:15 +02:00
Thomas Camlong 9e20041c4f fix(ci): stabilize quality checks 2026-07-15 11:06:42 +02:00
Thomas Camlong b573b4d46f fix(custom-widgets): parse fenced imports safely 2026-07-15 10:32:42 +02:00
Thomas Camlong e174e85c39 Merge remote-tracking branch 'origin/v2' into feat/custom-widgets-architecture-audit
# Conflicts:
#	pnpm-lock.yaml
2026-07-15 10:28:32 +02:00
Thomas Camlong 4c6ffcd0fa refactor(custom-widgets): extract domain package 2026-07-14 16:59:06 +02:00
Thomas Camlong 121a83040d Setup v2 pre-release branch 2026-07-13 18:14:19 +02:00
Thomas 06f1877ae1 Merge pull request #6282 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-13 18:11:10 +02:00
homarr-renovate[bot] 1d9e8aabf3 chore(deps): update turbo monorepo to ^2.10.4 (#6303)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 13:36:45 +00:00
homarr-renovate[bot] b111e5285b chore(deps): update dependency @scalar/api-reference-react to ^0.9.54 (#6317)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 10:38:14 +00:00
homarr-renovate[bot] f4c491b570 chore(deps): update dependency @types/node to ^24.13.3 (#6318)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 10:37:23 +00:00
homarr-renovate[bot] 29bd4fd70b chore(deps): update dependency posthog-js to ^1.398.1 (#6311)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 18:09:44 +00:00
homarr-renovate[bot] 269dadee8d fix(deps): update dependency swiper to ^14.0.2 (#6313)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 18:09:11 +00:00
Thomas Camlong c5703e7e53 fix(custom-widgets): address review feedback 2026-07-12 15:08:59 +02:00
Thomas 03f68a0421 fix(qbittorrent): sort torrents by progress so active items appear before limit cutoff (#6297) 2026-07-12 14:56:24 +02:00
Bergasha 807e5eb2a9 feat(docker-widget): replace footer timestamp text with compact refresh button (#6307) 2026-07-12 14:51:56 +02:00
Thomas 8e8c29979f fix(system-disks): format absolute storage values with shared byte helper (#6299) 2026-07-12 14:50:58 +02:00
Manuel 7d859c1af0 docs: update pr template (#6147) 2026-07-12 14:49:56 +02:00
Thomas Camlong c43836e85a refactor(custom-widgets): simplify authoring flow 2026-07-12 14:46:17 +02:00
Thomas Camlong 46add018bf feat(custom-widgets): add secure v2 authoring 2026-07-12 14:35:35 +02:00
homarr-renovate[bot] e9543dc30d chore(deps): update tiptap monorepo to v3.27.3 (#6308)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 09:12:55 +00:00
Nerdy Technician 9f313440ac fix(integrations): update PatchMon API key settings link (#6302) 2026-07-12 08:59:52 +02:00
homarr-renovate[bot] c2508dffea chore(deps): update dependency posthog-js to ^1.396.7 (#6285)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 06:18:17 +00:00
Artur d44bc06a93 feat(notebook): support text direction controls (#6293) 2026-07-12 07:58:02 +02:00
homarr-renovate[bot] c56b91570b chore(deps): update tiptap monorepo to v3.27.2 (#6292)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 03:01:23 +00:00
homarr-renovate[bot] d53c4e6513 chore(deps): update dependency hono@<4.6.5 to >=4.12.28 (#6291)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 02:58:51 +00:00
homarr-renovate[bot] 0426d600cc chore(deps): update dependency oxfmt to ^0.58.0 (#6305)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 00:06:59 +00:00
ajnart d37545c98e style: auto-fix lint and format 2026-07-11 22:44:04 +00:00
Thomas Camlong aa8410d326 feat(custom-widgets): full Mantine v9 + SubFetch support for custom JSX
Massively expands the custom JSX widget system to support virtually all
Mantine v9 components and adds in-widget HTTP request capabilities via
a server-proxied SubFetch system.

- Whitelist ~100 Mantine components (core, all 16 charts, dates)
- Add SubFetch, SubData, ActionButton, ToggleSwitch, RefreshButton
- Add server-side subFetch tRPC mutation with same-origin + auth
- Show HTTP method warning badges in widget and admin preview
- Remove `fetch` from forbidden keywords, increase template to 50KB
- Expand bindings: Date.*, parseInt, parseFloat, Array.from, etc.
- Update AI prompt with full component reference + demo templates
2026-07-12 00:42:31 +02:00
Thomas 576856d634 fix(beszel): add reliable live stats and filesystem UI (#6289) 2026-07-11 17:55:04 +02:00
Thomas def3ede3bc fix(db): restore widget_secret migration journal entries accidentally removed by #6206 (#6288) 2026-07-11 15:40:06 +02:00
github-actions[bot] 34bd574354 docs(helm): Update Helm chart documentation 2026-07-10 20:32:11 +00:00
homarr-releases[bot] d9c503d01b chore: update bug report template (#6281)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-07-10 20:17:43 +00:00
Releases Homarr 82aa905214 chore(release): 1.70.0 [skip ci] 2026-07-10 20:03:12 +00:00
Thomas 4b351bb209 chore(release): automatic release v1.70.0 2026-07-10 22:01:50 +02:00
Thomas 13c9e5e181 fix(release): repair v1.70.0 quality failures (#6279)
* fix(release): repair v1.70.0 quality failures

* style: auto-fix lint and format
2026-07-10 21:59:57 +02:00
homarr-releases[bot] 6b047a9c8e chore(release): automatic release v1.70.0 2026-07-10 18:16:39 +00:00
Thomas 7a768517ef fix(request-handler): restore media organizer build (#6277) 2026-07-10 18:09:05 +02:00
Thomas 4ae8d527a8 fix(releases): token not applied due to broken allowedRepoIds check (#6276) 2026-07-10 17:55:18 +02:00
homarr-update-contributors[bot] 05a48e2930 chore: update integrations list readme (#6275) 2026-07-10 17:50:02 +02:00
Josh PrestonandThomas 33395ae226 feat(integrations): add PatchMon integration and widget (#6119)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:48:43 +02:00
Thomas 174c5f98f8 fix(beszel): disable request handler cache (#6274) 2026-07-10 17:40:54 +02:00
a2cf2d6cff Update Helm chart documentation (#6261)
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-10 17:33:35 +02:00
Anil K TiwariandThomas 9fb2e37605 feat(widgets): add missing & queued media widget for Radarr and Sonarr (#6078)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:31:08 +02:00
homarr-crowdin[bot] 713fcd6f3f chore(lang): updated translations from crowdin (#6240) 2026-07-10 17:30:10 +02:00
homarr-update-contributors[bot] 6b10cd6874 chore: update integrations list readme (#6258) 2026-07-10 17:29:47 +02:00
homarr-update-contributors[bot] cabc66cc69 chore: update contributors (#6272) 2026-07-10 17:29:30 +02:00
Felix-AyushandThomas 2c5368dd4e feat: add Bazarr integration and subtitle status widget (#6156)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:29:10 +02:00
Nam NguyenandThomas 7d93df04dd fix: add utf8 email validation support (#6239)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:28:22 +02:00
Thomas 7c152cf4fe feat(api): expose board automation endpoints (#6213)
* feat(api): expose board automation endpoints

* style: auto-fix lint and format

* test(api): cover board settings updates

* Delete .opencode/changelog/2026-07-06.md
2026-07-10 17:27:42 +02:00
Thomas 718f095cb7 fix: prevent stale persisted query cache from blocking widget data (#6273) 2026-07-10 17:19:24 +02:00
Thomas 24c5d04bfa fix(beszel): disable request handler cache for stats to make live mode work (#6244) 2026-07-10 17:16:23 +02:00
Thomas 0e513fffd5 feat(widgets): add per-filesystem storage chart to Beszel system stats (#6209) 2026-07-10 17:08:22 +02:00
Thomas 2ada1aa249 fix(qbittorrent): handle v5 stopped torrent states for pause functionality (#6211) 2026-07-10 16:55:20 +02:00
Junker der Provinz af89423f66 fix(docker): report current CPU usage instead of the lifetime average (#6249) 2026-07-10 16:54:52 +02:00
Nerdy TechnicianandThomas 9864fed775 feat: Add Traefik Integration with dedicated widget and Add optional Icons for Gotify/Notifcation Widget (ONLY GOTIFY) (#6206)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 16:49:24 +02:00
Thomas bcd36b1b27 fix(integrations): sort media requests by last modified instead of creation date (#6264) 2026-07-10 16:46:33 +02:00
homarr-renovate[bot] a2f7b35c5c chore(deps): update pnpm to v11.10.0 (#6265)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-09 21:36:24 +00:00
homarr-renovate[bot] 616a444e64 chore(deps): update dependency @immich/sdk to ^3.0.1 (#6251)
* chore(deps): update dependency @immich/sdk to ^3.0.1

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-09 19:51:55 +00:00
Josh Preston 65ba866239 feat: add Synology integration support for health/system monitoring (#6121) 2026-07-09 18:23:21 +02:00
Thomasandajnart 260b167134 fix: context menu refresh stays open with loading spinner, cache age reacts to refetch
* fix: context menu refresh stays open with loading spinner, cache age reacts to refetch

* style: auto-fix lint and format

* refactor: pass isFetching as prop to WidgetCacheAge to avoid duplicate useIsFetching

---------

Co-authored-by: ajnart <ajnart@users.noreply.github.com>
2026-07-07 18:39:34 +02:00
Thomas VervestandThomas 7a4de03c0e fix(integrations): accept gluetun-latest VPN settings payload (#6149)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-07 17:24:50 +02:00
homarr-renovate[bot] 68f8e8146b chore(deps): update dependency @scalar/api-reference-react to ^0.9.51 (#6243)
* chore(deps): update dependency @scalar/api-reference-react to ^0.9.51

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-07 13:23:56 +00:00
Thomas 1297e6c2ac fix(query-cache): guard against undefined from superjson.parse on legacy cache entries (#6228) 2026-07-07 11:28:13 +02:00
Thomas Camlong 31288dcbfe fix(docs): update Algolia crawler config with exclusion patterns and ignoreCanonicalTo 2026-07-07 10:08:24 +02:00
Thomas fc2c251557 feat(demo): allow mutations in demo mode via DEMO_READ_ONLY flag (#6224) 2026-07-07 10:01:36 +02:00
Thomas Vervest d067261171 ci: skip lint-format-autofix on fork PRs (#6237) 2026-07-07 10:00:24 +02:00
Thomas Camlong ae2f05c834 fix(docs): sitemap priorities, exclude category redirects, remove trailingSlash 2026-07-07 09:57:32 +02:00
homarr-renovate[bot] c7fa7cdb50 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.1.3 (#6242)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-07 07:49:42 +00:00
homarr-renovate[bot] 0c28224e82 chore(deps): update nextjs monorepo to v16.2.10 (#6238)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 21:56:19 +00:00
homarr-renovate[bot] 4e6272dbe2 chore(deps): update dependency posthog-js to ^1.396.4 (#6232)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:56:28 +00:00
homarr-renovate[bot] f5bc090d36 chore(deps): update dependency lucide-react to ^1.23.0 (#6225)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:53:57 +00:00
homarr-renovate[bot] f23e06dff1 chore(deps): update dependency @semantic-release/github to ^12.0.9 (#6231)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:53:39 +00:00
homarr-renovate[bot] 24188ce51f chore(deps): update dependency posthog-node to ^5.39.2 (#6226)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 14:37:51 +00:00
homarr-renovate[bot] e31f60fdec chore(deps): update dependency cookie to v2.0.1 (#6223)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 14:36:14 +00:00
homarr-renovate[bot] 6bb547f3c5 chore(deps): update dependency conventional-changelog-conventionalcommits to ^10.2.0 (#6222)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 10:32:35 +00:00
Thomas b07cc14d0e Merge pull request #6221 from homarr-labs/feat/fix-build
fix(deps): revert typescript from 7.0.1-rc to ^6.0.3
2026-07-06 12:16:11 +02:00
Thomas Camlong 0dc93c1a02 fix(deps): revert typescript from 7.0.1-rc to ^6.0.3
Next.js 16.2.9 does not fully support TypeScript 7 (tsgo) as the
primary typescript package. The Go-native compiler has no in-process
JavaScript API — lib/typescript.js is missing — which causes Next.js's
hasNecessaryDependencies check to fail and the build to SIGABRT with
CI=true. Vercel is working on native tsgo integration expected in Q3
2026 alongside TypeScript 7 stable.
2026-07-06 12:15:38 +02:00
Thomas 00661c9b3d fix: fix pnpm lockfile (#6220) 2026-07-06 10:52:58 +02:00
Thomas 2e8fddd29f fix(docs): fix docs build — MDX acorn error and CSS @import ordering (#6217) 2026-07-06 10:14:36 +02:00
homarr-releases[bot] 302d5a0d04 chore: update bug report template (#6181) 2026-07-06 10:14:15 +02:00
ThomasandReleases Homarr a9ed95856a fix(layout): raise AppShellHeader z-index above Indicator dots (#6218)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-07-06 10:13:54 +02:00
Thomasandhomarr-renovate[bot] 4d42eb65e4 chore(deps): mega dependency update (#6212)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 10:05:51 +02:00
Releases Homarr 57e18f6ce6 chore(release): 1.69.2 [skip ci] 2026-07-06 08:05:24 +00:00
Thomas 29369e23c1 Merge pull request #6208 from homarr-labs/proxmox-status-zindex
fix(layout): raise AppShellHeader z-index above Proxmox status dots
2026-07-06 10:04:04 +02:00
Thomas 82858ecd75 fix(forms): break AppForm infinite render loop on edit page (#6198) (#6215) 2026-07-06 10:03:22 +02:00
homarr-crowdin[bot] 0e365ed53c 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-07-06 02:04:20 +00:00
Thomas a55dc98487 Remove memory regression job from code quality workflow
Removed memory regression testing steps from the workflow.
2026-07-06 02:22:05 +02:00
ajnart 687edb9226 fix(layout): raise AppShellHeader z-index above Indicator dots
The Mantine Indicator component (used by the Proxmox cluster health
widget for VM/LXC status dots) sits at z-index 200, which is higher
than the AppShellHeader's default 100. This caused the status dots
to bleed above the header when scrolling.

Set the header z-index to 201 so it stacks above Indicator content
without conflicting with modal/popover layers (300+).

Closes #6194
2026-07-06 01:50:13 +02:00
homarr-crowdin[bot] 5b91687912 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-07-05 01:57:18 +00:00
Thomas a3df31ff05 Revise Immich API key permissions in documentation
Updated API key permissions for Immich integration documentation.
2026-07-04 21:49:08 +02:00
homarr-renovate[bot] c5c9daf5a5 chore(deps): update dependency undici to v7.28.0 [security] (#6188)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 18:11:47 +00:00
homarr-renovate[bot] ab0e878fbf chore(deps): update dependency posthog-js to ^1.396.2 (#6189)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 16:20:03 +00:00
Thomas 885be1b9a1 fix(widget-secrets): add missing widget_secret migration and add error notifications (#6192)
* fix(widget-secrets): add missing widget_secret migration and add error notifications

The widget_secret table was defined in all three Drizzle schema files but
no SQL migration was ever generated to create it, causing setSecret to
fail with 'no such table: widget_secret' in production (drizzle-kit push
in development hid the problem).

- Add migration SQL for sqlite (0040), mysql (0042), postgresql (0008)
- Update journal entries for all three dialects
- Add onError handlers to setSecret/deleteSecret mutations so errors
  are shown as red notifications instead of silently swallowed

Fixes #6186

* fix(widget-secrets): add missing drizzle snapshots for merge-url and widget_secret migrations

The merge_custom_widget_url migration (0039/0041/0007) was committed
without a corresponding snapshot, breaking drizzle-kit generate for
all future migrations. This adds:
- 0039/0041/0007 snapshots reflecting the merge-url changes
  (base_url+endpoint -> url, add enabled column)
- 0040/0042/0008 snapshots reflecting the widget_secret table

All snapshot chains now match journal entries (41/43/9 entries each).
2026-07-04 17:57:40 +02:00
homarr-renovate[bot] 0f8a86fdde fix(deps): update tailwindcss monorepo to ^4.3.2 (#6190)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 14:55:47 +00:00
homarr-renovate[bot] 59c084bc57 chore(deps): update testcontainers-node monorepo to ^12.0.4 (#6185)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 12:18:18 +00:00
homarr-renovate[bot] f6cf262bab chore(deps): update dependency posthog-js to ^1.396.1 (#6183)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 10:27:08 +00:00
Releases Homarr ba860aacaa chore(release): 1.69.1 [skip ci] 2026-07-04 09:02:40 +00:00
homarr-releases[bot] 161dafbdb8 chore(release): automatic release v1.69.1 2026-07-04 09:01:34 +00:00
Thomas a382c75968 fix: unblock query cache mutations in demo mode and add cache buster (#6176) 2026-07-04 10:47:09 +02:00
homarr-renovate[bot] 529942e4da chore(deps): update dependency brace-expansion@>=1.0.0 <=1.1.11 to >=5.0.7 (#6173)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 07:55:55 +00:00
homarr-releases[bot] e20053b2c1 chore: update bug report template (#6134) 2026-07-04 09:09:59 +02:00
134106b82c Main (#6175)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-07-04 09:09:23 +02:00
homarr-crowdin[bot] 7635031fdf chore(lang): updated translations from crowdin (#6143) 2026-07-04 08:58:20 +02:00
Bergasha b663dae409 fix (Calendar Widget): Add superjson serialization to query cache persister (#6172) 2026-07-04 08:57:49 +02:00
homarr-renovate[bot] 2c2bdb6b76 chore(deps): update dependency brace-expansion@>=2.0.0 <=2.0.1 to >=5.0.7 (#6174)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-04 04:45:04 +00:00
homarr-renovate[bot] dee9cd7aac chore(deps): update dependency lucide-react to ^1.22.0 (#6171)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-03 20:27:36 +00:00
homarr-renovate[bot] 7c222bf5e8 chore(deps): update dependency fastify to ^5.9.0 (#6166)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-03 20:26:10 +00:00
homarr-renovate[bot] f914cc26be chore(deps): update mantine monorepo to ^9.4.1 (#6165)
* chore(deps): update mantine monorepo to ^9.4.1

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-03 20:15:37 +00:00
Releases Homarr 6f47a05cc2 chore(release): 1.69.0 [skip ci] 2026-07-03 18:08:44 +00:00
homarr-releases[bot] d8d5894b28 chore(release): automatic release v1.69.0 2026-07-03 18:07:40 +00:00
homarr-renovate[bot] 9252fdc139 fix(deps): update dependency postcss to ^8.5.16 (#6169)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-03 16:36:25 +00:00
Thomas f19033e0d5 feat: simplify caching — TanStack Query as single source of truth (#6075) 2026-07-03 16:33:03 +02:00
Thomas a5ec880eec fix(immich): v3 API compatibility — use searchAssets for album assets, drop deviceAssetId (#5977) 2026-07-03 15:59:08 +02:00
homarr-update-contributors[bot] b588672709 chore: update contributors (#6167)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-07-03 13:58:25 +00:00
homarr-update-contributors[bot] ceed5c6751 chore: update integrations list readme (#6085)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-07-02 15:47:49 +00:00
Thomas f7ede23dc3 Merge pull request #6089 from homarr-labs/feat/widget-only-integration-refactor 2026-07-02 17:37:07 +02:00
homarr-renovate[bot] 1b50249ea9 chore(deps): update dependency tar-fs@>=3.0.0 <3.0.9 to >=3.1.3 (#6162)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-02 14:11:50 +00:00
homarr-renovate[bot] 1d57519005 chore(deps): update dependency tar-fs@>=2.0.0 <2.1.3 to >=3.1.3 (#6161)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-02 11:44:07 +00:00
homarr-renovate[bot] dd2eb3aa66 chore(deps): update dependency posthog-js to ^1.395.0 (#6150)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-01 15:51:52 +00:00
Thomas Camlong 2c1a5c92e8 chore(i18n): revert non-English translations to dev
Keep translation changes in en.json only; Crowdin handles other locales.
2026-07-01 17:25:43 +02:00
ajnart 0425bec2ca style: auto-fix lint and format 2026-07-01 13:00:06 +00:00
Thomas Camlong 06da6191cc Merge origin/dev into feat/widget-only-integration-refactor
Resolve integration.ts conflict (keep widget-only documentationUrl: null, take defaultUrl from dev) and regenerate pnpm-lock.yaml.
2026-07-01 14:57:00 +02:00
homarr-renovate[bot] aec6bb18c5 chore(deps): update dependency @scalar/api-reference-react to ^0.9.49 (#6146)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-01 12:21:19 +00:00
Thomas f9da71a19b fix: quick wins — calendar, about, glances, admin UX, releases, iframe, notes (#6116) 2026-07-01 13:57:39 +02:00
homarr-renovate[bot] b83c8b6546 chore(deps): update dependency posthog-js to ^1.393.5 (#6135)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-30 21:53:10 +00:00
homarr-renovate[bot] 0e4189f693 chore(deps): update dependency dockerode to ^5.0.1 (#6130)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-30 20:09:36 +00:00
homarr-renovate[bot] 1cf010e9fb chore(deps): update node.js to v24.18.0 (#6107)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-30 18:11:58 +00:00
ajnart d670b69396 fix(timetable): restore station search by removing disabled query guard
The refactor from integration-based to option-based config changed the
useQuery enabled condition from 'integrationIds.length > 0' (always true
when an integration was linked) to 'query.length > 0'. The combination
of the debounced search value and the enabled toggle inside the nested
useOptions callback caused the query to never fire, making stations
unsearchable in the edit form.

Removing the enabled guard lets the query always run. The 300ms debounce
already prevents excessive requests.
2026-06-30 18:40:27 +02:00
Thomasandgithub-actions[bot] 7e58858353 feat(widgets): right-click context menu with auto-generated toggle actions (#6126)
* docs(helm): Update Helm chart documentation

* feat(widgets): add right-click context menu with auto-generated toggle actions

- Add WidgetContextMenu component using Mantine 9.3 Menu.ContextMenu
- Extend WidgetDefinition with contextActions API for widget-specific actions
- Auto-generate context menu toggle items for switch-type widget options
- Add 'Search' context action to media-requests widgets (list + stats)
- Keep existing dots-button menu for discoverability in edit mode
- Add widgetStateRef to WidgetComponentProps for client-side-only state
- Add skipContextMenu opt-out on switch option definitions

* style: auto-fix lint and format

* fix(widgets): address CodeRabbit review — badge in context menu target, edit-only pending gate

- Move title badge inside WidgetContextMenu wrapper so right-clicking the title overlay also triggers the context menu
- Remove full-menu hide during integration loading; only disable 'Edit' action while isPending
- Add isPending to allActions useMemo dependency array

* style: auto-fix lint and format

* fix(widgets): gate context menu behind authenticated session

- Only show right-click context menu for logged-in users
- Unauthenticated users see plain children without menu wrapper
- Matches existing BoardItemMenu session gating pattern

* style: auto-fix lint and format

* chore: revert unrelated sitemap change from auto-fix

* style: auto-fix lint and format

* fix(context-menu): persist widget option changes to server

Toggle switches and edit modal changes from the context menu now call
saveBoard mutation to persist to DB. Previously only the client cache
was updated, losing changes on reload. Removes unnecessary invalidation
that caused cascading refetch loops.

* style: auto-fix lint and format

* fix: use explicit React type imports instead of global namespace

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-30 18:10:08 +02:00
9ed55d57dd Main (#6138)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-30 17:52:37 +02:00
Releases Homarr 46ae756d15 chore(release): 1.68.1 [skip ci] 2026-06-30 12:02:44 +00:00
homarr-crowdin[bot] 8cac0b11c1 chore(lang): updated translations from crowdin (#6061) 2026-06-30 14:02:29 +02:00
Thomas 8b63799f1b Merge pull request #6124 from homarr-labs/fix/source-maps-memory-regression
fix: make production source maps opt-in to restore v1.67.0 memory baseline
2026-06-30 14:01:42 +02:00
homarr-renovate[bot] 5f011e8456 chore(deps): update turbo monorepo to ^2.10.0 (#6129)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-29 20:22:40 +00:00
homarr-renovate[bot] 9e1b529288 chore(deps): update dependency nanoid@>=4.0.0 <5.0.9 to >=5.1.16 (#6131)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-29 20:14:32 +00:00
homarr-renovate[bot] 4ad115dd2d chore(deps): update dependency posthog-js to ^1.393.4 (#6127)
* chore(deps): update dependency posthog-js to ^1.393.4

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-29 20:01:36 +00:00
27209a5dde chore: add integration CODEOWNERS (#6086)
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
2026-06-29 18:56:44 +02:00
ajnart 60a69e31ef fix: revert production source maps to restore v1.67.0 memory baseline
CodeRabbit review addressed:
- Removed the env-var gating (simpler: just remove source maps entirely)
- CI health check now fails fast if container never becomes healthy

Changes vs origin/main:
- next.config.ts: remove productionBrowserSourceMaps and serverSourceMaps
  (they default to false, restoring pre-v1.68.0 behavior)
- Dockerfile: remove .map copy step and NODE_OPTIONS=--enable-source-maps
- CI: add memory-regression job (<700MB guardrail)

Closes #6123
2026-06-29 15:13:08 +02:00
Thomas ed33748a49 Merge pull request #6118 from darbyjack/docs/warrior 2026-06-29 15:12:57 +02:00
ajnart 57ef0af4a2 fix: make source maps opt-in to restore v1.67.0 memory baseline
Production source maps (--enable-source-maps) caused a ~50% memory increase
(600MB to 1000MB) in v1.68.0. Node.js loads all .map files into memory when
this flag is enabled, and Next.js server chunks generate hundreds of map files.

Changes:
- Guard productionBrowserSourceMaps and serverSourceMaps behind
  HOMARR_ENABLE_SOURCE_MAPS env var (default off)
- Guard .map copy step and NODE_OPTIONS in Dockerfile behind
  ENABLE_SOURCE_MAPS build arg (default false)
- run.sh checks HOMARR_ENABLE_SOURCE_MAPS before setting --enable-source-maps
- Add Docker-based memory regression CI job (<700MB threshold)

Closes #6123
2026-06-29 10:08:52 +02:00
homarr-renovate[bot] 7a21e2a9c3 chore(deps): update dependency @playwright/test to ^1.61.1 (#6120)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 21:14:08 +00:00
darbyjack f66469d797 feat(docs): addd in documentation for archiveteam warrior 2026-06-28 15:45:52 -05:00
homarr-renovate[bot] c4a0c818d0 chore(deps): update mantine monorepo to ^9.4.0 (#6102)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 19:32:49 +00:00
homarr-renovate[bot] ef016061ae chore(deps): update pnpm to v11.9.0 (#6117)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 17:26:09 +00:00
homarr-renovate[bot] 3492700e6b chore(deps): update dependency vite to ^8.1.0 (#6114)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 14:06:36 +00:00
homarr-renovate[bot] 590d316671 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.1.0 (#6115)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 14:03:05 +00:00
homarr-renovate[bot] 6e9d20e651 chore(deps): update dependency recharts to ^3.9.0 (#6113)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 14:00:52 +00:00
homarr-renovate[bot] ed829fddfa chore(deps): update dependency posthog-js to ^1.393.0 (#6112)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 13:57:57 +00:00
Thomas Camlong 8747d5f93e fix: add id to repository schema and handle missing ids gracefully 2026-06-28 13:55:17 +02:00
homarr-renovate[bot] ed8b8a40eb chore(deps): update dependency @vitejs/plugin-react to ^6.0.3 (#6110)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 11:18:49 +00:00
homarr-renovate[bot] 7bc3bbb8c0 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.6 (#6109)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 09:03:24 +00:00
Thomas Camlong af35a50fa0 fix: handle missing widgetSecrets table and add default releases repo
- getConfiguredKinds now returns [] if table doesn't exist yet
- Add homarr-labs/homarr as default releases repository (v#.#.#)
2026-06-28 08:47:09 +02:00
homarr-renovate[bot] 74943ef9bc chore(deps): update dependency hono@<4.6.5 to >=4.12.27 (#6108)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 05:28:04 +00:00
homarr-renovate[bot] 5dd7f839ee chore(deps): update dependency axios@>=1.0.0 <1.8.2 to >=1.18.1 (#6104)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 00:16:33 +00:00
homarr-renovate[bot] d8c6c0f978 chore(deps): update dependency posthog-js to ^1.391.4 (#6094)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 00:16:09 +00:00
homarr-renovate[bot] 75b2d1bd1b chore(deps): update dependency oxlint to ^1.71.0 (#6101)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 00:15:23 +00:00
homarr-renovate[bot] 7c12a9ce48 chore(deps): update dependency @scalar/api-reference-react to ^0.9.48 (#6103)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-28 00:13:44 +00:00
homarr-renovate[bot] 9033acc66c chore(deps): update dependency oxfmt to ^0.56.0 (#6100)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-27 22:17:27 +00:00
Thomas a42dc34d34 Revert "feat(config): enable pnpm global virtual store for worktree support" (#6106) 2026-06-27 23:37:36 +02:00
euduardo db3d58821e feat(widgets): show calendar events on hover with pin on click (#2637) (#6081)
feat(widgets): show calendar events on hover with pin on click (#2637)

docs(widgets): update calendar interactions section for hover behavior
2026-06-27 22:49:26 +02:00
Bergasha 076526293d feat(docker-widget): add per-column visibility toggle (#6093)
* Add 'Columns to show' label in Docker stats

* feat: Add column translation keys for Docker widget

* feat: Enhance table with column visibility state

Added column visibility state management for the table.

* Add 'noColumns' error message to en.json
2026-06-27 22:44:39 +02:00
Thomasandajnart 2443f1cd3f fix: dns-hole widgets showing stale disconnected state (#6096)
Co-authored-by: ajnart <ajnart@users.noreply.github.com>
2026-06-27 22:37:34 +02:00
Thomas 597b5fbc8d Merge pull request #6097 from homarr-labs/docs/update-helm-docs
Update Helm chart documentation
2026-06-27 13:24:44 +02:00
Thomas 3864727502 feat(config): enable pnpm global virtual store for worktree support (#6098) 2026-06-27 13:23:01 +02:00
github-actions[bot] 5412577519 docs(helm): Update Helm chart documentation 2026-06-27 11:22:16 +00:00
ajnart a56bf0f32d style: auto-fix lint and format 2026-06-27 11:04:33 +00:00
Thomas Camlong e687bc8c92 fix: address review - paginate GH releases, GitLab tag_name, docs, test
- Paginate GitHub releases via api.paginate to avoid missing tags beyond page 1
- Use tag_name instead of name for GitLab releases (name is a display label)
- Remove ArchiveTeam Warrior from widget-configured note in docs
- Fix test assertion to use objectContaining for error shape with message
2026-06-27 13:02:58 +02:00
Thomas Camlong 64c7512283 fix: compile versionRegex once and add fetch timeouts
- Compile regex once via compileVersionRegex, reject invalid patterns early
- Add 10s timeout to all provider fetch calls via fetchProvider wrapper
2026-06-27 12:56:48 +02:00
ajnart 398c88f2c6 style: auto-fix lint and format 2026-06-27 09:06:44 +00:00
Thomas Camlong 34d976eeea fix: apply version filter to LinuxServer.io provider
LSIO was bypassing versionRegex unlike all other providers.
2026-06-27 11:01:12 +02:00
Thomas Camlong 757d59854e fix: address PR review - atomic upsert, MCP metadata, stale tokens, save UX
- Wrap setSecret delete+insert in a cross-DB transaction
- Add MCP metadata to all widget secret procedures and register in mcp.ts
- Show configured tokens even when no repos use that provider
- Only clear token input after mutation succeeds, with loading state
2026-06-27 10:58:35 +02:00
Thomas Camlong 2155f13f24 fix: verbose error messages in all release providers
Every error now includes a diagnostic message:
- noMatchingVersion: shows the regex used, how many tags were
  checked, and sample tag names so users can see why matching failed
- noReleasesFound: distinguishes "no releases at all" from "no match"
- invalidIdentifier: shows the problematic identifier string
- unexpected: includes HTTP status codes from provider APIs

getLatestRelease returns { release } | { noMatch: reason } instead
of Release | null, carrying the diagnostic through.
2026-06-27 10:57:14 +02:00
Thomas Camlong d893515b93 fix: catch individual repo fetch failures to prevent batch crashes
Wrap each repository fetch in try/catch so one failure (Redis,
network, etc.) doesn't crash the entire query batch. Failed repos
now return a proper error response instead of causing all repos
to show "No response from provider".
2026-06-27 10:57:14 +02:00
Thomas Camlong 30107e4e76 fix: gracefully degrade when widget_secret table is missing or access denied
The releases widget was crashing because throwIfActionForbiddenAsync
or the widgetSecrets query would throw before DB migrations are run.
Wrap in try/catch so the widget works without tokens in that case.
2026-06-27 10:57:14 +02:00
ajnart 8c51a3769e style: auto-fix lint and format 2026-06-27 10:57:14 +02:00
Thomas Camlong 77884a3179 feat: add per-widget secret storage for release provider authentication
Adds encrypted token storage per release widget so users can authenticate
with GitHub, GitLab, Docker Hub, etc. without needing full integration setup.

- New `widget_secret` table (all 3 DB drivers) keyed by (itemId, kind)
- tRPC router with setSecret/deleteSecret/getConfiguredKinds (board modify access required)
- Releases router injects decrypted tokens into provider requests (board view access required)
- Tokens only used for repos matching widget's stored configuration (prevents proxy abuse)
- Provider auth: GitHub (Octokit auth), GitLab (PRIVATE-TOKEN), Docker Hub (Bearer),
  GHCR (registry token), npm (Bearer), Codeberg (token header)
- UI: "Provider Tokens" section in widget editor with PasswordInput per provider
2026-06-27 10:57:13 +02:00
ajnart d8a7477039 style: auto-fix lint and format 2026-06-27 10:57:13 +02:00
Thomas Camlong 8e1dcecf64 chore: update lockfile 2026-06-27 10:57:13 +02:00
Thomas Camlong c892731479 fix: address PR review findings for widget-only integration refactor
- Fix npm scoped package support (filter time metadata keys, make
  description optional, exclude falsy releases from version selection)
- Fix timetable date formatting (use Intl.DateTimeFormat for stable
  MM/DD/YYYY output) and named zod import
- Guard migration 0000 against unknown provider keys
- Fix migration 0003 to delete integration links by integrationId
- Reset immich album carousel index when photo list changes
- Add bounded limit to timetable router input
- Remove stale misspelled noProviderSeleceted translation key
- Fix provider name visibility in release widget settings
- Update docs: add ArchiveTeam Warrior note, fix typos, correct
  showPhotoInfo default
2026-06-27 10:57:13 +02:00
Thomas Camlong 62baaf7813 fix: update CHANGES.md for ArchiveTeam Warrior restoration, remove typo translation key 2026-06-27 10:57:13 +02:00
Thomas Camlong 8a5ad5861b fix: restore ArchiveTeam Warrior as a proper integration
The widget-only integrations refactor incorrectly removed the
ArchiveTeam Warrior integration entirely. It only needed to be
excluded from auto-seeding (by removing defaultUrl), not deleted.

This restores the integration class, reverts the widget/router/handler
to use integration middleware, and removes archiveTeamWarrior from the
widget-only-integrations migration.
2026-06-27 10:57:13 +02:00
ajnart d18d07d2f1 style: auto-fix lint and format 2026-06-27 10:57:12 +02:00
Thomas Camlong fb348044fb refactor widget-only integrations 2026-06-27 10:57:12 +02:00
homarr-releases[bot] f3608b26a0 chore: update bug report template (#6091)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-26 18:43:53 +00:00
Thomas 73bed51df8 fix(widgets): make downloads widget table background transparent (#6025)
The MantineReactTable defaults table cell backgrounds to
var(--mantine-color-body) which is opaque. This makes the
downloads widget table block appear solid while the footer
and other widgets correctly show the board's background.

Added a styles.css targeting table elements to force
background-color: transparent, following the same pattern
used by the beszel-system-table widget. Also added
bg=transparent to mantinePaperProps for the Paper wrapper.
2026-06-26 20:27:25 +02:00
Releases Homarr ab9aca8b60 chore(release): 1.68.0 [skip ci] 2026-06-26 18:23:57 +00:00
homarr-releases[bot] dcde2819eb chore(release): automatic release v1.68.0 2026-06-26 18:22:47 +00:00
Thomas abd16354bc feat: re-add Docker quick-access button in user menu (#6054)
Adds a Docker button to the user avatar dropdown menu (admin-only)
that opens a 1400px modal containing the same Docker container table
as /manage/tools/docker.

Closes #2165
2026-06-26 20:16:34 +02:00
homarr-update-contributors[bot] 6ac352c520 chore: update contributors (#6088)
* chore: update contributors

* style: auto-fix lint and format

---------

Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-26 18:14:48 +00:00
Thomas 19f9769d9e fix(manage/about): bypass self-fetch + add manage error boundary (LLM exploration) (#6009)
* fix(manage/about): bypass self-fetch to prevent ECONNREFUSED crash

The /manage/about page fetched its own API routes over HTTP
(http://$HOSTNAME:3000/api/about/...). When the server could not
reach itself (misconfigured HOSTNAME, restricted container networking,
reverse-proxy setups), fetch threw ECONNREFUSED and the whole page
crashed with a full-page Next.js error (issue #6008).

All four routes were force-static serving static JSON / a direct fs
read, so the HTTP round-trip was unnecessary. Replace the fetches
with direct JSON imports and a direct getDependenciesAsync() call,
eliminating the self-network dependency entirely.

Also add a manage-level error.tsx boundary (Next.js 16 convention) so
that any future runtime error in a manage page renders a friendly,
localised fallback inside the existing layout (sidebar stays usable)
instead of the ugly default full-page error.

Exploration draft by an LLM.

Refs #6008

* style: auto-fix lint and format

* refactor: use @static-data path alias, simplify error ternary

- Add @static-data/* tsconfig path alias to replace 7-level relative
  imports (../../../../../../../static-data/...) with clean alias
  imports across the about page and all 3 API route handlers.
- Simplify error.tsx: replace ternary with && short-circuit.
- Preserve CI lint bot's .sort() -> .toSorted() fix.

Refs #6008
2026-06-26 20:12:39 +02:00
homarr-renovate[bot] f0b2486c8a chore(deps): update dependency timezones-ical-library to ^2.2.1 (#6077)
* chore(deps): update dependency timezones-ical-library to ^2.2.1

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-26 16:18:40 +00:00
Matt Van Horn 2443b89b46 fix(plex): use series title for media releases instead of season label (#6083) 2026-06-26 14:42:36 +02:00
Thomas 915c8a3501 fix: show app first in add item modal (#6087) 2026-06-26 14:42:13 +02:00
a2bb53a8b8 feat: add VPN status widget (#5812)
Co-authored-by: Lokesh-Kumar-Dudi <lkumardudi@gmail.com>
Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
2026-06-26 14:29:23 +02:00
homarr-renovate[bot] f56a05bd8e chore(deps): update dependency video.js to ^8.23.9 (#6084)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-26 11:04:52 +00:00
homarr-renovate[bot] 6051b17401 chore(deps): update dependency nanoid@>=4.0.0 <5.0.9 to >=5.1.15 (#6079)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-25 16:37:40 +00:00
homarr-renovate[bot] 29eed08408 chore(deps): update dependency isomorphic-dompurify to ^3.18.0 (#6074)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 17:20:55 +00:00
homarr-renovate[bot] bd74fe4a61 chore(deps): update dependency posthog-js to ^1.391.0 (#6073)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 17:18:43 +00:00
homarr-renovate[bot] b88e8861e1 chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.62.1 (#6076)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 17:17:34 +00:00
homarr-renovate[bot] 39af3ec7e6 chore(deps): update dependency mantine-datatable to ^9.3.1 (#6072)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 17:17:15 +00:00
homarr-renovate[bot] 7f50be3dbb chore(deps): update dependency fast-xml-parser to ^5.9.3 (#6069)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 14:48:29 +00:00
homarr-renovate[bot] b6b7bea1dd chore(deps): update dependency mantine-contextmenu to ^9.3.1 (#6071)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 11:56:45 +00:00
homarr-renovate[bot] 9bde1dfbd6 chore(deps): update dependency pg to ^8.22.0 (#6068)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-24 04:26:21 +00:00
homarr-renovate[bot] c2712ed7f1 chore(deps): update tiptap monorepo to v3.27.1 (#6067)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 22:24:52 +00:00
homarr-renovate[bot] 16be7ece33 chore(deps): update dependency nanoid@>=4.0.0 <5.0.9 to >=5.1.14 (#6066)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 20:19:16 +00:00
homarr-renovate[bot] b0a46186d1 chore(deps): update node.js to v24.17.0 (#6043)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 18:07:27 +00:00
homarr-renovate[bot] d0385a4480 chore(deps): update dependency @ctrl/qbittorrent to ^9.13.0 (#5978)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 18:06:38 +00:00
homarr-renovate[bot] 3d6a3594a3 chore(deps): update pnpm to v11.8.0 (#6064)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 16:02:26 +00:00
homarr-renovate[bot] 641ee18278 chore(deps): update dependency hono@<4.6.5 to >=4.12.26 (#6062)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 12:44:43 +00:00
homarr-renovate[bot] 2e538bf40d chore(deps): update dependency lucide-react to ^1.21.0 (#6063)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 09:52:42 +00:00
homarr-renovate[bot] 1c0a94a6dc chore(deps): update playwright monorepo (#6045)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 00:26:23 +00:00
homarr-renovate[bot] 3a3dc449de chore(deps): update scalar monorepo (#6056)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 00:22:43 +00:00
homarr-renovate[bot] d6b156b8ee chore(deps): update dependency posthog-js to ^1.388.1 (#6041)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 00:21:38 +00:00
homarr-renovate[bot] fc4e31fb42 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.5 (#6033)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-23 00:19:54 +00:00
homarr-renovate[bot] b82b22431b chore(deps): update vitest monorepo to ^4.1.9 (#6037)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 22:15:25 +00:00
homarr-renovate[bot] 510cba295a chore(deps): update dependency oxfmt to ^0.55.0 (#6040)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 19:49:24 +00:00
homarr-renovate[bot] 56d838dafc chore(deps): update dependency fast-xml-parser to ^5.9.2 (#6039)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 19:45:28 +00:00
homarr-renovate[bot] eae9f0438c chore(deps): update oxlint monorepo (#6044)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 16:17:14 +00:00
homarr-renovate[bot] b81f729bfd chore(deps): update testcontainers-node monorepo to ^12.0.3 (#6057)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 16:07:28 +00:00
homarr-renovate[bot] 86b83c5425 chore(deps): update mantine monorepo to ^9.3.2 (#6036)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 16:05:35 +00:00
Thomas bbc4589c9e fix(beszel): leverage TanStack Query for stale-while-revalidate caching (#6055) 2026-06-22 17:17:09 +02:00
Thomas ea9dd716f0 Merge pull request #6053 from homarr-labs/beszel-1w-30d-data-view
fix(beszel): use 480m interval for 1w/30d stats views
2026-06-22 15:44:43 +02:00
Thomas Camlong 4383da38ef fix(beszel): use 480m interval for 1w/30d stats views
Beszel only stores stats at 1m, 10m, 20m, 120m, 480m intervals.
The 1440m type doesn't exist, so queries for 1w/30d returned empty
results. Use 480m (8h) with appropriate perPage (21 for 1w, 90 for 30d).
2026-06-22 15:44:14 +02:00
homarr-crowdin[bot] 58cb65cb3a chore(lang): updated translations from crowdin (#6029) 2026-06-22 15:23:27 +02:00
Matt Van Horn 89380f81dd feat: ship source maps for readable production stack traces (#6031) 2026-06-22 15:22:54 +02:00
Thomas e01571b221 fix(icon-picker): keep URL visible in edit mode, add SVG-first ordering (#6051) 2026-06-22 15:22:25 +02:00
Thomasandajnart 49c266374d fix(beszel): harden cache layer and prevent blank widgets (#6052)
* fix(beszel): harden cache layer and prevent blank widgets

- Add debug logging across Beszel integration, request handler, and tRPC
  router (auth, fetch, SSE, per-procedure entry/exit) gated behind
  LOG_LEVEL=debug
- cached-request-handler: retry with backoff, isValid predicate to skip
  caching invalid data, in-flight dedup (single-flight per channel) to
  avoid thundering herd after cache invalidation
- redis channel: set-then-publish so storage is always consistent even
  when pubsub publish fails
- Beszel handlers: opt into fallbackToStaleOnError and retry so brief
  upstream hiccups don't throw away good cached data
- beszel-system-stats: add refetchInterval matching each time period's
  cache TTL so historical charts refresh in the background
- tRPC router: switch Promise.all -> Promise.allSettled for getSystems
  and getAlerts; getSystemStats returns an error object instead of
  throwing. Each result carries an optional error field so one failed
  integration no longer nukes the whole widget
- Widgets: add explicit isPending loader, empty state for grid and
  stats, and a shared per-integration error indicator (tooltip with
  failed integration names). Alerts and table now only show their
  empty state after loading completes
- Translation: add empty.noSystems key to beszelSystemGrid and
  beszelSystemStats

Addresses #6030

* style: auto-fix lint and format

---------

Co-authored-by: ajnart <ajnart@users.noreply.github.com>
2026-06-22 15:20:12 +02:00
homarr-renovate[bot] 577950d20e chore(deps): update tiptap monorepo to v3.27.0 (#6047)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 11:10:10 +00:00
homarr-renovate[bot] 82bf71b073 chore(deps): update pnpm to v11.7.0 (#6046)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 11:08:43 +00:00
homarr-renovate[bot] e26883acfa chore(deps): update lucide monorepo (#6042)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 11:05:29 +00:00
homarr-renovate[bot] 8ee747cde8 chore(deps): update dependency axios@>=1.0.0 <1.8.2 to >=1.18.0 (#6038)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 10:57:58 +00:00
homarr-renovate[bot] faa7e55b1b chore(deps): update dependency video.js to ^8.23.8 (#6035)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 10:50:43 +00:00
homarr-renovate[bot] ce021a89ac chore(deps): update dependency posthog-node to ^4.18.0 (#6034)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-22 10:50:04 +00:00
Thomas 55fb227305 fix(beszel): improve stats date display, add click-to-stats modal, fix grid ordering (#6026) 2026-06-20 15:38:04 +02:00
Thomas 39e424563a fix(beszel): widget transparency, error handling, and cache invalidation (#5993) 2026-06-20 12:21:26 +02:00
Afonso Jorge Ramos e29c79a902 fix(widgets): clean up health monitoring disk and size display (#6019) 2026-06-20 11:47:38 +02:00
Afonso Jorge Ramos a19c9d88ca fix(widgets): hide system disk usage value when it overflows and show it in a tooltip (#6022) 2026-06-20 11:45:39 +02:00
Afonso Jorge Ramos 1fb97ee9c6 fix(unraid): report disk free space instead of total size (#6018) 2026-06-20 10:12:11 +02:00
Afonso Jorge Ramos f69e545d64 fix(truenas): report actual memory usage and pool free space (#6017) 2026-06-20 10:11:12 +02:00
Afonso Jorge Ramos 52df56b19d fix(widgets): hide health monitoring default tab option for non-proxmox integrations (#6021) 2026-06-20 08:14:56 +02:00
Afonso Jorge Ramos 0f66bfd6c1 fix(integrations): prevent certificate trust button from submitting the form (#6020) 2026-06-20 07:46:36 +02:00
Thomas 842c5a0b4c Merge pull request #6013 from homarr-labs/chore/update-cron 2026-06-20 07:27:19 +02:00
homarr-crowdin[bot] ce9134dee2 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-06-20 02:14:01 +00:00
homarr-releases[bot] 9ce9be6b7e chore: update bug report template (#6015)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-19 20:44:05 +00:00
Releases Homarr 7ec22411a6 chore(release): 1.67.0 [skip ci] 2026-06-19 20:27:41 +00:00
homarr-releases[bot] d3ae06e515 chore(release): automatic release v1.67.0 2026-06-19 20:26:34 +00:00
Manuel d09a4f8468 chore: update cron 2026-06-19 22:08:25 +02:00
Thomas e1a245ff2d fix(auth): isolate session cookies from other Auth.js apps on same host (#5996)
Prefix all Auth.js cookies (default "homarr") so they no longer collide
with other Auth.js apps on the same hostname.
2026-06-19 21:24:15 +02:00
homarr-crowdin[bot] bf4396917e chore(lang): updated translations from crowdin (#5994)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-06-19 21:22:48 +02:00
Thomas d059023b51 feat(analytics): migrate from Umami to PostHog server-side analytics (#5983)
* feat(analytics): migrate from Umami to PostHog server-side analytics

Replace @umami/node with posthog-node for all server-side telemetry.
Add singleton PostHog client, feature tracking POC, DAU heartbeat,
and tRPC analytics router. Cron job is now locked (no manual trigger,
no custom interval). Update consent copy to explain what we collect
and why.

* style: auto-fix lint and format

* refactor(analytics): simplify + add client-side posthog-js init

- Inline constants.ts and track.ts into client.ts (single source)
- Remove dead exports (shutdownPostHogAsync, getPostHogClient)
- Flatten all DB queries into one Promise.all (no waterfall)
- Derive integration/widget totals from group-by (drop redundant counts)
- Add posthog-js with dynamic import, gated on enableGeneral
- Move Analytics component to body, pass consent from server settings

* style: auto-fix lint and format

* feat(cron): add hidden flag to cron jobs, hide analytics from task panel

The analytics job runs silently in the background — users don't need
to see or interact with it. Hidden jobs are filtered out of getAllAsync
so they never appear in the management UI.

* refactor(analytics): simplify to single toggle, always collect all data

Remove granular enableWidgetData/enableIntegrationData/enableUserData
toggles. Analytics is now a single on/off switch. When enabled, all
anonymous usage data is collected in one flat Promise.all call.

Side effect: existing users with enableGeneral=true who had granular
flags off will now send all data. This is by design — the granular
controls added complexity without meaningful privacy benefit since
the data is already anonymous counts.

* style: auto-fix lint and format
2026-06-19 21:21:50 +02:00
d9cbd5427d feat: Calculate and display total CPU and memory usage (#5911)
* Calculate and display total CPU and memory usage

Added total CPU and memory usage calculations for containers.

* Refactor Docker component layout

Refactor component layout and remove redundant Group elements.

* Create test.yaml

* Add files via upload

* Delete packages/icons/icons directory

Accidental

* Add total CPU and memory strings to English translations

* Add English translations and size refactor

* Fix typo in search label in en.json

* chore(docs): update OpenAPI spec

* Remove color styling from CPU and memory usage text

* Enhance Docker component with footer and toolbar updates

Updated createColumns function to include totals and relativeTime parameters for footer display. Adjusted table configuration to enable bottom toolbar based on isTiny flag.

* Fix formatting and add missing newline at end of file

* Enable sticky header and footer in component

Enable sticky header and add footer row properties for sticky positioning.

* Refactor table component properties

* Refactor table columns and fix footers

* Revert "chore(docs): update OpenAPI spec"

This reverts commit 26f3c809d9.

---------

Co-authored-by: Bergasha <Bergasha@users.noreply.github.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2026-06-19 20:58:57 +02:00
Matt Van HornandMatt Van Horn c56d9d6894 fix(downloads): sort stalled torrents last when sorting by finish time (#6011)
Stalled torrents have time=0 (infinite/never), which raw numeric sort
places between negative (completed) and positive (active) values.
Add a custom sortingFn on the time column that maps 0 to Infinity so
stalled entries always sort last in ascending order and first in
descending order.

Closes #5416

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-19 20:42:48 +02:00
homarr-update-contributors[bot] 3ea49f0391 chore: update contributors (#6010)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-19 15:02:14 +00:00
homarr-renovate[bot] e2f8f5f081 chore(deps): update lucide monorepo to ^1.18.0 (#5986)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 16:49:33 +00:00
homarr-renovate[bot] f57037b568 chore(deps): update dependency mantine-datatable to ^9.3.0 (#5992)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 16:49:08 +00:00
homarr-renovate[bot] 84803aeefb chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.62.0 (#5998)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 16:48:53 +00:00
homarr-renovate[bot] 5f47298f3d chore(deps): update dependency isomorphic-dompurify to ^3.17.0 (#6003)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 16:48:07 +00:00
homarr-renovate[bot] de858011fc chore(deps): update dependency mantine-contextmenu to ^9.3.0 (#5991)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 13:08:17 +00:00
homarr-renovate[bot] 12f8e2b83a chore(deps): update dependency sass to ^1.101.0 (#5979)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 13:06:59 +00:00
homarr-renovate[bot] 78b6e520ac fix(deps): update tailwindcss monorepo to ^4.3.1 (#5988)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 13:04:08 +00:00
Thomas Camlong bcaa93f90b chore: trigger release build for v1.66.1 2026-06-18 13:39:22 +02:00
homarr-releases[bot] 15e6612697 chore: update bug report template (#6001)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-18 11:39:09 +00:00
Thomas Camlong eef06dd5a3 Revert "chore(release): 1.66.0 [skip ci]"
This reverts commit 84e044f427.
2026-06-18 13:17:30 +02:00
Releases Homarr 84e044f427 chore(release): 1.66.0 [skip ci] 2026-06-18 11:14:43 +00:00
homarr-releases[bot] 609ab9976a chore(release): automatic release v1.66.0 2026-06-18 11:13:29 +00:00
Thomas Camlong 28f570aa79 Revert "chore(release): 1.66.0 [skip ci]"
This reverts commit 5b4f03da35.
2026-06-18 13:07:44 +02:00
Releases Homarr 5b4f03da35 chore(release): 1.66.0 [skip ci] 2026-06-18 10:55:09 +00:00
Matt Van Horn 6960ac15e9 fix(integrations): make Emby Studios field optional (#5997) 2026-06-18 12:45:59 +02:00
Thomas f1ee76c99c fix(db): crash on MySQL — replace onConflictDoNothing with dialect-neutral pre-filter (#5995) 2026-06-18 12:45:02 +02:00
homarr-renovate[bot] 9a9ee5a9d7 chore(deps): update tiptap monorepo to v3.26.1 (#5976)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 09:24:34 +00:00
homarr-renovate[bot] 7debe34e24 chore(deps): update dependency jotai to ^2.20.1 (#5974)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 04:14:37 +00:00
homarr-renovate[bot] 0d67c5a451 chore(deps): update scalar monorepo to ^0.9.46 (#5975)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 23:36:17 +00:00
homarr-renovate[bot] 2db0fe15fb chore(deps): update testcontainers-node monorepo to ^12.0.2 (#5987)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 21:20:11 +00:00
homarr-releases[bot] 4b85afbb87 chore: update bug report template (#5990)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-17 20:10:20 +00:00
homarr-crowdin[bot]andThomas 536f82af04 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: Thomas <thomas@ajnart.fr>
2026-06-17 20:00:36 +00:00
Thomas 445e191490 fix: persist Beszel server selection and improve picker visibility (#5985) 2026-06-17 17:06:23 +02:00
Thomas 52c624addb Merge pull request #5966 from homarr-labs/feat/beszel-fixes
fix(beszel): chart color mismatch, system selector UX, SSE infrastructure, and widget fixes
2026-06-17 11:39:23 +02:00
homarr-renovate[bot] 91f9329773 chore(deps): update pnpm to v11.6.0 (#5980)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 11:38:47 +02:00
homarr-renovate[bot] 4f1fc4d616 chore(deps): update pnpm to v11.6.0 (#5980)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 00:29:01 +00:00
ajnart 8bee0174e9 style: auto-fix lint and format 2026-06-16 16:30:39 +00:00
Thomas Camlong 45dfe3b9ab fix: address PR review findings
- Remove ternary operators from new code (widget-integration-select,
  TabsContainer, mock data, members page)
- Remove stale /docs/management/tools/backup from sitemap type
- Add value prop to error-state Select in beszel-system-stats
2026-06-16 18:29:28 +02:00
Thomas Camlong 5078c300f8 fix(widgets): race conditions in useLiveStats and stale value in integration select
- Add generation guard to prevent late SSE events from writing stale
  data after systemId/integration changes
- Sort integrationIds for stable key (avoids unnecessary resubscribes)
- Include integrationKey and systemId in flush interval deps
- Preserve current form value in WidgetIntegrationSelectInput when it's
  missing from loaded options (matches WidgetDynamicSelectInput pattern)
2026-06-16 17:59:41 +02:00
Thomas Camlong 065b78fa3e fix(widgets): prevent TabsContainer state reset on data refetch
react-jsx-parser auto-generates random keys on every render, causing
full remounts that reset uncontrolled Tabs to defaultValue. Fix by:
- Adding disableKeyGeneration to preserve React reconciliation
- Memoizing bindings to avoid unnecessary re-parses
- Converting TabsContainer to controlled Tabs with useState
- Validating defaultTab against actual panel values
- Clearing stale parseErrors when template/data changes
2026-06-16 17:55:37 +02:00
Thomas Camlong 39900090b3 fix(ui): prevent icon/name wrapping in custom widgets table
The Group in the Name cell lacked wrap="nowrap", causing the icon to
detach onto its own line when the widget name was too long.
2026-06-16 17:24:21 +02:00
Thomas Camlong 2af2d59b8f docs: align Gotify integration page with documentation PR #558
Match the official Gotify description wording and list Gotify as a
supported integration on the Notifications widget page.
2026-06-16 16:41:30 +02:00
Thomas Camlong d0450b5153 docs(integrations): add missing Gotify integration page
The Gotify integration (merged from dev) declares a documentationUrl but
had no docs page, failing the docs-coverage validation plugin once gotify
was added to the integration doc-slug map.
2026-06-16 16:30:07 +02:00
Thomas Camlong 5f588ae287 fix(definitions): add doc slugs for gotify and archiveTeamWarrior
Merging dev introduced the Gotify integration and ArchiveTeam Warrior
widget/integration, which require entries in the doc-slug maps and the
generated documentation sitemap. Also remove a misplaced non-null
assertion in the group members page flagged by oxlint.
2026-06-16 16:23:02 +02:00
Thomas Camlong 066fb038b5 Merge remote-tracking branch 'origin/dev' into feat/beszel-fixes 2026-06-16 16:08:32 +02:00
Thomas Camlong ece436a1cc fix(docs): use valid string type for beszel system stats config
The "text" type is not a valid WidgetConfigurationItem value type;
the searchable system selector maps to "string".
2026-06-16 15:59:20 +02:00
Thomas Camlong 4da2feda35 refactor(beszel): tighten background color lookup type 2026-06-16 15:49:06 +02:00
Thomas Camlong 0a05bbed8d fix(beszel): review fixes for MCP and widget styling
- Throw TRPCError instead of returning null when no integration
- Sort beszelRouter import alphabetically in mcp.ts
- Replace ternary with lookup table for background color
- Mention integration_all in MCP server instructions
2026-06-16 15:18:13 +02:00
Thomas Camlong 420c0c30e4 fix(beszel): MCP description accuracy and type strictness
- Add .default("1h") to timePeriod so MCP clients can omit it
- Include "1m" in the description's valid timePeriod values
- Clarify that getSystemStats uses only the first integrationId
- Use MantineSize type instead of plain string for itemRadius
2026-06-16 15:13:35 +02:00
Thomas Camlong 8600c63bab feat(beszel): expose Beszel systems, alerts, and stats via MCP
Register beszelRouter in the MCP server with .meta({ mcp: enabled })
on getSystems, getAlerts, and getSystemStats procedures. AI assistants
can now query Beszel-monitored systems, view alerts, and fetch
historical metrics through the MCP protocol.

Ref #5968
2026-06-16 15:09:36 +02:00
Thomas Camlong 2a5398bec0 fix(beszel): system grid widget respects board opacity and radius
The inner Card components used Mantine's default opaque background and
hard-coded radius, clashing with board styling. Now uses board.opacity
and board.itemRadius with the same pattern as system-resources widgets.

Closes #5968
2026-06-16 15:02:58 +02:00
ajnart c751feedf7 style: auto-fix lint and format 2026-06-16 12:39:26 +00:00
Thomas Camlong cbb5e301dc refactor(widgets): replace beszelSystem option with generic integrationSelect 2026-06-16 14:38:25 +02:00
Thomas Camlong ea8f07e2bd docs(management): move SQLite Backup & Restore out of Tools category 2026-06-16 14:20:57 +02:00
Louis Bailleau fb18b975b2 feat: add local group support for OIDC users (#5952) 2026-06-16 13:45:44 +02:00
homarr-update-contributors[bot] 4692e7ce3a chore: update integrations list readme (#5973)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-16 10:45:49 +02:00
GlareandManuel dd57ee2102 feat(integrations): add ArchiveTeam Warrior widget (#5784)
* feat(integrations): add ArchiveTeam Warrior widget

* feat(integrations): add support for basic auth for archive warrior

* refactor(integrations): address ArchiveTeam Warrior review feedback

* Increase cache duration from 1 to 5 minutes

---------

Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
2026-06-16 10:43:55 +02:00
homarr-update-contributors[bot] 2ae83458e6 chore: update integrations list readme (#5972)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-16 10:40:19 +02:00
Nerdy Technician 5a1967bdd4 feat(integrations): add Gotify notifications integration (#5847)
* feat(integrations): add Gotify notifications integration

Closes #4186

> "It would be really useful if Homarr could integrate with Gotify —
> view notifications from Gotify right inside the Homarr dashboard,
> maybe as a widget or a small panel. A widget that displays the latest
> messages from Gotify (with title, message, time)."
> — #4186: feat: Gotify Integration - display notifications in Homarr

- `gotify/gotify-schema.ts` — Zod schema validating the `GET /message`
  API response from the Gotify server, including id, date, title,
  message body, and optional priority fields.
- `gotify/gotify-integration.ts` — `GotifyIntegration` class extending
  the base `Integration` and implementing `INotificationsIntegration`.
  Authenticates via HTTP Basic Auth (username + password) and fetches
  the latest 100 messages from the `/message` endpoint. Connection
  testing hits `/health`.
- `index.ts` — exports `GotifyIntegration` from the package public API.
- `base/creator.ts` — registers `GotifyIntegration` in the integration
  creator map so it can be instantiated at runtime.

- `integration.ts` — registers the `gotify` integration definition with:
  - `secretKinds: [["username", "password"]]` (Basic Auth credentials)
  - `category: ["notifications"]` (picked up automatically by the
    Notifications widget via `getIntegrationKindsByCategory`)
  - Icon sourced from `homarr-labs/dashboard-icons`
  - Documentation URL pointing to `/docs/integrations/gotify`
- `docs/homarr-docs-sitemap.ts` — adds `/docs/integrations/gotify` to
  the `HomarrDocumentationPath` union type so the documentation link
  passes the TypeScript path validation check.

Because `gotify` is registered under the `notifications` category, the
existing Notifications widget automatically supports it with no widget
code changes required. Users add the integration via Settings →
Integrations, supply their Gotify server URL, username, and password,
then attach it to a Notifications widget on any board.

* refactor(integrations): inline json parse and add error handling in Gotify integration

Address PR review feedback: remove intermediate `as unknown` cast by inlining
response.json() directly, and use safeParseAsync to handle deserialization
failures gracefully instead of throwing unhandled.
2026-06-16 10:29:48 +02:00
homarr-crowdin[bot] a4f0eda4a0 chore(lang): updated translations from crowdin (#5965)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-06-16 10:27:23 +02:00
homarr-renovate[bot] 1e07ce5c96 chore(deps): update mantine monorepo to ^9.3.1 (#5956)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-16 10:26:10 +02:00
homarr-renovate[bot] 176222f56b chore(deps): update dependency form-data@>=4.0.0 <4.0.4 to >=4.0.6 [security] (#5969)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-16 10:25:28 +02:00
homarr-renovate[bot] d6ce320eb2 chore(deps): update turbo monorepo to ^2.9.18 (#5971)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-16 10:24:22 +02:00
homarr-renovate[bot] 094f23778c chore(deps): update dependency @types/node to ^24.13.2 (#5970)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-16 10:23:50 +02:00
homarr-update-contributors[bot] ec9f3cc5ad chore: update contributors (#5945)
* chore: update contributors

* style: auto-fix lint and format

---------

Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-06-16 10:21:33 +02:00
homarr-releases[bot] 7ff115465a chore: update bug report template (#5944)
* chore: update bug report template

* style: auto-fix lint and format

---------

Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-16 10:21:19 +02:00
Matt Van HornandMatt Van Horn 58058f2ea6 fix: make quicklook optional in Glances integration schema (#5949)
On macOS (and some Glances configurations) the /api/4/all endpoint
omits the quicklook key, causing a Zod invalid_type error and crashing
the system health widget. Mark quicklook as .optional() in allSchema
and add a null-safe fallback (`?? "Unknown"`) for cpuModelName.

Fixes #5728

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-16 10:17:56 +02:00
Daniel Lopes 6881b8355c fix: fixes and unifies descriptions in dns-hole-controls and dns-hole-summary docs (#5938) 2026-06-16 10:15:56 +02:00
homarr-renovate[bot] 7837b6fa6b chore(deps): update pnpm to >=11.5.2 (#5941)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-15 21:55:49 +00:00
homarr-renovate[bot] f263388f71 chore(deps): update turbo monorepo to ^2.9.17 (#5959)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-15 18:40:14 +00:00
Thomas Camlong 9aecb2ae13 refactor(beszel): replace last ternary in subscribeSystemStats 2026-06-15 17:33:58 +02:00
ajnart 1d691c0994 style: auto-fix lint and format 2026-06-15 15:33:22 +00:00
Thomas Camlong baf2b09327 refactor(beszel): consolidate hooks, extract helpers, deduplicate SSE envelope
Consolidate 5 stat mapper useCallbacks and 6 series useMemos into
single mappers/series objects. Extract statsWhenShown helper for
repeated visibility guard pattern. Deduplicate SSE record envelope
construction. Simplify prepareRecords with if/else. Remove unused
gridColumns ternary with Number() indexing.
2026-06-15 17:30:50 +02:00
ajnart 3d93ae1246 style: auto-fix lint and format 2026-06-15 15:25:49 +00:00
Thomas Camlong df66cf25c7 refactor(beszel): remove ternary operators and fix dead code
Replace all ternary operators with if/else, &&/||, or early returns.
Remove unused noAnimation constant that caused lint error.
Fix variable shadowing in tooltip deduplication loop.
2026-06-15 17:24:18 +02:00
ajnart 15ed1e51fd style: auto-fix lint and format 2026-06-15 15:15:38 +00:00
Thomas Camlong 9abce89a14 perf(beszel): throttle live renders, reduce buffer, skip hidden charts
- Decouple 1Hz SSE ingestion from render rate: accumulate data in a
  ref and flush to React state every 3s instead of every tick
- First data point flushes immediately to avoid blank screen
- Reduce MAX_BUFFER from 60 to 30 (halves SVG reconciliation work)
- Gate chart data hooks behind visibility flags so disabled charts
  skip all array processing
2026-06-15 17:14:18 +02:00
ajnart 7ffb90abd2 style: auto-fix lint and format 2026-06-15 14:58:56 +00:00
Thomas Camlong 4a65d2ae87 fix(beszel): don't clear live data on transient connection errors
Keep showing existing charts during brief WebSocket reconnections
instead of flashing the error UI. The error state only shows when
no data was ever received (genuine initial connection failure).
2026-06-15 16:57:35 +02:00
ajnart 4857b72804 style: auto-fix lint and format 2026-06-15 14:14:28 +00:00
Thomas Camlong 8134b168e1 perf(beszel): deep memoization pass for live chart rendering
- Set withDots={false} to eliminate thousands of hidden SVG Dot nodes
- Disable Recharts animation (isAnimationActive: false) during data updates
- Merge ChartPanel + BeszelAreaChart into BeszelChartPanel to fix
  React.memo being defeated by children prop creating new refs
- Memoize yAxisProps inside BeszelAreaChart instead of rebuilding inline
- Hoist chartStyle, panelStyle, CPU_Y_AXIS_DOMAIN to module constants
- Memoize subscription input/enabled in useLiveStats
- Replace O(n) find() with Map lookup in useDockerChartData hot path
2026-06-15 16:12:47 +02:00
ajnart 583b1d2935 style: auto-fix lint and format 2026-06-15 14:09:55 +00:00
Thomas Camlong 6189daf6a4 perf(beszel): fix live mode re-render cascade for Docker charts
- Stabilize useContainerNames with ref comparison so it returns the
  same array reference when container names haven't changed
- Memoize containerSeries (was computed inline every render)
- Hoist all system chart series arrays into useMemo
- Hoist activeDotProps/dotProps to module-level constants
- Wrap BeszelAreaChart and ChartPanel in React.memo
2026-06-15 16:07:37 +02:00
ajnart 569293275d style: auto-fix lint and format 2026-06-15 14:06:34 +00:00
Thomas Camlong d6ced037df refactor(beszel): deduplicate alerts query input and document systemId option
- Memoize alerts query input to avoid triple repetition
- Add system selector to docs configuration items
2026-06-15 16:04:59 +02:00
ajnart 8d2b2ee74a style: auto-fix lint and format 2026-06-15 14:03:03 +00:00
Thomas Camlong c5b4cf9a0e fix(beszel): add recovery button when live mode fails
When SSE connection fails, the error UI now shows a specific message
and a button to switch back to historical view, so users don't need
to reload the page to recover.
2026-06-15 16:01:14 +02:00
ajnart 2316f6e79d style: auto-fix lint and format 2026-06-15 13:57:09 +00:00
Thomas Camlong 16094876fa fix(beszel): restore timePeriod cast and improve error visibility
- Restore timePeriod union cast to fix typecheck (TS2769)
- Clear live data on subscription error so error UI always shows
- Emit error when no integration is configured for live subscription
- Add error state to system select input when query fails
2026-06-15 15:55:41 +02:00
ajnart 6cb9024781 style: auto-fix lint and format 2026-06-15 13:50:38 +00:00
Thomas Camlong a68eee24eb fix(beszel): improve live subscription error handling and cleanup
- Surface subscription errors in useLiveStats via onError + error state
- Show error UI in live mode when SSE connection fails
- Abort SSE reader on subscribeTopic failure instead of silent log
- Guard mock realtime against pre-aborted signal to prevent interval leak
- Remove dead includeDocker from subscription input schema
2026-06-15 15:49:14 +02:00
ajnart 2d4832dc96 style: auto-fix lint and format 2026-06-15 13:40:03 +00:00
Thomas Camlong e29c46cc5a fix(beszel): address final review findings
- Gate stats/live queries on systems loading state to prevent stale ID
  requests during initial load
- Simplify useLiveStats: functional setState, stable integrationKey dep,
  remove unused includeDocker param
- Extract prepareRecords helper to deduplicate chart ordering logic
- Remove stale timePeriod type cast
2026-06-15 15:39:01 +02:00
homarr-renovate[bot] 85e74691f2 fix(deps): update dependency postcss to ^8.5.15 (#5940)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-15 13:27:40 +00:00
Thomas Camlong ed333e1cbb feat(beszel): add searchable system select input for widget options
Replace plain text systemId option with a dedicated beszelSystem factory
type backed by a searchable Select that fetches available systems from
the integration. Shows proper states for no integration, loading, and
no systems found.
2026-06-15 15:16:59 +02:00
ajnart e0e493e2f9 style: auto-fix lint and format 2026-06-15 13:16:43 +00:00
Thomas Camlong d13effdda0 fix(beszel): address review findings for live streaming
- Skip toReversed() for live data (already chronological from buffer)
- Reset live buffer on systemId/integration change to avoid stale data
- Throw on unexpected SSE stream end so subscription emits error
- Delete unused time-period.ts config (duplicate of server-side config)
2026-06-15 15:15:06 +02:00
Thomas Camlong 7e46c71187 fix(beszel): remove unused {name} placeholder from systemNotFound translation 2026-06-15 15:11:48 +02:00
ajnart 4e32e81c54 style: auto-fix lint and format 2026-06-15 13:11:01 +00:00
Thomas Camlong c5266a7a42 feat(beszel): wire live SSE streaming for 1-minute time period
Add "Live" option to time period selector that uses the existing
subscribeSystemStats tRPC subscription (PocketBase SSE) instead of
REST polling. A useLiveStats hook accumulates incoming data points
in a 60-entry rolling buffer. Charts use HH:mm:ss timestamps in
live mode.
2026-06-15 15:09:44 +02:00
ajnart 680cbc69d8 style: auto-fix lint and format 2026-06-15 13:00:46 +00:00
Thomas Camlong e22eee271c docs(beszel): update system stats widget documentation and add time-period config
Update docs with new widget options and add client-side time-period
configuration for future live streaming support.
2026-06-15 14:59:01 +02:00
Thomas Camlong 441cc692f9 feat(beszel): add error definitions and retry config to beszel widgets
Add INTERNAL_SERVER_ERROR error definitions to alerts, grid, and table
widgets. Set retry: false and appropriate staleTime/refetchInterval.
2026-06-15 14:58:53 +02:00
Thomas Camlong c9769bf530 feat(beszel): add SSE subscription endpoint and per-period cache TTL
- Add subscribeSystemStats tRPC subscription bridging PocketBase SSE
- Add subscribeRealtimeMetrics to BeszelIntegration with SSE parsing
- Add error handling: try/catch in subscription, response check in topic POST
- Add cacheDurationForInput to request handler for per-period cache TTL
- Add mock subscribeRealtimeMetrics with 1s interval for dev testing
2026-06-15 14:58:47 +02:00
Thomas Camlong 5035c26dbd feat(beszel): add translation keys for system stats widget
Add systemId option label, systemNotFound error message, and
internalServerError translations for all beszel widgets.
2026-06-15 14:58:38 +02:00
Thomas Camlong 4d8beac7d3 style(beszel): reorder container colors to rainbow spectrum
Rearrange containerColors from random order to rainbow sequence
(red -> orange -> yellow -> ... -> violet -> grape -> pink).
2026-06-15 14:58:32 +02:00
Thomas Camlong 824f31d6aa fix(beszel): deduplicate tooltip entries and round indicator pill
Non-stacked charts produce duplicate Recharts payload entries (stroke +
fill). Deduplicate by dataKey keeping the first entry (correct color).
Also increase indicator borderRadius for a fully rounded pill.
2026-06-15 14:58:27 +02:00
Thomas Camlong 90ceaf5fe8 feat(beszel): add hover-reveal CSS for system toggle button
System selector icon fades in on widget hover via CSS module.
2026-06-15 14:58:20 +02:00
Thomas Camlong 82eac2dcc1 fix(beszel): make chart stacking configurable and fix color mismatch
BeszelAreaChart hardcoded type="stacked" for all charts, causing Docker
Network I/O to render blended colors that didn't match the tooltip.

- Make `type` prop configurable (default "default")
- Only stack Memory, Docker CPU, and Docker Memory charts
- Add systemId as a persisted widget option with Menu-based selector
- Replace Popover with Menu trigger="click-hover" for proper hover UX
- Show error state with Select when selected system is not found
- Remove 1-minute time period (live SSE not wired yet)
2026-06-15 14:58:14 +02:00
homarr-renovate[bot] ab41b76a66 chore(deps): update semantic-release monorepo to ^25.0.4 (#5960)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-15 06:36:32 +00:00
homarr-renovate[bot] 1f474c9ee7 chore(deps): update nextjs monorepo to v16.2.8 (#5961)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-15 00:21:13 +00:00
homarr-renovate[bot] 0c15dc13fa chore(deps): update oxlint monorepo to ^1.69.0 (#5953)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-14 22:22:45 +00:00
homarr-renovate[bot] 992c48fdde chore(deps): update dependency undici to v7.27.2 (#5424)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-14 20:28:50 +00:00
homarr-renovate[bot] 7ccaabbd97 chore(deps): update scalar monorepo to ^0.9.43 (#5954)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-14 18:27:39 +00:00
homarr-renovate[bot] 030b84674f chore(deps): update dependency hono@<4.6.5 to >=4.12.24 (#5950)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-14 16:32:22 +00:00
Thomas 5fe43eb363 fix(beszel): correct unit scaling for system stats and container charts (#5958) 2026-06-14 18:21:29 +02:00
811 changed files with 63096 additions and 21875 deletions

No files matched your search

+7
View File
@@ -6,7 +6,14 @@ npm-debug.log
README.md
.next
.turbo
.pnpm-store
**/.pnpm-store
coverage
**/coverage
**/.docusaurus
**/build
.git
.github
dev
.build
e2e
+8 -1
View File
@@ -59,4 +59,11 @@ TURBO_TELEMETRY_DISABLED=1
# ENABLE_KUBERNETES=true
# Enable mock integration
UNSAFE_ENABLE_MOCK_INTEGRATION=true
UNSAFE_ENABLE_MOCK_INTEGRATION=true
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
# DEMO_MODE=true
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
# Set to false for writable preview deployments so the demo user can modify the dashboard.
# DEMO_READ_ONLY=false
+8
View File
@@ -33,6 +33,14 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.70.0
- 1.69.2
- 1.69.0
- 1.68.0
- 1.67.0
- 1.66.1
- 1.66.0
- 1.65.0
- 1.64.0
- 1.62.0
- 1.61.0
+2
View File
@@ -0,0 +1,2 @@
.secrets
!.secrets.example
+4
View File
@@ -0,0 +1,4 @@
TURBO_API=
TURBO_TEAM=
TURBO_TOKEN=
TURBO_REMOTE_CACHE_SIGNATURE_KEY=
+36
View File
@@ -0,0 +1,36 @@
# Running CI locally
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
```bash
pnpm ci:act:fast
pnpm ci:act:docker
pnpm ci:act:all
pnpm ci:act:cleanup
```
`fast` runs the quality gate. `docker` and `all` run the gate followed by the single container build, E2E suite, and memory regression test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
```bash
ACT_SECRET_FILE=.github/act/.secrets pnpm ci:act:all
```
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
## PR preview images
Successful non-draft pull requests from this repository publish an amd64 image:
```bash
docker pull ghcr.io/homarr-labs/homarr-test:pr-<number>
```
The tag is replaced when the pull request is updated and deleted when it closes. Production Homarr images remain the supported multi-architecture releases. The `homarr-test` package must be configured as public in GHCR for unauthenticated testers.
## Dokploy preview cache
Keep `needs-demo` as the Dokploy preview label. Under the application's **Preview Build-time Secrets**, configure `TURBO_API`, `TURBO_TEAM`, `TURBO_TOKEN`, and `TURBO_REMOTE_CACHE_SIGNATURE_KEY`. Dokploy passes these to the Dockerfile as BuildKit secrets. Do not configure the token or signature key as Docker build arguments.
Rotate any credentials that have appeared in chat, logs, or committed configuration before enabling the cache.
+1 -1
View File
@@ -11,7 +11,7 @@ outputs:
runs:
using: "composite"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up Docker Buildx
+8
View File
@@ -11,3 +11,11 @@
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project
**When contributing a new integration or widget:**
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
tests (e.g. what happens if it is offline)
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
provide support and maintain the integration (e.g. in case of breaking or failure)
+47
View File
@@ -0,0 +1,47 @@
name: Auto-fix lint and format
on:
pull_request:
types: [opened, synchronize, reopened]
branches: ["*"]
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
autofix:
name: Auto-fix
if: github.event.pull_request.head.repo.id == github.repository_id
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Setup
uses: ./tooling/github/setup
- name: Get changed files
id: changed
env:
BASE_REF: ${{ github.base_ref }}
run: |
git diff --name-only --diff-filter=ACMR "origin/${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@v7
with:
commit_message: "style: auto-fix lint and format"
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
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
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
+347
View File
@@ -0,0 +1,347 @@
name: CI
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: ["*"]
pull_request_target:
types: [closed]
branches: ["*"]
push:
branches: [main, dev, beta, next]
merge_group:
workflow_dispatch:
inputs:
operation:
description: Operation to run
required: true
default: validate
type: choice
options:
- validate
- publish
- cleanup
pr_number:
description: PR number for publish or cleanup
required: false
type: number
ref:
description: Git ref to validate or publish
required: false
type: string
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
FORCE_COLOR: 3
SKIP_ENV_VALIDATION: true
TURBO_TELEMETRY_DISABLED: 1
REGISTRY: ghcr.io
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
jobs:
fast-gate:
name: Fast gate
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
runs-on: ubuntu-latest
timeout-minutes: 15
env:
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || 'HEAD~1' }}
TURBO_SCM_HEAD: ${{ github.sha }}
TURBO_PLATFORM: linux/amd64
steps:
- uses: actions/checkout@v7
if: '!env.ACT'
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Validate manual inputs
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'publish'
env:
PR_NUMBER: ${{ inputs.pr_number }}
BUILD_REF: ${{ inputs.ref }}
run: |
test -n "$PR_NUMBER" || { echo "pr_number is required for publish"; exit 1; }
test -n "$BUILD_REF" || { echo "ref is required for publish"; exit 1; }
- name: Setup
uses: ./tooling/github/setup
- name: Copy environment defaults
run: cp .env.example .env
- name: Check formatting in affected workspaces
run: pnpm turbo run format --affected --continue -- --check
- name: Lint, typecheck, and build affected workspaces
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful
- name: Validate workspace configuration
run: pnpm lint:ws
- name: Run unit tests
run: pnpm test
- name: Report coverage
if: always() && !env.ACT
uses: davelosert/vitest-coverage-report-action@v2
container:
name: Container, E2E, and memory
needs: fast-gate
if: |
github.event_name == 'merge_group' ||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
if: '!env.ACT'
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Setup
uses: ./tooling/github/setup
- name: Set up Docker Buildx
if: '!env.ACT'
uses: docker/setup-buildx-action@v4
- name: Build container once
if: '!env.ACT'
uses: docker/build-push-action@v7
with:
context: .
network: host
platforms: linux/amd64
load: true
push: false
tags: |
homarr-ci
homarr-e2e
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.ref.name=pr-${{ github.event.pull_request.number || inputs.pr_number || 'none' }}
cache-from: type=gha,scope=homarr-linux-amd64
cache-to: type=gha,scope=homarr-linux-amd64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Build container once with Act
if: env.ACT
env:
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
run: |
secret_args=()
for secret in TURBO_API TURBO_TEAM TURBO_TOKEN TURBO_REMOTE_CACHE_SIGNATURE_KEY; do
if [ -n "${!secret:-}" ]; then
secret_args+=(--secret "id=${secret},env=${secret}")
fi
done
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
- name: Cache Playwright browsers
if: '!env.ACT'
id: playwright-cache
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload E2E artifacts
if: failure() && !env.ACT
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: e2e/test-results/
retention-days: 7
- name: Run memory regression test
env:
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
SECRET_ENCRYPTION_KEY: 0000000000000000000000000000000000000000000000000000000000000000
run: |
docker run -d --name "$CONTAINER_NAME" -p 127.0.0.1::7575 \
-e NO_EXTERNAL_CONNECTION=true \
-e SECRET_ENCRYPTION_KEY="$SECRET_ENCRYPTION_KEY" \
homarr-ci
PORT=$(docker port "$CONTAINER_NAME" 7575/tcp | sed 's/.*://')
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "ERROR: invalid port mapping: '$PORT'"; exit 1; }
URL="http://127.0.0.1:${PORT}"
healthy=false
for i in $(seq 1 30); do
if curl -sf "$URL/api/health/ready" >/dev/null 2>&1; then
echo "Container healthy after $((i * 2))s"
healthy=true
break
fi
sleep 2
done
if [ "$healthy" != "true" ]; then
echo "ERROR: Container never became healthy"
docker logs "$CONTAINER_NAME" --tail 100
exit 1
fi
warmup_ok=0
for _ in $(seq 1 5); do
curl -sf "$URL/" >/dev/null 2>&1 && warmup_ok=$((warmup_ok + 1))
sleep 2
done
if [ "$warmup_ok" -eq 0 ]; then
echo "ERROR: All warmup requests failed"
docker logs "$CONTAINER_NAME" --tail 50
exit 1
fi
docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" | grep -qx true || {
echo "ERROR: Container died before memory measurement"
docker logs "$CONTAINER_NAME" --tail 100
exit 1
}
RAW_MEMORY=$(docker stats --no-stream --format '{{.MemUsage}}' "$CONTAINER_NAME" | sed 's| /.*||')
MEMORY_MB=$(echo "$RAW_MEMORY" | awk '
/GiB/ { gsub(/GiB/, ""); print $1 * 1024; next }
/MiB/ { gsub(/MiB/, ""); print $1; next }
/KiB/ { gsub(/KiB/, ""); print $1 / 1024; next }
{ print $1 / 1048576 }
')
echo "Memory usage: ${MEMORY_MB} MB (${RAW_MEMORY})"
[[ "$MEMORY_MB" =~ ^[0-9]+(\.[0-9]+)?$ ]] || {
echo "ERROR: non-numeric memory value: '$MEMORY_MB' (raw: '$RAW_MEMORY')"
docker logs "$CONTAINER_NAME" --tail 100
exit 1
}
if awk "BEGIN { exit !($MEMORY_MB < 50) }"; then
echo "ERROR: Memory suspiciously low (${MEMORY_MB} MB) — container may have crashed"
docker logs "$CONTAINER_NAME" --tail 100
exit 1
elif awk "BEGIN { exit !($MEMORY_MB > 700) }"; then
echo "ERROR: Memory usage ${MEMORY_MB} MB exceeds 700 MB"
docker logs "$CONTAINER_NAME" --tail 100
exit 1
fi
- name: Clean up memory test container
if: always()
env:
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
run: docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
- name: Determine preview tag
id: preview
if: |
!env.ACT &&
(
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
env:
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
run: echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
if: steps.preview.outputs.tag != ''
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push preview image
if: steps.preview.outputs.tag != ''
env:
PREVIEW_TAG: ${{ steps.preview.outputs.tag }}
run: |
docker tag homarr-ci "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
docker push "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
- name: Show Act publication result
if: env.ACT
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
cleanup:
name: Clean up preview image
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
packages: write
env:
IMAGE_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
steps:
- name: Validate cleanup input
if: github.event_name == 'workflow_dispatch'
env:
PR_NUMBER: ${{ inputs.pr_number }}
run: test -n "$PR_NUMBER" || { echo "pr_number is required for cleanup"; exit 1; }
- name: Delete GHCR package version
if: '!env.ACT'
uses: actions/github-script@v8
with:
script: |
const packageName = "homarr-test";
const org = context.repo.owner;
const tag = process.env.IMAGE_TAG;
const versions = await github.paginate(
github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg,
{ package_type: "container", package_name: packageName, org, per_page: 100 },
);
const target = versions.find((version) =>
version.metadata?.container?.tags?.includes(tag),
);
if (!target) {
core.info(`No package version found with tag "${tag}"`);
return;
}
await github.rest.packages.deletePackageVersionForOrg({
package_type: "container",
package_name: packageName,
org,
package_version_id: target.id,
});
core.info(`Deleted package version ${target.id} (${tag})`);
- name: Show Act cleanup result
if: env.ACT
run: echo "Act validated cleanup for ${IMAGE_TAG}; external GHCR writes are disabled locally."
-165
View File
@@ -1,165 +0,0 @@
name: "[Quality] Code Analysis"
permissions:
contents: read
on:
pull_request:
branches: ["*"]
push:
branches: ["main"]
merge_group:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
FORCE_COLOR: 3
SKIP_ENV_VALIDATION: true
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
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@v7
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
- name: Copy env
shell: bash
run: cp .env.example .env
- name: Lint
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 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: pnpm turbo typecheck --affected
test:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Setup
uses: ./tooling/github/setup
- name: Test
run: pnpm test
- name: "Report Coverage"
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
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@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
run: pnpm exec playwright install chromium
- name: Run E2E Tests
shell: bash
run: pnpm test:e2e
build:
runs-on: ubuntu-latest
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 turbo build --affected
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Upload Crowdin translations
uses: crowdin/github-action@v2
@@ -26,7 +26,7 @@ jobs:
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
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get Next Version
+14 -4
View File
@@ -57,7 +57,7 @@ jobs:
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
- uses: actions/checkout@v7
if: env.SKIP_RELEASE == 'false'
with:
persist-credentials: false
@@ -66,7 +66,7 @@ jobs:
- uses: actions/setup-node@v6
if: env.SKIP_RELEASE == 'false'
with:
node-version: 24.16.0
node-version: 24.18.0
package-manager-cache: false
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
@@ -119,7 +119,7 @@ jobs:
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.git_ref }}
@@ -148,6 +148,11 @@ jobs:
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
env:
SKIP_ENV_VALIDATION: true
build-arm64:
@@ -158,7 +163,7 @@ jobs:
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.git_ref }}
@@ -187,6 +192,11 @@ jobs:
platforms: linux/arm64
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
env:
SKIP_ENV_VALIDATION: true
extract-asset-amd64:
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Setup
uses: ./tooling/github/setup
- name: Build docs
@@ -3,6 +3,7 @@ name: "[Deployment] Automatic Weekly Release"
on:
schedule:
- cron: "0 19 * * 5" # https://crontab.guru/#0_19_*_*_5
timezone: Europe/Zurich
workflow_dispatch:
inputs:
send-notifications:
@@ -28,7 +29,7 @@ jobs:
with:
args: "Automatic 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
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get Next Version
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Setup
uses: ./tooling/github/setup
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: dev
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: dev
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Validate prebuilt dependencies for amd64
id: validate-amd64
uses: ./.github/actions/prebuilt-debian
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Validate prebuilt dependencies for arm64
id: validate-arm64
uses: ./.github/actions/prebuilt-debian
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- run: |
npx --yes --package renovate -- \
renovate-config-validator --strict .github/renovate.json5
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
token: ${{ steps.obtainToken.outputs.token }}
ref: dev
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
@@ -28,7 +28,7 @@ jobs:
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- name: Setup
+4
View File
@@ -2,6 +2,7 @@
# dependencies
node_modules
.pnpm-store/
.pnp
.pnp.js
.idea/
@@ -70,7 +71,10 @@ apps/nextjs/public/images/background.png
# personal agent config
.opencode
.agents/
skills-lock.json
# docusaurus
apps/docs/.docusaurus
apps/docs/build
.pnpm-store
+1 -1
View File
@@ -1,2 +1,2 @@
[tools]
node = "24.16.0"
node = "24.18.0"
+1 -1
View File
@@ -1 +1 @@
24.16.0
24.18.0
+1 -1
View File
@@ -1,7 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"oxc.oxc-vscode",
"yoavbls.pretty-ts-errors",
"million.million-lint",
"lokalise.i18n-ally"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib",
"js/ts.tsdk.path": "node_modules\\typescript\\lib",
"js/ts.implicitProjectConfig.experimentalDecorators": true,
"cSpell.words": [
"ajnart",
+535
View File
@@ -0,0 +1,535 @@
# Custom Widgets Architecture Audit and North Star
> Status: implemented in the current working tree; baseline retained below for traceability
> Evidence snapshot: `c5703e7e5` on PR #6304
> Scope: built-in custom-widget displays, Custom JSX, network execution, authoring, preview, import/export, AI prompts, and MCP tools
## Implementation outcome
The migration described by this audit is implemented. `@homarr/custom-widgets` now owns the domain through explicit `core`, `jsx`, `runtime`, `server`, and `workbench` exports. API, Widgets, and Next.js retain only authorization, persistence, transport, board registration, navigation, and localized adapter concerns.
Key outcomes:
- One typed configuration and display-descriptor model drives schemas, defaults, form hydration, serialization, import/export, preview, examples, diagnostics, generated references, and AI prompts.
- Display-data extraction moved out of API and Widgets into the domain package; the former API-to-Widgets re-export chain no longer exists.
- The interpreter, shared security policy, registry, wrappers, named-request runtime, hardened executor, preview sessions, rate limits, and workbench UI have focused package-owned modules and tests.
- Runtime and workbench network behavior is injected through typed ports. The domain package has no dependency on API, Widgets, Next.js routes, notification infrastructure, or modal infrastructure.
- Public tRPC and MCP procedures are characterized, v2 and v3 import envelopes remain accepted, display-only v1 remains covered, and MCP offers iterative schema, validation, template read/write/patch, create/import/update, preview query, simulation, and journal tools.
- The AI prompt uses a multiline two-block authoring bundle, documents the `SubFetch` render-child contract, and is generated from the canonical registry and tested examples.
- Architecture enforcement currently scans 110 production modules for dependency direction, cycles, file budgets, dynamic loading, and untranslated JSX literals. Generated-reference drift and every Mantine export classification are test-enforced.
- The latest production build measured the largest Custom JSX route payload at 99,852 gzip bytes across two chunks, below the 768 KiB budget.
Focused verification is green: 278 package tests, 35 API/MCP tests, 52 Widgets renderer tests, 7 E2E tests, affected typecheck, affected lint, docs build, production Next.js build, architecture checks, generated drift, and the bundle budget. The root `pnpm test` command is presently blocked on this workstation by unrelated Docker-backed integration suites timing out while the shared Docker daemon is under external load; no Custom Widget test failed in those runs.
## Executive summary
The feature has a strong security direction and a capable authoring experience, but its implementation has outgrown its current ownership model. PR #6304 changes 56 files with 13,206 additions. The primary feature directories contain 10,437 lines, spread across `@homarr/widgets`, `@homarr/api`, `@homarr/definitions`, `@homarr/validation`, and the Next.js management route.
The main problem is not the absolute line count. It is that domain policy, transport, persistence, rendering, form state, compatibility, and documentation generation are interleaved across package boundaries. A change to one display type can require synchronized edits in schemas, form defaults, import conversion, preview extraction, runtime rendering, documentation, and AI instructions. Several of those paths use untyped records, duplicated conversion logic, or direct API dependencies.
The north star is a single `@homarr/custom-widgets` domain package with explicit `core`, `jsx`, `runtime`, `server`, and `workbench` entry points. Existing applications and packages become thin adapters. The extraction must preserve public APIs, import formats, permission behavior, and the current UI while improving internal cohesion.
This audit does not authorize a big-bang rewrite. The migration should be a sequence of behavior-preserving PRs, beginning with characterization tests and ending with enforced dependency and complexity budgets.
## Measured baseline
### Change footprint
| Measure | Current value |
| ------------------------------- | ------------: |
| PR files changed | 56 |
| PR additions | 13,206 |
| PR deletions | 1,385 |
| Primary feature-directory lines | 10,437 |
Generated component-reference JSON accounts for 2,491 added lines and is not treated as source complexity.
### Largest production files
| File | Lines | Responsibilities currently combined |
| ------------------------------------------------------------------------------- | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx` | 1,911 | Schema refinement, defaults, server-error mapping, form conversion, submission, preview state, focus management, display picker, connection fields, and every display-specific editor |
| `packages/api/src/router/custom-widget/custom-widget-router.ts` | 978 | CRUD, import/export, template patching, MCP descriptions, preview sessions, preview execution, journals, and action simulation/execution |
| `packages/widgets/src/custom-api/safe-jsx-interpreter.tsx` | 944 | AST model, parsing, environments, budgets, sanitization, expression evaluation, safe methods, callbacks, and React rendering |
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview.tsx` | 834 | Preview orchestration, error boundary, tabs, response tree, diagnostics, request journal, sample data, and renderer selection |
| `packages/widgets/src/custom-api/jsx-whitelist.ts` | 775 | Mantine imports, wrappers, icon mapping, runtime component map, registry filtering, data sanitization, and safe bindings |
| `packages/widgets/src/custom-api/jsx-sub-fetch.tsx` | 599 | Query rendering, render props, path display, action execution, buttons, toggles, refresh, notifications, and confirmations |
These files exceed the proposed 300-line production-module target. The interpreter is a reasonable candidate for a documented 400-line exception after its independent policies, evaluators, and renderer are extracted.
### Current ownership
```mermaid
flowchart LR
D["@homarr/definitions<br/>registry and examples"]
V["@homarr/validation<br/>schemas and AST diagnostics"]
W["@homarr/widgets<br/>renderers and interpreter"]
A["@homarr/api<br/>CRUD, permissions, execution and MCP"]
N["Next.js route<br/>workbench, preview and prompt"]
X["Docs<br/>generated reference and guides"]
N --> A
N --> V
N --> D
N --> W
W --> A
W --> D
A --> V
X --> D
```
The `@homarr/widgets → @homarr/api` edge is particularly costly. Runtime request components call the generated tRPC client directly, so moving the domain into a package that is also consumed by the API would create a dependency cycle unless request execution is inverted behind a typed port.
## What is working well
- Custom JSX v2 replaces raw runtime URLs and methods with named, typed requests.
- Board permissions, network scope, DNS validation, redirect policy, response limits, caching, concurrency, and action rules are server-enforced.
- The safe interpreter no longer treats an upstream JSX parser as a sandbox.
- Component metadata drives runtime classification, editor completion, docs generation, and part of the AI prompt.
- The workbench supports multiline JSX, diagnostics, real preview sizes, response inspection, request journals, import review, undo/redo, and paste-to-import.
- MCP exposes schema, validation, preview, journal, template read/write/patch, import, and update workflows without exposing stored secrets.
- Shared examples cover simple metrics, nested queries, charts, actions, and richer interactive layouts.
The migration must preserve these capabilities. Package extraction is not a reason to weaken the security boundary or remove authoring features.
## Findings
### P1 — Domain ownership is fragmented
Custom-widget behavior is implemented in five packages/applications. None of them is the clear source of truth for the complete lifecycle. `@homarr/definitions` owns component metadata and examples, `@homarr/validation` owns schemas and template diagnostics, `@homarr/widgets` owns runtime evaluation, `@homarr/api` owns network and persistence flows, and Next.js owns authoring transformations.
Impact:
- Maintainers must search unrelated packages to understand one behavior.
- Changes have a large synchronization surface and can drift silently.
- The domain cannot be tested or reused as a coherent unit.
- Package names describe infrastructure location rather than feature ownership.
### P1 — Giant modules mix unrelated reasons to change
The largest files are not merely long; they combine policy, orchestration, state, and presentation. The form contains all display editors. The router contains every management and preview procedure. The interpreter contains parsing through React emission. The request component file contains both query and mutation interaction models.
Impact:
- Reviews are broad and regression-prone.
- Unit-test seams are difficult to identify.
- Merge conflicts concentrate in a small number of files.
- Small feature changes require understanding thousands of lines of adjacent behavior.
### P1 — Runtime code depends directly on the API client
`packages/widgets/src/custom-api/component.tsx` and `jsx-sub-fetch.tsx` import `@homarr/api/client`. They also directly invoke notifications and confirmation modals.
Impact:
- The renderer cannot be used or tested without application infrastructure.
- A future `@homarr/custom-widgets` package would cycle with `@homarr/api` if both retained these imports.
- Preview and board runtime cannot share the same renderer through different request implementations without branching inside the components.
Required direction: a runtime provider must receive typed query, action, confirmation, notification, and invalidation ports. The board and preview layers supply adapters.
### P1 — Configuration transformations are duplicated
Create defaults and builders live in the form, edit hydration lives in the edit page, and import hydration lives in `_custom-widget-import-utils.ts`. These paths independently translate between stored display configs and form values. They rely on display-type switches, record casts, and JSON text fields such as the request manifest.
Impact:
- Create, edit, paste replacement, import, export, and MCP can disagree.
- Adding a field requires updating multiple transforms.
- Round trips are difficult to prove lossless.
Required direction: each display type supplies one typed descriptor containing schema, defaults, `toForm`, `fromForm`, preview projection, and migration behavior.
### P1 — Type safety degrades at domain boundaries
`Record<string, unknown>`, `as never`, and `as unknown as` are frequent in form conversion, display extraction, preview state, imports, registry wrappers, and API parsing. Some casts are necessary at the interpreter/React boundary, but most domain-level casts indicate missing discriminated unions or validated adapters.
Impact:
- Invalid combinations survive until runtime.
- Refactors do not receive complete compiler guidance.
- Server and client transformations can diverge while continuing to compile.
Required direction: parse unknown values once at ingress and use typed domain objects afterward. Unsafe casts must be isolated to named boundary modules and justified by tests.
### P1 — Security policy is represented more than once
The template validator and interpreter both parse JSX and maintain concepts such as blocked properties, blocked props, AST depth, and operation limits. Runtime component filtering adds another policy layer.
The analyzer and interpreter have different jobs, so they should not be collapsed into one evaluator. They must, however, consume one immutable policy definition and shared AST normalization utilities.
Impact:
- A property can be rejected by one layer and missed by another.
- Security changes require parallel edits and duplicate tests.
- Diagnostics can describe behavior that differs from runtime enforcement.
### P1 — The API router combines transport and application behavior
The router owns tRPC metadata, Zod inputs, database writes, encryption, import/export, template revisions, preview execution, rate-limit acquisition, journal recording, and MCP documentation.
Impact:
- Domain logic is only testable through tRPC context setup.
- MCP and browser endpoints can accidentally implement different workflows.
- Framework-specific `TRPCError` values leak into lower-level executor and manifest code.
Required direction: tRPC procedures authorize, load persistence models, call a domain service, and map domain errors. They do not implement request rendering, template patching, preview execution, or import conversion inline.
### P2 — Metadata generation is only partially centralized
The component registry and examples are shared, but runtime maps, wrappers, safe bindings, display-type metadata, AI prompt prose, form choices, and some docs remain separately assembled. The AI prompt is built inside a client button component.
Impact:
- Agent instructions can drift from validation and runtime behavior.
- Prompt generation requires UI and network context.
- Display types lack the same single-registry guarantee as JSX components.
Required direction: prompt generation is a pure function in `core`, using the schema, display descriptors, component registry, and tested fixtures. The UI only fetches context and copies the result.
### P2 — Compatibility behavior is mixed into primary flows
V1 detection, migration warnings, legacy network diagnostics, v2 imports, v3 exports, and fallback display parsing are embedded beside current authoring/runtime logic.
Impact:
- Happy paths are harder to read.
- Legacy behavior can accidentally influence v2 code.
- Compatibility retirement has no obvious deletion boundary.
Required direction: version detection and migrations live under `core/migrations`. Runtime and workbench receive a normalized current model plus explicit review state.
### P2 — Test topology does not mirror the architecture
Interpreter coverage is substantial, and request-executor/router tests exist, but tests are colocated under their current infrastructure owners instead of a feature package. A focused renderer run passed 52 tests during review, while the selected API suites failed before collecting tests because the root Vitest client environment rejected server-only environment access.
Impact:
- The complete feature cannot be verified with one package command.
- Server test failures can be configuration failures rather than behavioral failures.
- Form conversion and workbench orchestration have limited direct characterization coverage relative to their size.
Required direction: the package owns focused test projects or environment-specific configs for core, DOM runtime/workbench, and Node server tests. API adapter tests remain in `@homarr/api`.
### P2 — A small amount of user-facing copy bypasses translation
Examples found during the snapshot include the raw-response tooltip and a default empty template message. Dynamic translation keys also require repeated `as never` casts, reducing translation-key safety.
Impact:
- Locale completeness gates are unreliable.
- Refactoring copy is harder than changing typed translation keys.
Required direction: all package UI receives a typed message catalog or uses typed scoped translations. No visible fallback literals are allowed in production components.
## North-star package
```text
packages/custom-widgets/
├── src/core/
│ ├── config/ # discriminated display configurations and form models
│ ├── displays/ # typed display descriptors and built-in display metadata
│ ├── import/ # v2/v3 parsing, export, review, and authoring bundles
│ ├── migrations/ # v1 detection, normalization, and review requirements
│ ├── prompts/ # pure AI prompt generation
│ └── examples/ # typed fixtures shared by tests, docs, editor, and prompts
├── src/jsx/
│ ├── ast/ # parser adapter, AST types, traversal, and normalization
│ ├── policy/ # blocked properties, props, URLs, and budgets
│ ├── analyzer/ # authoring diagnostics
│ ├── interpreter/ # expression and collection evaluation
│ ├── registry/ # component descriptors, wrappers, and export classification
│ └── bindings/ # branded safe helpers and sanitized data
├── src/runtime/
│ ├── displays/ # built-in renderers
│ ├── jsx/ # Custom JSX renderer and error containment
│ ├── requests/ # SubFetch, actions, toggles, refresh, and state handling
│ └── provider/ # injected runtime capabilities
├── src/server/
│ ├── network/ # hardened executor and destination policy
│ ├── requests/ # manifest resolution and parameter substitution
│ ├── limits/ # rate and concurrency limits
│ └── preview/ # sessions, simulation, and journals
└── src/workbench/
├── form/ # shared state and connection/display sections
├── editor/ # CodeMirror and registry-driven diagnostics
├── preview/ # widget, response, journal, and diagnostic views
└── import/ # clipboard parsing and review UI
```
Public exports are explicit:
```json
{
"exports": {
"./core": "./src/core/index.ts",
"./jsx": "./src/jsx/index.ts",
"./runtime": "./src/runtime/index.ts",
"./server": "./src/server/index.ts",
"./workbench": "./src/workbench/index.ts"
}
}
```
There is no broad root barrel. Internal folders cannot be imported by consumers.
### Dependency rules
```mermaid
flowchart LR
C["custom-widgets/core"]
J["custom-widgets/jsx"]
R["custom-widgets/runtime"]
S["custom-widgets/server"]
B["custom-widgets/workbench"]
API["@homarr/api adapter"]
W["@homarr/widgets adapter"]
N["Next.js route adapter"]
DOC["Docs generator"]
J --> C
R --> C
R --> J
S --> C
B --> C
B --> R
API --> C
API --> S
W --> C
W --> R
N --> C
N --> B
DOC --> C
DOC --> J
```
Hard rules:
1. `@homarr/custom-widgets` never imports `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
2. `core` has no React, Mantine, tRPC, database, Redis, or browser dependency.
3. `jsx` has no API, persistence, Redis, notification, or modal dependency.
4. `runtime` performs no direct tRPC calls. It consumes `CustomWidgetRuntimePort`.
5. `workbench` performs no direct tRPC calls. It consumes `CustomWidgetWorkbenchPort` callbacks.
6. `server` has no tRPC or database-query dependency. API adapters load authorized records and map errors.
7. Cross-entry imports follow the arrows above; reverse imports fail CI.
### Runtime ports
The exact transport remains owned by adapters. The domain contract should cover behavior rather than tRPC details:
```ts
interface CustomWidgetRuntimePort {
getData(input: { itemId: string; signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
query(input: NamedRequestInvocation & { signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
action(input: NamedRequestInvocation & { confirmed?: boolean }): Promise<CustomWidgetRequestResult>;
confirm(input: ActionConfirmation): Promise<boolean>;
notify(input: RuntimeNotification): void;
invalidate(input: RuntimeInvalidation): Promise<void>;
}
```
Board runtime and preview runtime implement the same port. Preview simulation is an adapter capability, not a conditional tRPC import inside `ActionButton`.
### Display descriptors
Every display type is represented once:
```ts
interface CustomWidgetDisplayDescriptor<TConfig, TForm> {
type: CustomWidgetDisplayType;
configSchema: ZodType<TConfig>;
formSchema: ZodType<TForm>;
createDefault(): TConfig;
toForm(config: TConfig): TForm;
fromForm(form: TForm): TConfig;
migrate(input: unknown): DisplayMigrationResult<TConfig>;
extract(response: unknown, config: TConfig): DisplayModel;
capabilities(config: TConfig): CustomWidgetCapability[];
}
```
UI fields and renderer components may be registered in runtime/workbench companion maps keyed by the same typed display identifier. Domain descriptors remain serializable and framework-independent.
## Developer-experience north star
### Maintainers
- Start at `packages/custom-widgets` for every feature behavior.
- Find one module per concept and one descriptor per display type.
- Add a display type without editing central switches in multiple packages.
- Run one package command for core, DOM, and server tests.
- Receive compile-time failures when a configuration field is not handled by forms, preview, migration, or export.
- Review PRs that move one domain boundary at a time.
### Administrators and template authors
- Preserve the current continuous form and live workbench.
- Keep preview usable before a live response exists through schema-valid defaults and editable sample data.
- Keep diagnostics actionable, localized, clickable, and consistent with runtime policy.
- Keep imports reviewable and secrets excluded.
- Keep query and action capabilities visible without noisy permanent warnings.
### AI agents
- Obtain schema, registry, limits, examples, and prompt rules from the same typed sources.
- Use readable multiline templates through authoring bundles and line-based template tools.
- Validate, preview, inspect journals, patch, and revalidate without rewriting unrelated configuration.
- Receive stable machine-actionable issue codes in addition to localized or human-readable messages.
- Never need to infer runtime behavior from UI-only prompt strings.
## Fragmentation rules
- Production modules target at most 300 lines, excluding generated files, fixtures, and type-only declarations.
- Algorithmic modules can reach 400 lines only with a file-level rationale and focused tests.
- A module has one primary reason to change; moving lines without separating policy from orchestration does not satisfy the target.
- React components do not contain schema definitions or persistence transforms.
- Routers do not contain reusable domain algorithms.
- Domain functions do not throw framework-specific errors.
- Unknown input is validated once. Parsed values are not carried as `Record<string, unknown>` through internal layers.
- Compatibility code lives in versioned migration modules and has explicit removal criteria.
- Generated artifacts are reproducible and checked for drift, but excluded from line budgets.
## Migration roadmap
### PR 1 — Characterize contracts
- Add round-trip tests for every display descriptor: default → form → config, stored config → form → config, and import → export.
- Add fixtures for v1 display-only, v1 network-review-required, v2 import, and v3 export.
- Add prompt snapshots sourced from the component registry and examples.
- Split Node and DOM Vitest projects so server suites collect and run reliably.
- Record bundle and render baselines before moving modules.
Gate: all existing behavior is represented by tests, with no production moves yet.
### PR 2 — Establish `core`
- Create `@homarr/custom-widgets` and explicit exports.
- Move custom-widget schemas, typed configs, display descriptors, examples, import/export, migration, capability analysis, and prompt generation into `core`.
- Replace form/edit/import conversion duplication with descriptor calls.
- Temporarily re-export legacy imports from definitions and validation to keep consumers stable.
Gate: create, edit, import, export, MCP validation, and AI prompt generation use the same domain contracts.
### PR 3 — Extract JSX policy and interpreter
- Move AST types, parser adapter, shared security policy, analyzer, evaluator, budgets, bindings, and registry into `jsx`.
- Separate evaluator modules by expression family and React emission.
- Generate diagnostics and runtime enforcement from the same policy constants.
- Keep the Mantine export-classification CI check.
Gate: the security suite passes unchanged, analyzer/runtime policy drift tests pass, and no consumer imports JSX internals.
### PR 4 — Extract runtime
- Move built-in renderers, Custom JSX display, SubFetch, actions, toggles, refresh, and error containment into `runtime`.
- Introduce the runtime port and adapters for board and preview contexts.
- Remove direct imports of the API client, modals, and notifications from domain components.
- Split request components into query, action, toggle, refresh, feedback, and shared-state modules.
Gate: board rendering and workbench preview use the same runtime components through different adapters; actions retain confirmation, rollback, invalidation, and permission behavior.
### PR 5 — Extract server services
- Move executor, manifests, network policy, limits, caching/single-flight, and preview-session logic into `server`.
- Replace `TRPCError` below the adapter with typed domain errors.
- Split API procedures into management, templates, preview, and runtime routers that delegate to package services.
- Keep authorization, database lookup, encryption, and tRPC/MCP metadata in `@homarr/api`.
Gate: browser and MCP flows call the same services; request security tests run as Node package tests; public procedure names and inputs remain compatible.
### PR 6 — Extract and fragment workbench
- Move reusable editor, preview, response tree, import review, connection section, display picker, and display-specific fields into `workbench`.
- Introduce a workbench port for validate, preview, query, simulate action, journal, and save callbacks.
- Keep Next.js pages responsible only for routing, loading initial data, and constructing adapters.
- Replace the monolithic form with small sections and descriptor-driven display fields while preserving the continuous form, two-pane scrolling, autofocus, and mobile modes.
Gate: no workbench source file exceeds the agreed budget, and create/edit/paste replacement share one state model.
### PR 7 — Remove scaffolding and enforce boundaries
- Remove compatibility re-exports and old implementations after all consumers move.
- Add CI checks for forbidden imports, dependency cycles, source-size budgets, untranslated literals, generated-artifact drift, component classification, and custom-JSX chunk size.
- Update contributor and architecture documentation with package entry points and extension recipes.
Gate: old ownership paths contain only thin adapters or are deleted.
## Verification strategy
### Core
- Every display descriptor accepts valid data and rejects mismatched configuration.
- All create/edit/import/export transformations are lossless where expected.
- V1/v2/v3 compatibility and review states are deterministic.
- Prompt output, authoring bundles, fixtures, docs data, and completion data are generated from shared sources.
### JSX
- Computed and Unicode-obfuscated reflective access remains blocked.
- Analyzer and interpreter share property, prop, URL, and budget policy.
- Own-property resolution, branded calls, callback accounting, and node/operation limits remain enforced.
- Every upstream Mantine export is classified and every enabled runtime component resolves.
### Runtime and workbench
- Query cancellation, stale results, retries, render children, action locks, DELETE confirmation, toggle rollback, and invalidation behave identically in preview and boards.
- Error boundaries isolate template and display failures.
- Empty, sample, stale, loading, failure, and success states are localized and accessible.
- Keyboard, focus, responsive, reduced-motion, and narrow-widget behavior remain covered.
### Server and adapters
- Board IDOR, permission levels, anonymous actions, named-request kinds, substitutions, DNS classes, redirects, headers, response limits, caching, and rate limits remain covered.
- Server tests run under an explicit Node environment rather than failing during client-environment initialization.
- MCP schemas and descriptions remain discoverable, stable, and absent from unsafe runtime procedures.
### Required commands
At the end of the migration, the package and repository must pass:
```sh
pnpm --filter @homarr/custom-widgets test
pnpm test
pnpm test:e2e
pnpm turbo typecheck --affected
pnpm turbo lint --affected
pnpm turbo build --filter=@homarr/docs
pnpm check:custom-widget-bundle # after the Next.js production build
```
CI additionally checks dependency direction, cycles, file budgets, generated references, translations, and client bundle size.
## Completion criteria
- `@homarr/custom-widgets` owns every reusable custom-widget domain behavior.
- No circular dependencies exist.
- The package does not import `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
- Runtime and workbench networking uses injected typed ports.
- One typed descriptor is the source of truth for each display type.
- One shared JSX policy drives authoring diagnostics and runtime enforcement.
- Create, edit, import, export, preview, MCP, and board runtime use the same typed transformations.
- Public tRPC/MCP names, import formats, stored configuration, and permission behavior remain compatible.
- V1 display-only migration and network-review states remain tested.
- Production modules satisfy the 300/400-line budgets.
- No untranslated user-facing strings remain.
- Core, JSX, runtime, server, workbench, integration, E2E, accessibility, docs, and bundle checks pass.
- A maintainer can locate a custom-widget behavior from the package structure without searching unrelated packages.
## Explicit non-goals
- Replacing Mantine or Homarr's visual language.
- Allowing arbitrary cross-origin requests.
- Replacing named requests with general JavaScript execution.
- Changing stored secrets or exposing them through export or MCP.
- Redesigning the current management UX during the package extraction.
- Removing compatibility formats before migration and review flows are proven.
## Decision record
- The package owns all Custom Widget behavior, not only Custom JSX.
- Security and correctness are hard constraints.
- Maintainer simplicity is the primary tie-breaker after those constraints.
- Current user and agent workflows are preserved during extraction.
- Generated artifacts do not count toward source-size budgets.
- Migration occurs through reviewable, behavior-preserving PRs rather than a single rewrite.
+93
View File
@@ -1,3 +1,96 @@
## [1.70.0](https://github.com/homarr-labs/homarr/compare/v1.69.2...v1.70.0) (2026-07-10)
## [1.69.2](https://github.com/homarr-labs/homarr/compare/v1.69.1...v1.69.2) (2026-07-06)
### Bug Fixes
* **layout:** raise AppShellHeader z-index above Indicator dots ([687edb9](https://github.com/homarr-labs/homarr/commit/687edb9226307cb83986097abb24fe7f672e1778)), closes [#6194](https://github.com/homarr-labs/homarr/issues/6194)
## [1.69.1](https://github.com/homarr-labs/homarr/compare/v1.69.0...v1.69.1) (2026-07-04)
### Bug Fixes
* unblock query cache mutations in demo mode and add cache buster ([#6176](https://github.com/homarr-labs/homarr/issues/6176)) ([a382c75](https://github.com/homarr-labs/homarr/commit/a382c75968e73bafff55b5890b1e95f0779b50d3))
## [1.69.0](https://github.com/homarr-labs/homarr/compare/v1.68.1...v1.69.0) (2026-07-03)
### Features
* add per-widget secret storage for release provider authentication ([77884a3](https://github.com/homarr-labs/homarr/commit/77884a31794e12a6f7bc59d96c8f61aa429ca68d))
* **config:** enable pnpm global virtual store for worktree support ([#6098](https://github.com/homarr-labs/homarr/issues/6098)) ([3864727](https://github.com/homarr-labs/homarr/commit/3864727502a64052c4e3838468094b30bf04a133))
* **docker-widget:** add per-column visibility toggle ([#6093](https://github.com/homarr-labs/homarr/issues/6093)) ([0765262](https://github.com/homarr-labs/homarr/commit/076526293d13379004461633d7ba4c2e83bd5ffe))
* **docs:** addd in documentation for archiveteam warrior ([f66469d](https://github.com/homarr-labs/homarr/commit/f66469d79797a2da214588f4e8253b03633c59a5))
* simplify caching — TanStack Query as single source of truth ([#6075](https://github.com/homarr-labs/homarr/issues/6075)) ([f19033e](https://github.com/homarr-labs/homarr/commit/f19033e0d53d248aed678737f3d04d5d8c7815b5))
* **widgets:** right-click context menu with auto-generated toggle actions ([#6126](https://github.com/homarr-labs/homarr/issues/6126)) ([7e58858](https://github.com/homarr-labs/homarr/commit/7e58858353ae866efd657270d4bb16d2c7d3e551))
* **widgets:** show calendar events on hover with pin on click ([#2637](https://github.com/homarr-labs/homarr/issues/2637)) ([#6081](https://github.com/homarr-labs/homarr/issues/6081)) ([db3d588](https://github.com/homarr-labs/homarr/commit/db3d58821e4de8d46b110125d0fa36919765248f))
### Bug Fixes
* add id to repository schema and handle missing ids gracefully ([8747d5f](https://github.com/homarr-labs/homarr/commit/8747d5f93ee9a295aad3c7035e8c6c7080bf5ee6))
* address PR review - atomic upsert, MCP metadata, stale tokens, save UX ([757d598](https://github.com/homarr-labs/homarr/commit/757d59854e5ec82d91c053b8bc3d7eaf2e70efc4))
* address PR review findings for widget-only integration refactor ([c892731](https://github.com/homarr-labs/homarr/commit/c892731479b47fab6218bd1f52fa6eccbdaa371e))
* address review - paginate GH releases, GitLab tag_name, docs, test ([e687bc8](https://github.com/homarr-labs/homarr/commit/e687bc8c9277797acaa164a09c74b3808a2cb346))
* apply version filter to LinuxServer.io provider ([34d976e](https://github.com/homarr-labs/homarr/commit/34d976eeea09e396334ce8fce5c492c07e7dd32b))
* catch individual repo fetch failures to prevent batch crashes ([d893515](https://github.com/homarr-labs/homarr/commit/d893515b932608ea08c9c6e90b83f340e40f6f8e))
* compile versionRegex once and add fetch timeouts ([64c7512](https://github.com/homarr-labs/homarr/commit/64c75122833981a49e9c8bf1789d501faff6d62d))
* **deps:** update dependency postcss to ^8.5.16 ([#6169](https://github.com/homarr-labs/homarr/issues/6169)) ([9252fdc](https://github.com/homarr-labs/homarr/commit/9252fdc13971bd982224a31d7be577112f99ec45))
* dns-hole widgets showing stale disconnected state ([#6096](https://github.com/homarr-labs/homarr/issues/6096)) ([2443f1c](https://github.com/homarr-labs/homarr/commit/2443f1cd3ff6765a91783d60d42aa03d9ba63fcd))
* gracefully degrade when widget_secret table is missing or access denied ([30107e4](https://github.com/homarr-labs/homarr/commit/30107e4e76a454ff7402de9a8bc2e0dd6f422aa8))
* handle missing widgetSecrets table and add default releases repo ([af35a50](https://github.com/homarr-labs/homarr/commit/af35a50fa05fee2f0118e98727eb108b0200f762))
* **immich:** v3 API compatibility — use searchAssets for album assets, drop deviceAssetId ([#5977](https://github.com/homarr-labs/homarr/issues/5977)) ([a5ec880](https://github.com/homarr-labs/homarr/commit/a5ec880eec276e0814d2ab3ed7f9bc0b804d0465))
* quick wins — calendar, about, glances, admin UX, releases, iframe, notes ([#6116](https://github.com/homarr-labs/homarr/issues/6116)) ([f9da71a](https://github.com/homarr-labs/homarr/commit/f9da71a19bdfa589aafe26b164d0e0782208ac0a))
* restore ArchiveTeam Warrior as a proper integration ([8a5ad58](https://github.com/homarr-labs/homarr/commit/8a5ad5861ba4c2d0978f1fa3f6115a1c5974f8a2))
* **timetable:** restore station search by removing disabled query guard ([d670b69](https://github.com/homarr-labs/homarr/commit/d670b6939672932c1a3d17ab9913901491cb46e3))
* update CHANGES.md for ArchiveTeam Warrior restoration, remove typo translation key ([62baaf7](https://github.com/homarr-labs/homarr/commit/62baaf78130c4b74e899a948cee17097d8c02593))
* verbose error messages in all release providers ([2155f13](https://github.com/homarr-labs/homarr/commit/2155f13f245f601b99458e227b6e058fbfa44cd9))
* **widgets:** make downloads widget table background transparent ([#6025](https://github.com/homarr-labs/homarr/issues/6025)) ([73bed51](https://github.com/homarr-labs/homarr/commit/73bed51df895250f415efd7a87e721e403b3fd99))
## [1.68.1](https://github.com/homarr-labs/homarr/compare/v1.68.0...v1.68.1) (2026-06-30)
### Bug Fixes
* make source maps opt-in to restore v1.67.0 memory baseline ([57ef0af](https://github.com/homarr-labs/homarr/commit/57ef0af4a252ea39727caeba7e13587dabc6254e)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
* revert production source maps to restore v1.67.0 memory baseline ([60a69e3](https://github.com/homarr-labs/homarr/commit/60a69e31efc7168b8f4309daf75954d7730d883b)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
## [1.68.0](https://github.com/homarr-labs/homarr/compare/v1.67.0...v1.68.0) (2026-06-26)
### Features
* add VPN status widget ([#5812](https://github.com/homarr-labs/homarr/issues/5812)) ([a2bb53a](https://github.com/homarr-labs/homarr/commit/a2bb53a8b8fd17da3d2e23851b75d4e179f66225))
* re-add Docker quick-access button in user menu ([#6054](https://github.com/homarr-labs/homarr/issues/6054)) ([abd1635](https://github.com/homarr-labs/homarr/commit/abd16354bc9ef3dddbb497fd16b03476535046ac)), closes [#2165](https://github.com/homarr-labs/homarr/issues/2165)
* ship source maps for readable production stack traces ([#6031](https://github.com/homarr-labs/homarr/issues/6031)) ([89380f8](https://github.com/homarr-labs/homarr/commit/89380f81ddd132f5b1041a8f2c3b6c6b138923dd))
### Bug Fixes
* **beszel:** harden cache layer and prevent blank widgets ([#6052](https://github.com/homarr-labs/homarr/issues/6052)) ([49c2663](https://github.com/homarr-labs/homarr/commit/49c266374df12fea2404710232ced42e2de11dbf)), closes [#6030](https://github.com/homarr-labs/homarr/issues/6030)
* **beszel:** improve stats date display, add click-to-stats modal, fix grid ordering ([#6026](https://github.com/homarr-labs/homarr/issues/6026)) ([55fb227](https://github.com/homarr-labs/homarr/commit/55fb227305fe51963e486ffb25c4ce6811a2f0e4))
* **beszel:** leverage TanStack Query for stale-while-revalidate caching ([#6055](https://github.com/homarr-labs/homarr/issues/6055)) ([bbc4589](https://github.com/homarr-labs/homarr/commit/bbc4589c9e1608721fb58a6f9b03cca8d65c1c4a))
* **beszel:** use 480m interval for 1w/30d stats views ([4383da3](https://github.com/homarr-labs/homarr/commit/4383da38ef634327c288626c58480cc1e5a2f805))
* **beszel:** widget transparency, error handling, and cache invalidation ([#5993](https://github.com/homarr-labs/homarr/issues/5993)) ([39e4245](https://github.com/homarr-labs/homarr/commit/39e424563a3187dcf0dba5f654e2225be76aef51))
* **icon-picker:** keep URL visible in edit mode, add SVG-first ordering ([#6051](https://github.com/homarr-labs/homarr/issues/6051)) ([e01571b](https://github.com/homarr-labs/homarr/commit/e01571b221812cd19a6d67ebe3cf44432104ec27))
* **integrations:** prevent certificate trust button from submitting the form ([#6020](https://github.com/homarr-labs/homarr/issues/6020)) ([0f66bfd](https://github.com/homarr-labs/homarr/commit/0f66bfd6c128253ba644d80d7c908de5314cf997))
* **manage/about:** bypass self-fetch + add manage error boundary (LLM exploration) ([#6009](https://github.com/homarr-labs/homarr/issues/6009)) ([19f9769](https://github.com/homarr-labs/homarr/commit/19f9769d9e669d56d793e9f6a0d265316113d743)), closes [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008)
* **plex:** use series title for media releases instead of season label ([#6083](https://github.com/homarr-labs/homarr/issues/6083)) ([2443b89](https://github.com/homarr-labs/homarr/commit/2443b89b46f88fddbf41f0382a9f3bc5469c6fa8))
* show app first in add item modal ([#6087](https://github.com/homarr-labs/homarr/issues/6087)) ([915c8a3](https://github.com/homarr-labs/homarr/commit/915c8a3501551fdccf786d0c34dc01107d873ce0))
* **truenas:** report actual memory usage and pool free space ([#6017](https://github.com/homarr-labs/homarr/issues/6017)) ([f69e545](https://github.com/homarr-labs/homarr/commit/f69e545d6429aece0e55975c3b2426f20e91e545))
* **unraid:** report disk free space instead of total size ([#6018](https://github.com/homarr-labs/homarr/issues/6018)) ([1fb97ee](https://github.com/homarr-labs/homarr/commit/1fb97ee9c6fbe656a7dd3a9d1e436125982533f4))
* **widgets:** clean up health monitoring disk and size display ([#6019](https://github.com/homarr-labs/homarr/issues/6019)) ([e29c79a](https://github.com/homarr-labs/homarr/commit/e29c79a9028d9311dcb3d8323e0950a444589c7a))
* **widgets:** hide health monitoring default tab option for non-proxmox integrations ([#6021](https://github.com/homarr-labs/homarr/issues/6021)) ([52df56b](https://github.com/homarr-labs/homarr/commit/52df56b19da8e0bcb6c2e5df47b7936ec63f3149))
* **widgets:** hide system disk usage value when it overflows and show it in a tooltip ([#6022](https://github.com/homarr-labs/homarr/issues/6022)) ([a19c9d8](https://github.com/homarr-labs/homarr/commit/a19c9d88caacc86b42854c732818f0b5e5cc8dd5))
## [1.67.0](https://github.com/homarr-labs/homarr/compare/v1.66.1...v1.67.0) (2026-06-19)
### Features
* **analytics:** migrate from Umami to PostHog server-side analytics ([#5983](https://github.com/homarr-labs/homarr/issues/5983)) ([d059023](https://github.com/homarr-labs/homarr/commit/d059023b51a323df027c1cf033454440bd0d7e57))
* Calculate and display total CPU and memory usage ([#5911](https://github.com/homarr-labs/homarr/issues/5911)) ([d9cbd54](https://github.com/homarr-labs/homarr/commit/d9cbd5427d0e5be3e5dbecca8d6069dc8074d810))
### Bug Fixes
* **auth:** isolate session cookies from other Auth.js apps on same host ([#5996](https://github.com/homarr-labs/homarr/issues/5996)) ([e1a245f](https://github.com/homarr-labs/homarr/commit/e1a245ff2d8520d08957c4a7dbe01d039607a973))
* **deps:** update tailwindcss monorepo to ^4.3.1 ([#5988](https://github.com/homarr-labs/homarr/issues/5988)) ([78b6e52](https://github.com/homarr-labs/homarr/commit/78b6e520ac095129398d3da8f2441df408e93e9f))
* **downloads:** sort stalled torrents last when sorting by finish time ([#6011](https://github.com/homarr-labs/homarr/issues/6011)) ([c56d9d6](https://github.com/homarr-labs/homarr/commit/c56d9d6894c717b5c70ab75d6a100d4d1071c2d3)), closes [#5416](https://github.com/homarr-labs/homarr/issues/5416)
## [1.65.0](https://github.com/homarr-labs/homarr/compare/v1.64.0...v1.65.0) (2026-06-12)
### Features
+96
View File
@@ -0,0 +1,96 @@
# Changes
## Goal
Remove widget-only services from Homarr's global Integration system.
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
## Why
These services do not need shared integration state.
- Release providers are selected per Releases widget repository.
- Search.ch is only used by the Timetable widget.
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
## What Changed
### Release Providers
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
- Removed release providers from `IntegrationKind`.
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
- Updated the Releases widget to store provider configuration directly in widget options:
- `provider`
- `identifier`
- `versionFilter`
- `providerUrl`
- Updated the Releases widget editor to use a provider select instead of an integration select.
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
### Timetable / Search.ch
- Removed Search.ch as an integration.
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
- Updated the Timetable API to receive `baseUrl` directly from widget options.
### ArchiveTeam Warrior
- Kept as a proper integration (not removed).
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
### Integration Cleanup
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
- Removed unused dependencies from `packages/integrations/package.json`.
### Database Migration
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
### Docs
- Updated Releases widget docs to describe providers instead of integrations.
- Updated Timetable docs to remove Search.ch integration setup.
- Removed obsolete integration docs pages for the deleted integration kinds.
- Updated docs sitemap and integration docs mappings.
### Translations
- Added English labels for the new widget options:
- `widget.timetable.option.baseUrl.label`
- `widget.releases.option.repositories.providerUrl.label`
## How It Works Now
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
## Deliberate Non-Goals
- No provider secret/token support was added.
- No new secret storage model was introduced.
- No compatibility layer was kept in `packages/integrations`.
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
## Verification So Far
- Focused typechecks passed for affected packages.
- Full `pnpm turbo typecheck` passed.
- Full `pnpm lint` passed with existing warnings.
- Focused release provider normalization tests passed.
- Widget translation test passed.
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
+214 -1
View File
@@ -1,7 +1,220 @@
# ── CODEOWNERS ───────────────────────────────────────
* @homarr-labs/maintainers
# Exempt Renovate‑managed files (no owners)
# Specific features / integrations
# These owners come from the PR that introduced each integration implementation.
# Specific rules override the global fallback because CODEOWNERS uses the last
# matching pattern; do not include @homarr-labs/maintainers here unless they
# should also be requested for that integration.
# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy
apps/docs/docs/integrations/adguard-home @hillaliy
packages/integrations/src/adguard-home @hillaliy
# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi
apps/docs/docs/integrations/anchor @ohmzi
packages/integrations/src/anchor @ohmzi
# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack
apps/docs/docs/integrations/archive-team-warrior @darbyjack
packages/integrations/src/archive-team-warrior @darbyjack
# Bazarr - introduced in https://github.com/homarr-labs/homarr/pull/6156 by @Ayush7614
apps/docs/docs/integrations/bazarr @Ayush7614
apps/docs/docs/widgets/bazarr @Ayush7614
packages/api/src/router/widgets/bazarr.ts @Ayush7614
packages/integrations/src/bazarr @Ayush7614
packages/request-handler/src/bazarr.ts @Ayush7614
packages/widgets/src/bazarr @Ayush7614
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/audiobookshelf @ajnart
packages/integrations/src/audiobookshelf @ajnart
# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart
apps/docs/docs/integrations/beszel @ajnart
packages/integrations/src/beszel @ajnart
# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/codeberg @Aandree5
packages/integrations/src/codeberg @Aandree5
# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K
apps/docs/docs/integrations/coolify @Rhiz3K
packages/integrations/src/coolify @Rhiz3K
# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw
apps/docs/docs/integrations/dash-dot @manuel-rw
packages/integrations/src/dashdot @manuel-rw
# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/docker-hub @Aandree5
packages/integrations/src/docker-hub @Aandree5
# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf
apps/docs/docs/integrations/emby @Meierschlumpf
packages/integrations/src/emby @Meierschlumpf
# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/github @Aandree5
packages/integrations/src/github @Aandree5
# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5
apps/docs/docs/integrations/github-containerregistry @Aandree5
packages/integrations/src/github-container-registry @Aandree5
# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/gitlab @Aandree5
packages/integrations/src/gitlab @Aandree5
# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw
apps/docs/docs/integrations/glances @manuel-rw
packages/integrations/src/glances @manuel-rw
# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar
apps/docs/docs/integrations/gluetun @Lok3sh-kumar
packages/integrations/src/gluetun @Lok3sh-kumar
# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician
apps/docs/docs/integrations/gotify @Nerdy-Technician
packages/integrations/src/gotify @Nerdy-Technician
# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw
apps/docs/docs/integrations/home-assistant @manuel-rw
packages/integrations/src/homeassistant @manuel-rw
# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman
apps/docs/docs/integrations/ical @NicolasNewman
packages/integrations/src/ical @NicolasNewman
# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw
apps/docs/docs/integrations/immich @manuel-rw
packages/integrations/src/immich @manuel-rw
# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw
apps/docs/docs/integrations/jellyfin @manuel-rw
packages/integrations/src/jellyfin @manuel-rw
# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
apps/docs/docs/integrations/jellyseerr @manuel-rw
packages/integrations/src/jellyseerr @manuel-rw
# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
apps/docs/docs/integrations/linux-server-io @Aandree5
packages/integrations/src/linuxserverio @Aandree5
# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/navidrome @ajnart
packages/integrations/src/navidrome @ajnart
# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw
apps/docs/docs/integrations/nextcloud @manuel-rw
packages/integrations/src/nextcloud @manuel-rw
# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/npm @Aandree5
packages/integrations/src/npm @Aandree5
# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow
apps/docs/docs/integrations/ntfy @ajnart @manuel-rw
packages/integrations/src/ntfy @ajnart @manuel-rw
# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy
apps/docs/docs/integrations/open-media-vault @hillaliy
packages/integrations/src/openmediavault @hillaliy
# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman
apps/docs/docs/integrations/opnsense @Oupsman
packages/integrations/src/opnsense @Oupsman
# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
apps/docs/docs/integrations/overseerr @manuel-rw
packages/integrations/src/overseerr @manuel-rw
# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/paperless-ngx @ajnart
packages/integrations/src/paperless-ngx @ajnart
# PatchMon - introduced in https://github.com/homarr-labs/homarr/pull/6119 by @mrjoshuap
apps/docs/docs/integrations/patchmon @mrjoshuap
packages/integrations/src/patchmon @mrjoshuap
packages/widgets/src/patchmon @mrjoshuap
packages/api/src/router/widgets/patchmon.ts @mrjoshuap
packages/request-handler/src/patchmon.ts @mrjoshuap
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
apps/docs/docs/integrations/peanut @afonsojramos
packages/integrations/src/peanut @afonsojramos
# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf
apps/docs/docs/integrations/pi-hole @Meierschlumpf
packages/integrations/src/pi-hole @Meierschlumpf
# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy
apps/docs/docs/integrations/plex @hillaliy
packages/integrations/src/plex @hillaliy
# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy
apps/docs/docs/integrations/prowlarr @hillaliy
packages/integrations/src/prowlarr @hillaliy
# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf
apps/docs/docs/integrations/proxmox @Meierschlumpf
packages/integrations/src/proxmox @Meierschlumpf
# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
apps/docs/docs/integrations/quay @Aandree5
packages/integrations/src/quay @Aandree5
# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf
apps/docs/docs/integrations/search-ch @Meierschlumpf
packages/integrations/src/search-ch @Meierschlumpf
# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
packages/integrations/src/speedtest-tracker @kylegrieder
# Synology - introduced in https://github.com/homarr-labs/homarr/pull/6121 by @mrjoshuap
apps/docs/docs/integrations/synology @mrjoshuap
packages/integrations/src/synology @mrjoshuap
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
apps/docs/docs/integrations/technitium-dns @lopsch
packages/integrations/src/technitium @lopsch
# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle
apps/docs/docs/integrations/tracearr @StrandedTurtle
packages/integrations/src/tracearr @StrandedTurtle
# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw
apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos
packages/integrations/src/truenas @manuel-rw @afonsojramos
# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42
apps/docs/docs/integrations/umami @febrile42
packages/integrations/src/umami @febrile42
# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi
apps/docs/docs/integrations/unifi-controller @pitschi
packages/integrations/src/unifi-controller @pitschi
# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw
apps/docs/docs/integrations/unraid @manuel-rw
packages/integrations/src/unraid @manuel-rw
# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/uptime-kuma @ajnart
packages/integrations/src/uptime-kuma @ajnart
# Traefik - by @Nerdy-Technician
apps/docs/docs/integrations/traefik @Nerdy-Technician
apps/docs/docs/widgets/traefik @Nerdy-Technician
packages/api/src/router/widgets/traefik.ts @Nerdy-Technician
packages/integrations/src/traefik @Nerdy-Technician
packages/request-handler/src/traefik.ts @Nerdy-Technician
packages/widgets/src/traefik @Nerdy-Technician
# Exempt Renovate-managed files (no owners)
package.json
**/package.json
package-lock.json
+32 -12
View File
@@ -1,24 +1,43 @@
FROM node:24.16.0-alpine AS base
# syntax=docker/dockerfile:1.25
FROM node:24.18.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
RUN apk add --no-cache libc6-compat curl bash
RUN apk update
RUN apk add --no-cache libc6-compat curl bash && apk update
RUN corepack enable pnpm
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY patches ./patches
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
# content-addressable store is populated). By temporarily switching to the
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
# which IS created by pnpm fetch. The original hoisted linker is restored
# before the install step so the final node_modules layout stays flat.
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm config set store-dir /pnpm/store && \
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
pnpm fetch && \
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
COPY . .
# Follow the pnpm fetch pattern from https://pnpm.io/cli/fetch
# --frozen-lockfile is omitted as recommended by the pnpm fetch docs
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm install --recursive
RUN corepack enable pnpm && pnpm install --recursive --frozen-lockfile
# Copy static data as it is not part of the build
COPY static-data ./static-data
ARG SKIP_ENV_VALIDATION='true'
ARG CI='true'
ARG DISABLE_REDIS_LOGS='true'
ARG TARGETPLATFORM
RUN corepack enable pnpm && pnpm build
RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" pnpm build
FROM base AS runner
WORKDIR /app
@@ -42,6 +61,7 @@ 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/pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
@@ -23,6 +23,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
| AUTH_PROVIDERS | Select Which provider to use between *credentials*, *ldap* and *oidc*. <br /> Multiple providers can be enabled with by separating them with `,`, (ex. `AUTH_PROVIDERS=credentials,oidc`, it is highly recommended to just enable one provider). | credentials |
| AUTH_LOGOUT_REDIRECT_URL | URL to redirect to after clicking logging out. | --- |
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
<Tabs>
<TabItem value="credentials" label="Credentials Provider" default>
@@ -124,6 +125,8 @@ Homarr supports multiple authentication options, from internal userbase (credent
Roles are fetched from group claims. This can also be changed to roles for example added to a azure app registration by using the `AUTH_OIDC_GROUPS_ATTRIBUTE`.
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
<details>
<summary>
Example Setup
@@ -197,6 +200,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
| ``AUTH_OIDC_AUTO_LOGIN`` | Automatically redirect to OIDC login | false |
| ``AUTH_OIDC_SCOPE_OVERWRITE`` | Overwrite default scopes (openid, profile, email) | openid email profile groups |
| ``AUTH_OIDC_GROUPS_ATTRIBUTE`` | Attribute used for groups (roles) claim | groups |
| ``AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT`` | Stop syncing group memberships of OIDC users from the groups claim and manage them locally via the group members page instead | false |
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
+24 -14
View File
@@ -5,43 +5,47 @@ tags:
- Help
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
### Can I install Homarr on a Raspberry Pi?
Yes, Homarr is compatible with the architecture of the Raspberry Pi. Have a look in our <a href="/docs/category/installation-1">installation guide</a> on how to do so.
### I have a suggestion / idea for improvement or want to report a bug
You can report issues, suggestions, or ideas for improvements <a href="https://github.com/homarr-labs/homarr/issues/new/choose">here</a>.
### Can I use my own custom icons for apps?
Yes, check out our guide on <a href="/docs/advanced/customizations/icons">how to add your custom icons</a>.
### Some of my online icons are not working (anymore)
It seems the `cdn.jsdelivr.net` database of icon sometimes appear in the lists for DNS-Holes (PiHole, AdGuard Home...). <br />
So make sure to manually whitelist this address.
### Can I add my own widgets? Do I need programming skills?
We have decided, not to support custom widgets, until Homarr is in a stable and highly polished state.
Enabling users to write their own widgets is easy at the first glance, but there are some major issues with it:
- There are mainly two options for adding plugins or custom widgets to Homarr. The first option is, to allow and inject custom code in Homarr. Apps like Nextcloud can do this easily, because they use the Runtime to render all components of the application (In reality, the process is still a bit more complicated). In contrast, Homarr is using Next.js - a framework that needs to be compiled in production. That means, that each time you would modify a custom widget or plugin, the app would have to be restarted. There are options to avoid this, but they are quite flakey and bring disadvantages in our experience. The second option is, to write widgets declaratively instead of imperatively. This way, we would be able to render and modify widgets without any restarting at all. However, we'd have to write our own interpreter for said declarative schema. This is totally possible, but requires a lot of planning and architecture design to function properly. The required work to implement either option is therefore quite substantial.
- Custom widgets would need to be able to request data, so they work properly. We use tRPC for this. Many APIs use different formats and we'd have to convert and read those.
- Using custom widgets, one could easily create harmful widgets, leak your personal data (potentially including your tokens) and trick your users. A proper implementation (eg. official plugin store, where we can ban harmful widgets from) would resolve this problem to a certain extent, but again requires additional work and time to be put in.
Yes. Administrators can create [Custom Widgets](/docs/management/custom-widgets) that read an HTTP API and render the response with a built-in display type. These display types require no programming.
If you still want to add a widget, please consider to contribute it directly to Homarr. For that, basic programming skills are required. Our [Developer Guides](/docs/advanced/development/getting-started) explain this even more detailed.
Custom JSX is available when you need a more specific layout or an interactive control. It uses a restricted declarative JSX interpreter with a classified Mantine component set. Network operations are declared as named, same-origin requests and are checked against board permissions on the server. It does not execute arbitrary JavaScript or expose browser `fetch`.
You should be comfortable reading JSON and JSX to author complex templates. The management workbench includes examples, diagnostics, a component reference, API response inspection, and a prompt you can copy into an AI assistant. Always review imported templates and request capabilities before saving them.
### Is it secure to expose Homarr to the web?
Yes, since v0.14.0, we are confident that Homarr is secure enough to be exposed to the web.
Yes, since v0.14.0, we are confident that Homarr is secure enough to be exposed to the web.
Make sure to enable the "allow anonymous access" [option in the settings](/docs/management/boards#access-control), if you want to allow people without an account to be able to see your dashboard (not edit)
### How do I open the console / log?
There are two different sources of information:
- console: This is your client (browser). It can tell us, if there is an issue on the client (crash, bug, ...).
- log: This is your server. It will tell us, if there are issues with connections for the integrations, crashes and errors.
Both variants *can* contain personal data. It's recommended, that you don't post them publicly or remove personal data from it.
Both variants _can_ contain personal data. It's recommended, that you don't post them publicly or remove personal data from it.
<Tabs>
<TabItem value="apple" label="Read console" default>
@@ -49,39 +53,45 @@ Both variants *can* contain personal data. It's recommended, that you don't post
> Note: This will not work on most mobile browsers.
Open your browser and ensure, that Homarr is your current tab.
Press ``CTRL`` + ``SHIFT`` + ``I`` on your keyboard. If this shortcut doesn't work, please use one of the alternatives: https://developer.chrome.com/docs/devtools/open/.
Click on ``Console`` in the new window, that opened. There, you should see white, orange and sometimes red messages. You can use the Windows Snip & Sketch tool (or your respective tool on Linux / MacOS) to screenshot those messages.
Press `CTRL` + `SHIFT` + `I` on your keyboard. If this shortcut doesn't work, please use one of the alternatives: https://developer.chrome.com/docs/devtools/open/.
Click on `Console` in the new window, that opened. There, you should see white, orange and sometimes red messages. You can use the Windows Snip & Sketch tool (or your respective tool on Linux / MacOS) to screenshot those messages.
> Please never execute any commands in the console. Scammers are using the console to let you execute harmful commands. If you want to resolve an issue in Homarr, **you'll never have to execute any commands here**
</TabItem>
<TabItem value="orange" label="Read log">
This depends on your installation method. If you use Unraid, you can simply click on the App and on ``Log``. If you have a bare Docker install, use ``docker container logs homarr``. If you used a different name than ``homarr`` for your container, you need to replace said argument. Here is some additional information about the command: https://geekflare.com/check-docker-logs/.
This depends on your installation method. If you use Unraid, you can simply click on the App and on `Log`. If you have a bare Docker install, use `docker container logs homarr`. If you used a different name than `homarr` for your container, you need to replace said argument. Here is some additional information about the command: https://geekflare.com/check-docker-logs/.
</TabItem>
</Tabs>
### How do I post logs?
Please check [the section above](#how-do-i-open-the-console--log), on how to obtain logs. Logs should always be posted to https://pastebin.com/ or https://rentry.co/.
### Can I use Homarr at my company?
Yes, you can! But please consider a donation, if our work has been useful to you.
### I think I found a bug. What can I do?
If you think, that you found a bug, please create an issue <a href="https://github.com/homarr-labs/homarr/issues/new/choose">here</a>.
### Why can't I use my custom icons?
You need to restart the Homarr Docker container after adding icons.
### Why can't homarr see/ping containers running locally behind GluetunVPN?
This is because GluetunVPN, and many other docker VPN services, secure their VPN connections with strict firewalls. These firewalls often block access to local containers, even on the same docker network. The workaround for <a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/firewall.md">GluetunVPN</a> is to add "- FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24" into the environmental variables of the docker compose file. If you are using a firewall on your system, you will need to add a rule that allows requests from ip's in the range of 192.168.xxx.xxx . If you are using UFW the command would be "sudo ufw allow from 192.168.0.0/16".
Tip: If you are running your torrent client behind a VPN, and running Sonarr (or something similar) normally, make sure to open your torrent client's port in your firewall. If you don't, Sonarr will not be able to see it. Furthermore, use your machines IP (192.168.x.x) rather then 127.0.0.1 or localhost.
### Can I get in touch with you?
Yes, go <a href="/docs/community/get-in-touch">here</a> to get with us in touch.
<hr/>
<hr />
:::tip
@@ -28,6 +28,7 @@ Read our [glossary](/docs/getting-started/glossary) if you are new to Homarr.
Congratulations on installing Homarr. Now it's time to get yourself familiar with the concepts of Homarr and how to use it.
## Onboarding
The onboard will show the first time you start Homarr.
This process will help you with your first steps and adding some initial data, such as the administrator account.
@@ -45,50 +46,68 @@ You can either import a previous dashboard using the ZIP archive or start from s
You may refresh anytime and continue from a different device - the onboarding is saved immediately on the server for your convenience.
### Starting from scratch
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
If credentials provider was selected, you'll be prompted to create an admin user for Homarr.
This user cannot be deleted and has full control over Homarr and all of it's boards.
You must choose an username and password (both can later be changed).
We enforce strict password requirements, when you enter your password, to protect your instance if you accidentially
expose it to the Internet.
<img src={imageStartFromScratchStepAdminUser} alt={"Screenshot form to create admin user"} width={300} />
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
If the credentials provider was selected, you'll be prompted to create an admin user for Homarr.
This user cannot be deleted and has full control over Homarr and all of its boards.
You must choose a username and password (both can later be changed).
We enforce strict password requirements when you enter your password to protect your instance if you accidentally
expose it to the internet.
<img
src={imageStartFromScratchStepAdminUser}
alt={"Screenshot form to create admin user"}
width={300}
/>
If you selected external providers (such as LDAP or OIDC), you'll be prompted to create an admin group.
This group will be used to synchronize users from your provider to Homarr.
When a user is logged in through the provider the groups of him that match groups with any in Homarr will be added to the user.
The name of the group has to match exactly the name of the group in the provider.
This group will be used to synchronize users from your provider to Homarr.
When a user logs in through the provider, any of their groups that match groups in Homarr will be assigned to the user.
The name of the group must match exactly the name of the group in the provider.
<img src={imageStartFromScratchStepExternalAdminGroup} alt={"Screenshot form to create external admin group"} width={300} />
<img
src={imageStartFromScratchStepExternalAdminGroup}
alt={"Screenshot form to create external admin group"}
width={300}
/>
Next, you can configure some basic settings for Homarr. We recommend to leave all settings at default for most users.
Here you can also disable analytics. Please note that our analytics are completly anonymous and is fully compliant with GDPR and PECR.
<img src={imageStartFromScratchStepServerSettings} alt={"Screenshot of analytics and indexing settings in the onboarding"} width={300} />
<img
src={imageStartFromScratchStepServerSettings}
alt={"Screenshot of analytics and indexing settings in the onboarding"}
width={300}
/>
### Setting up integrations
After configuring server settings, the onboarding guides you through setting up your integrations.
#### Docker service discovery
If Docker is available (socket mounted), Homarr automatically detects running containers and matches them to known integration types.
Matching integrations are pre-selected in the integration list, saving you from manually picking each one.
#### URL templating
You can define a base URL template to auto-fill integration URLs in bulk.
Two modes are supported:
- **Subdomain mode** - e.g. `{app}.example.com` where `{app}` is replaced with the integration name
- **Port mode** - e.g. `192.168.1.1:{port}` where `{port}` is replaced with the integration's default port
#### API key helper links
For integrations that require API keys (such as Sonarr, Radarr, SABnzbd, or Seerr), a helper link opens the relevant settings page in the target application so you can quickly copy your key.
#### Creating apps from Docker containers
You can optionally auto-create bookmark apps for all discovered Docker containers, making them immediately available on your board.
:::note
Some niche and registry integrations (GitHub, npm, Docker Hub, GitLab, Codeberg, LinuxServer.io, GHCR, Quay, Search.ch) are hidden from the onboarding selection grid but remain available on the manage integrations page.
Release providers and Search.ch are configured directly in their widgets instead of the integrations page.
:::
@@ -97,30 +116,41 @@ The onboarding can no longer be started again.
From here you can click on one of the provided links to continue your journey.
We recommend you to continue with the section [creating your first board on this page](#creating-your-first-board).
<img src={imageStartFromScratchStepFinish} alt={"Screenshot of the finish onboarding process with links"} width={300} />
<img
src={imageStartFromScratchStepFinish}
alt={"Screenshot of the finish onboarding process with links"}
width={300}
/>
### Importing a ZIP from version before 1.0.0
This is recommended when you used Homarr before and want to re-import your data.
It is important that you first migrate correctly using [the list of breaking changes for 1.0](/blog/2024/09/23/version-1.0).
<img src={manageExportOldHomarr} alt="Export from old Homarr" style={{ maxWidth: 500 }} />
<img
src={manageExportOldHomarr}
alt="Export from old Homarr"
style={{ maxWidth: 500 }}
/>
**How to export and import data**
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open ``Migrate to 1.0``.
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open `Migrate to 1.0`.
2. Check the contents that you want to export. We recommend you to export all data.
3. Click on the export data button
4. Save the ZIP file, that is being downloaded, on your device. If you selected "integrations" or "users", you also get an encryption / decryption key.
Copy it and do not loose it!
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose ``Import from Homarr before 1.0``
Copy it and do not loose it!
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose `Import from Homarr before 1.0`
6. Drag and drop your previously downloaded ZIP archive into the file upload: <br/><img src={manageImportOldHomarrDropzone} style={{ maxWidth: 500 }} />
7. Next, choose the data that you want to import and how to import it. Since screen sizes were removed in 1.0, we create a new board for every screen size by default. We recommend you to only keep the best layout and adjust it later in 1.0.0 - but you can also import all of them and pick later.<br/><img src={manageImportOldHomarrChooseData} style={{ maxWidth: 500 }} />
8. If you previously exported integrations or users, you will be asked to decrypt the data safely using your obtained encryption token. Paste into the modal and confirm your action.<br/><img src={manageImportDecryptionToken} style={{ maxWidth: 500 }} />
9. Next, you can [follow the same setup as when starting from scratch](#starting-from-scratch). After you completed the onboarding, all of your data will be migrated and available.
## Creating your first board
In Homarr, you organize your apps and widgets on boards.
Boards can be organized using the interactive drag and drop.
Let's create your first board by navigating here: ``Manage`` > ``Boards`` > ``New board``:
Let's create your first board by navigating here: `Manage` > `Boards` > `New board`:
![](./img/manage-create-board-button.png)
A board must always have a name and can either be public or private.
@@ -133,12 +163,13 @@ Congratulations - you've just created your first board. You can see boards, that
![](./img/manage-view-board.png)
## Add your first app to Homarr
To add links to your dashboard, you must first add them as an "app".
Navigate to this page to add an app:
![](./img/manage-navigate-apps.png)
Next, click on the ``New app`` button. You'll need to provide a name and an icon to create an app.
Next, click on the `New app` button. You'll need to provide a name and an icon to create an app.
The name must be at least one character long but you can provide any image URL for the icon.
Homarr ships with over 11K icons - so you can just search by entering a search text instead.
@@ -149,8 +180,9 @@ After you created the app, you'll see in on the apps page:
![](./img/manage-created-app.png)
## Arrange and organize your board
Let's add your freshly created app, from the previous step, to the board.
To do this, edit your previously created board by clicking on the big ``Open board`` button:
To do this, edit your previously created board by clicking on the big `Open board` button:
![](./img/manage-view-board.png)
@@ -165,23 +197,38 @@ Here you have the following options in the header:
When entering the edit mode, you'll get some additional buttons to work with:
<img src={manageBoardHeaderEditMode} alt={"Screenshot of buttons in the edit mode"} width={200} />
<img
src={manageBoardHeaderEditMode}
alt={"Screenshot of buttons in the edit mode"}
width={200}
/>
- **Add item**: Click this button to add new elements to your page.
- **Exit edit mode**: Click this btuton to save your changes and exit edit mode.
To continue, click the option "New Item":
<img src={manageBoardHeaderAddItem} alt={"Screenshot of the add item menu"} width={200} />
<img
src={manageBoardHeaderAddItem}
alt={"Screenshot of the add item menu"}
width={200}
/>
Next, you can choose what item you'd like to add.
We will discuss the other options in the following sections - for now choose "App" to continue.
<img src={manageBoardHeaderChooseItem} alt={"Screenshot of choosing the app item in the modal"} width={300} />
<img
src={manageBoardHeaderChooseItem}
alt={"Screenshot of choosing the app item in the modal"}
width={300}
/>
As soon as you chose the app, the modal will close and you'll get the following item on your board:
<img src={manageBoardNoApp} alt="Screenshot of the No app element on the board" />
<img
src={manageBoardNoApp}
alt="Screenshot of the No app element on the board"
/>
This is expected, because you haven't told Homarr what app it should show in this item.
To configure this, click on the small 3 dots at the top of the item in the edit mode and choose "Edit item" in the menu.
@@ -228,17 +275,18 @@ Be sure to save your changes by exiting edit mode!
![](./img/exit-edit-mode.png)
## Adding your first integration
Now it's time to integrate some basic data into your board.
To do this, we first need to create an integration.
An integration is a connection between Homarr and a third party application - usually something that you run and deploy yourself.
Navigate to ``Manage`` > ``Integrations`` (for example via your user menu).
Navigate to `Manage` > `Integrations` (for example via your user menu).
As an example, we are going to use the public demo instance of **Dash.** - a popular application to monitor CPU / hardware usage.
Choose the **Dash.** integration from the list of available integrations:
![](./img/choose-integration.gif)
Next, choose a name (or leave it at default), enter ``https://dash.mauz.dev/`` as the URL in the form and click on ``Test connection and create``.
Next, choose a name (or leave it at default), enter `https://dash.mauz.dev/` as the URL in the form and click on `Test connection and create`.
Pressing the save button will perform a test request to the integration, to confirm that the networking setup is working as expected.
Sometimes invalid configurations, firewalls and other mistakes can lead to connectivity problems - [follow this guide here](/docs/management/integrations/#troubleshooting-connection) if you experience issues.
@@ -254,13 +302,14 @@ the target application or your router.
:::
## Embed integration data using widgets
Let's add your integration, that you have configured in the section above, as a widget to your board.
First, navigate back to your board that you want to add it to.
Next, enter edit mode again and add another item to your board:
![](./img/manage-board-header-add-item-menu.png)
Instead of choosing an app, let's choose ``System Health Monitoring`` this time.
Instead of choosing an app, let's choose `System Health Monitoring` this time.
Click on "Add to board":
![](./img/manage-board-header-choose-system-health.png)
@@ -290,10 +339,12 @@ As with every change, don't forget to save your changes using the exit edit mode
![](./img/exit-edit-mode.png)
## Grouping your items
As the last step, let's organize your freshly created items using two different options.
You can choose either or both of them on your boards.
### Using dynamic sections
Dynamic sections are a simple, resizable visual group to group items.
![](./img/create-dynamic-section.png)
@@ -303,6 +354,7 @@ After adding a dynamic section, you can simply drag and drop items into the sect
![](./img/dynamic-section.gif)
### Using categories
Categories are more advanced and complicated than dynamic sections.
To create a category, open the add item modal and choose "new category":
@@ -329,22 +381,27 @@ You cannot reduce their horizontal width.
After completing the onboarding, Homarr provides interactive guided tours to help you discover key features.
### Management tour
When you first visit the management area (`/manage`), Homarr walks you through the main pages: boards, apps, integrations, and users (admin only).
Each page highlights the list view and the create action so you know where to find and add things.
### Board tour
When you first open a board, a tour highlights the board header elements: search, edit mode, settings, board switcher, and user menu.
### Tour controls
- Press **Enter** (or click **Next**) to advance through tour steps
- Tours auto-start on first visit and do not repeat once completed
- Tours are skipped on mobile screens (below `sm` breakpoint)
- You can **reset tours** from the user settings page under **Manage > Users > [Your user] > General**
## Next steps
You're done with the basic setup 🔥. Now it's time to organize and customize even more.
We highly recommend you to experiment with different options, styles and configurations.
See these curated pages for your next steps:
- Create new apps using the [apps management](/docs/management/apps/)
- Customize your board or add more boards using the [boards management](/docs/management/boards/)
- Integrate software that you use using the [integrations management](/docs/management/integrations/)
@@ -2,9 +2,9 @@
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
![Version: 8.19.0](https://img.shields.io/badge/Version-8.19.0-informational?style=flat)
![Version: 8.22.0](https://img.shields.io/badge/Version-8.22.0-informational?style=flat)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat)
![AppVersion: v1.60.0](https://img.shields.io/badge/AppVersion-v1.60.0-informational?style=flat)
![AppVersion: v1.70.0](https://img.shields.io/badge/AppVersion-v1.70.0-informational?style=flat)
A Helm chart to deploy homarr for Kubernetes
@@ -383,6 +383,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
| containerPorts | object | `{"http":{"port":7575,"protocol":"TCP"}}` | containerPorts defines the ports to open on the container. It is a map where each entry specifies: - `port` (int) (required): The port number to expose inside the container. - `protocol` (string) (required): The network protocol (TCP or UDP) used for the port. - `disabled` (bool) : Optional flag to disable this port (defaults to false). Can be overridden via Helm values. By default, this configuration exposes TCP port 7575 with the name `http`. |
| database.migrationEnabled | bool | `true` | Database migration configuration. DB_MIGRATIONS_DISABLED Set to `true` to disable database migrations. Migrations are enabled by default (`false`). |
| database.type | string | `"sqlite"` | Database type: sqlite, mysql or postgresql |
| env.AUTH_COOKIE_PREFIX | string | `"homarr"` | Prefix used for all authentication cookies. Change if you run another Auth.js/NextAuth app on the same hostname to avoid cookie name collisions. Only letters, numbers, hyphens and underscores. |
| env.AUTH_LDAP_BASE | string | `nil` | Base dn of your LDAP server |
| env.AUTH_LDAP_BIND_DN | string | `nil` | User used for finding users and groups |
| env.AUTH_LDAP_GROUP_CLASS | string | `"groupOfUniqueNames"` | Class used for querying groups |
@@ -428,7 +429,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
| image.tag | string | `"v1.60.0"` | Overrides the image tag whose default is the chart appVersion |
| image.tag | string | `"v1.70.0"` | Overrides the image tag whose default is the chart appVersion |
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
| ingress.annotations | object | `{}` | Ingress annotations |
| ingress.enabled | bool | `false` | Enable ingress |
@@ -0,0 +1,35 @@
---
title: "ArchiveTeam Warrior"
description: "ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive."
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { archiveTeamWarriorIntegration } from ".";
<IntegrationHeader integration={archiveTeamWarriorIntegration} categories={["archiving"]} />
The ArchiveTeam Warrior integration lets Homarr connect to your ArchiveTeam Warrior instance so you can manage and monitor your archiving tasks directly from your dashboard.
### Adding the integration
<AddingIntegration />
### Secrets
ArchiveTeam Warrior does not require authentication by default. If your instance is protected with basic authentication, you can provide a username and password.
<IntegrationSecrets
secrets={[
{
credentials: [],
steps: [],
},
{
credentials: ["username", "password"],
steps: [],
},
]}
/>
@@ -0,0 +1,9 @@
import { IntegrationDefinition } from "@site/src/types";
export const archiveTeamWarriorIntegration: IntegrationDefinition = {
name: "ArchiveTeam Warrior",
description:
"ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive.",
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/archiveteam-warrior.png",
path: "../../integrations/archiveteam-warrior",
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,54 @@
---
title: 'Bazarr'
description: "Bazarr is a companion application to Sonarr and Radarr that manages and downloads subtitles."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { bazarrIntegration } from '.';
import { bazarrWidget } from '@site/docs/widgets/bazarr';
<IntegrationHeader
integration={bazarrIntegration}
categories={['Subtitles', 'Media']}
/>
Bazarr complements Sonarr and Radarr by managing subtitle downloads. The Homarr integration uses your Bazarr API key to fetch missing subtitle counts and health badges.
### Screenshots
import apiKeySettings from './img/api-key-settings.png';
import widgetOverview from '@site/docs/widgets/bazarr/img/widget-overview.png';
<div className="flex flex-col gap-4">
<div>
<p>API key location in Bazarr (<strong>Settings → General → Security</strong>):</p>
<img src={apiKeySettings} alt="bazarr-api-key-settings" width={128 * 4} />
</div>
<div>
<p>Bazarr widget on a Homarr board:</p>
<img src={widgetOverview} alt="bazarr-widget-on-board" width={128 * 4} />
</div>
</div>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{ widget: bazarrWidget }]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
You can find your Bazarr API key under **Settings → General → Security**.
<IntegrationSecrets secrets={[{
credentials: ['apiKey'],
steps: [
"Open Bazarr and go to Settings → General → Security.",
"Copy the API key and paste it into Homarr when creating the integration.",
],
}]} />
@@ -0,0 +1,9 @@
import { IntegrationDefinition } from "@site/src/types";
export const bazarrIntegration: IntegrationDefinition = {
name: "Bazarr",
description:
"Bazarr is a companion application to Sonarr and Radarr that manages and downloads subtitles for your media library.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/bazarr.svg",
path: "../../integrations/bazarr",
};
@@ -1,37 +0,0 @@
---
title: 'Codeberg'
description: "Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { codebergIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={codebergIntegration}
categories={['Version Control', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ['personalAccessToken'],
steps: []
}]} />
@@ -1,9 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const codebergIntegration: IntegrationDefinition = {
name: "Codeberg",
description:
"Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/codeberg.svg",
path: "../../integrations/codeberg",
};
@@ -1,37 +0,0 @@
---
title: 'Docker Hub'
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { dockerHubIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={dockerHubIntegration}
categories={['Container Management', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ['username', 'personalAccessToken'],
steps: []
}]} />
@@ -1,8 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const dockerHubIntegration: IntegrationDefinition = {
name: "Docker Hub",
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/docker.svg",
path: "../../integrations/docker-hub",
};
@@ -1,42 +0,0 @@
---
title: 'GitHub Container Registry'
description: "GitHub Container Registry is a platform for hosting and managing packages."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { gitHubContainerRegistryIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={gitHubContainerRegistryIntegration}
categories={['Package Management', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ["personalAccessToken"],
steps: []
}, {
tabLabel: "GitHub App",
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
}]} />
@@ -1,11 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const gitHubContainerRegistryIntegration: IntegrationDefinition = {
name: "GitHub Container Registry",
description: "GitHub Container Registry is a platform for hosting and managing packages.",
iconUrl: {
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
},
path: "../../integrations/github-packages",
};
@@ -1,41 +0,0 @@
---
title: 'GitHub'
description: "GitHub is a web-based platform for version control and collaboration."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { githubIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={githubIntegration}
categories={['Version Control', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ['personalAccessToken'],
steps: []
}, {
tabLabel: "GitHub App",
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
}]} />
@@ -1,11 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const githubIntegration: IntegrationDefinition = {
name: "GitHub",
description: "GitHub is a web-based platform for version control and collaboration.",
iconUrl: {
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
},
path: "../../integrations/github",
};
@@ -1,37 +0,0 @@
---
title: 'GitLab'
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { gitlabIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={gitlabIntegration}
categories={['Version Control', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ['personalAccessToken'],
steps: []
}]} />
@@ -1,8 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const gitlabIntegration: IntegrationDefinition = {
name: "GitLab",
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitlab.svg",
path: "../../integrations/gitlab",
};
@@ -0,0 +1,52 @@
---
title: 'Gluetun'
description: 'Connect Gluetun to Homarr as a VPN integration.'
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { gluetunIntegration } from '.';
import { vpnWidget } from '@site/docs/widgets/vpn';
<IntegrationHeader
integration={gluetunIntegration}
categories={['VPN', 'Network']}
/>
[Gluetun](https://github.com/qdm12/gluetun) exposes a control server that Homarr can query to report the VPN tunnel
status, DNS status, public IP address, and active provider details in the VPN widget.
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: vpnWidget,
}]}
/>
### Setting up the Gluetun control server
Refer to the [Gluetun control server documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication) to enable and configure authentication.
### Adding the integration
<AddingIntegration />
### Secrets
Gluetun's control server can be left unprotected or secured with either an API key or HTTP Basic authentication.
Choose the option that matches how your Gluetun instance integration is set up.
<IntegrationSecrets secrets={[{
credentials: [],
steps: [],
},{
credentials: ['username', 'password'],
steps: [],
},{
credentials: ['apiKey'],
steps: [],
},,]} />
@@ -0,0 +1,8 @@
import { IntegrationDefinition } from "@site/src/types";
export const gluetunIntegration: IntegrationDefinition = {
name: "Gluetun",
description: "VPN control server integration for Homarr's VPN widget.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gluetun.svg",
path: "../../integrations/gluetun",
};
@@ -1,26 +1,25 @@
---
title: 'LinuxServer.io'
description: "LinuxServer.io is a platform for managing and deploying containerized applications."
title: 'Gotify'
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { notificationsWidget } from '@site/docs/widgets/notifications';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { linuxServerIoIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { gotifyIntegration } from '.';
<IntegrationHeader
integration={linuxServerIoIntegration}
categories={['Container Management', 'Software']}
integration={gotifyIntegration}
categories={['Notifications', 'Messaging']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
widget: notificationsWidget
}]}
/>
@@ -29,6 +28,6 @@ import { releasesWidget } from '@site/docs/widgets/releases';
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
credentials: ['username', 'password'],
steps: []
}]} />
}]} />
@@ -0,0 +1,8 @@
import { IntegrationDefinition } from "@site/src/types";
export const gotifyIntegration: IntegrationDefinition = {
name: "Gotify",
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gotify.svg",
path: "../../integrations/gotify",
};
+21 -8
View File
@@ -39,13 +39,26 @@ import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
'Scroll down to "API Keys"',
'Click "New API Key"',
'Enter a name, e.g. "Homarr"',
(<span>
Select the following permissions:{" "}
<code>album.read</code>,{" "}
<code>user.read</code>,{" "}
<code>server.statistics</code>,{" "}
<code>asset.download</code>
</span>),
(
<span>
Select the following permissions:{" "}
<code>album.statistics</code>,{" "}
<code>album.read</code>,{" "}
<code>album.create</code>,{" "}
<code>user.read</code>,{" "}
<code>user.update</code>,{" "}
<code>asset.read</code>,{" "}
<code>asset.download</code>,{" "}
<code>asset.statistics</code>,{" "}
<code>server.statistics</code>,{" "}
<code>server.about</code>.
<br />
Additional permissions may be required depending on your Immich version. If you encounter authorization errors, grant any missing permissions requested by the server.
</span>
),
'Create the integration in Homarr and copy the API key to there'
]
}]} />
}]} />
@@ -1,8 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const linuxServerIoIntegration: IntegrationDefinition = {
name: "LinuxServer.io",
description: "LinuxServer.io is a platform for managing and deploying containerized applications.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
path: "../../integrations/linux-server-io",
};
-34
View File
@@ -1,34 +0,0 @@
---
title: 'NPM'
description: "NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { npmIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={npmIntegration}
categories={['Package Management', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}]} />
-9
View File
@@ -1,9 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const npmIntegration: IntegrationDefinition = {
name: "NPM",
description:
"NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/npm.svg",
path: "../../integrations/npm",
};
@@ -0,0 +1,43 @@
---
title: 'PatchMon'
description: "PatchMon is a patch management and monitoring solution for Linux hosts."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { patchmonIntegration } from '.';
import { patchmonWidget } from '@site/docs/widgets/patchmon';
<IntegrationHeader
integration={patchmonIntegration}
categories={['Health Monitoring']}
/>
PatchMon provides host and package update statistics including total hosts, hosts needing updates, and security update counts.
The PatchMon widget requires users to be **signed in to Homarr**; see the [PatchMon widget documentation](/docs/widgets/patchmon#authentication) for details on authentication and public boards.
### Widgets & Capabilities
<IntegrationCapabilites
items={[
{ widget: patchmonWidget },
]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: ['patchmonApiKey', 'patchmonApiSecret'],
steps: [
'Open your PatchMon instance',
'Navigate to Settings and create a new GetHomepage API key',
'Copy the API key and secret from the success dialog',
'Paste them into Homarr — Basic authentication is handled automatically',
]
}]} />
@@ -0,0 +1,9 @@
import { IntegrationDefinition } from "@site/src/types";
export const patchmonIntegration: IntegrationDefinition = {
name: "PatchMon",
description:
"PatchMon is a patch management and monitoring solution for Linux hosts, tracking package updates and security patches across your infrastructure.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/patchmon.svg",
path: "../../integrations/patchmon",
};
@@ -1,37 +0,0 @@
---
title: 'Quay'
description: "Quay is a container registry for storing and managing container images."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { quayIntegration } from '.';
import { releasesWidget } from '@site/docs/widgets/releases';
<IntegrationHeader
integration={quayIntegration}
categories={['Container Registry', 'Software']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{
widget: releasesWidget
}]}
/>
### Adding the integration
<AddingIntegration />
### Secrets
<IntegrationSecrets secrets={[{
credentials: [],
steps: []
}, {
credentials: ["personalAccessToken"],
steps: []
}]} />
@@ -1,8 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const quayIntegration: IntegrationDefinition = {
name: "Quay",
description: "Quay is a container registry for storing and managing container images.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png",
path: "../../integrations/quay",
};
@@ -1,28 +0,0 @@
---
title: 'Search.ch'
description: "Search.ch supports searching for timetables in Switzerland."
hide_title: true
---
import { IntegrationHeader } from '@site/src/components/integrations/header';
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
import { AddingIntegration } from '@site/src/components/integrations/adding';
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
import { IconDownload } from "@tabler/icons-react";
import { searchChIntegration } from '.';
import Alert from '@theme/Admonition';
import { timetableWidget } from '@site/docs/widgets/timetable';
<IntegrationHeader
integration={searchChIntegration}
categories={['Timetable']}
/>
### Widgets & Capabilities
<IntegrationCapabilites
items={[{ widget: timetableWidget }]}
/>
### Adding the integration
<AddingIntegration />
@@ -1,7 +0,0 @@
import { IntegrationDefinition } from "@site/src/types";
export const searchChIntegration: IntegrationDefinition = {
name: "Search.ch",
description: "Search.ch supports searching for timetables in Switzerland.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png",
path: "../../integrations/search-ch",
};
@@ -0,0 +1,96 @@
---
title: "Synology DiskStation"
description: "Monitor CPU, memory, storage volumes, and system health from Synology DSM."
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { synologyIntegration } from ".";
import { systemResourcesWidget } from "@site/docs/widgets/system-resources";
import { systemDisksWidget } from "@site/docs/widgets/system-disks";
import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring";
import Alert from "@theme/Admonition";
<IntegrationHeader integration={synologyIntegration} categories={["NAS", "Hardware"]} />
### Widgets & Capabilities
<IntegrationCapabilites
items={[
{ widget: healthMonitoringWidget },
{ widget: systemResourcesWidget },
{ widget: systemDisksWidget },
]}
/>
### Supported metrics
| Widget section | DSM API source | May be unavailable |
| ---------------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
| CPU ring / chart | `SYNO.Core.System.Utilization` | — |
| Memory ring / chart | `SYNO.Core.System.Utilization` | — |
| CPU temperature ring | `SYNO.Core.System` system info | Models without a temperature sensor |
| Network charts | `SYNO.Core.System.Utilization` network totals | Some DSM builds or permission configurations |
| Load average (info modal) | `SYNO.Core.System.Utilization` CPU load fields | — |
| Storage volume usage | `SYNO.Core.System` storage info / Storage Manager API | — |
| SMART status and temperature | Storage Manager and SMART APIs | Disk-to-volume mapping may be incomplete on some setups |
| Reboot / update indicator | `SYNO.Core.System.Status` / upgrade APIs | Permission-dependent |
| GPU | Not available | Typical DiskStation models |
### Adding the integration
<AddingIntegration />
<Alert type="info" title="Use the correct DSM URL">
Enter the HTTP or HTTPS URL of your Synology DSM web interface, for example <code>http://diskstation.local:5000</code>{" "}
or <code>https://diskstation.local:5001</code>. If DSM uses a self-signed certificate, add it under{" "}
<a href="/docs/management/certificates">Management &gt; Tools &gt; Certificates</a> so Homarr can connect securely.
</Alert>
### Secrets
<IntegrationSecrets
secrets={[
{
credentials: ["username", "password"],
steps: [
"Create a dedicated DSM user with a strong password for Homarr.",
"Add the user to the default Administrators group so Homarr can read `SYNO.Core.System` metrics.",
"Under User Groups and Permissions, deny access to shared folders and most applications.",
"Allow login to DSM only, optionally restricted by source IP address.",
"Log in once with the service account in DSM to accept terms and conditions.",
],
body: (
<>
<Alert type="caution" title="Administrators group required">
Homarr reads system, storage, and utilization data from APIs that require membership in the default
Administrators group.
</Alert>
<Alert type="warning" title="Two-factor authentication is not supported">
Accounts with 2-step verification enabled cannot authenticate with this integration. Use a dedicated service
account without 2FA.
</Alert>
<Alert type="tip" title="First-time setup">
If login fails during testing, temporarily disable automatic blocking for invalid logins under Control Panel
&gt; Security &gt; Protection, then re-enable it after Homarr connects successfully.
</Alert>
</>
),
},
]}
/>
### Storage volume selection
Health Monitoring and System Disks widgets can filter Synology storage volumes when **every** selected integration is a Synology DiskStation. Leave **Visible storage volumes** empty to show all volumes, or pick specific volumes from the list. When multiple Synology integrations are connected, volumes are labeled with the integration name.
<Alert type="note" title="Synology-only option">
The volume filter is hidden when the widget also uses non-Synology integrations such as TrueNAS or Unraid.
</Alert>
<Alert type="note" title="Volume ordering">
When no volume filter is configured, the first volume returned by DSM is not guaranteed to be `volume_1`.
</Alert>
@@ -0,0 +1,8 @@
import { IntegrationDefinition } from "@site/src/types";
export const synologyIntegration: IntegrationDefinition = {
name: "Synology DiskStation",
description: "Monitor CPU, memory, storage volumes, and system health from Synology DSM.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/synology.svg",
path: "../../integrations/synology",
};
@@ -0,0 +1,57 @@
---
title: "Traefik"
description: "Cloud-native reverse proxy and load balancer"
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { traefikIntegration } from ".";
import { traefikWidget } from "../../widgets/traefik";
<IntegrationHeader integration={traefikIntegration} categories={["Reverse proxy", "Monitoring"]} />
### Widgets & Capabilities
<IntegrationCapabilites items={[{ widget: traefikWidget }]} />
### Adding the integration
<AddingIntegration />
:::info URL configuration
Use the URL where Traefik exposes its dashboard/API, for example `http://traefik:8080` or `https://traefik.example.com`. Homarr calls Traefik endpoints under `/api` automatically.
:::
:::warning API requirement
Traefik must have the API enabled. For local or internal setups this is commonly exposed through `api.insecure=true`; for production setups, expose the `api@internal` service through an authenticated router.
:::
### Secrets
<IntegrationSecrets
secrets={[
{
credentials: [],
steps: [
"Choose this option when the Traefik API is only reachable from a trusted internal network and does not require authentication.",
],
},
{
credentials: ["username", "password"],
steps: [
"Use this option when your Traefik dashboard route is protected by Basic Auth.",
"Enter the same username and password required to open the Traefik dashboard.",
],
},
{
credentials: ["apiKey"],
steps: [
"Use this option when your Traefik dashboard route accepts a bearer token.",
"Paste the token into the API Key field in Homarr.",
],
},
]}
/>
@@ -0,0 +1,8 @@
import { IntegrationDefinition } from "@site/src/types";
export const traefikIntegration: IntegrationDefinition = {
name: "Traefik",
description: "Cloud-native reverse proxy and load balancer",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/traefik.svg",
path: "../../integrations/traefik",
};
+13 -1
View File
@@ -70,4 +70,16 @@ API key authentication enables programmatic access for:
- Automation scripts and CI/CD pipelines
- Third-party integrations
- Custom dashboards and monitoring tools
- Container orchestration systems that need to sync app definitions
- Container orchestration systems that need to sync app definitions
## Board Automation
The OpenAPI and MCP APIs include board management endpoints for common automation workflows:
- Create, duplicate, rename and delete boards
- Change board visibility
- Update board visual and behavior settings
- Set desktop and mobile home boards for the current user
- Configure global board defaults such as public home boards and status behavior
Use the interactive API reference to inspect the exact request body for each endpoint under the `boards` and `settings` tags.
@@ -0,0 +1,26 @@
---
sidebar_label: AI and MCP authoring
---
# AI and MCP authoring
Homarr exposes an iterative, validation-first workflow to authenticated administrators. Agents should continue until validation, preview, and diagnostics are clean; they must not invent inline network access.
## Recommended loop
1. Read `customWidget_schema` and the generated component/capability reference.
2. Read an existing definition with `customWidget_byId`, or create a v3 draft.
3. Call `customWidget_validate`; fix every issue using its exact path.
4. Call `customWidget_preview` for the base GET and retain the preview session ID.
5. Test each named query with `customWidget_previewQuery`.
6. Dry-run every action with `customWidget_simulatePreviewAction`.
7. Inspect `customWidget_previewJournal` for status, duration, and method coverage.
8. Revalidate, then call import/create or a focused update/patch operation.
Stored secret values are never readable. Omitting secrets during an update preserves them. MCP actions are simulation-only; enabling live preview actions remains a browser-only administrator decision.
## Template format
Use multiline JSX when reading or patching templates. Prefer focused template read/write/patch tools over rewriting an escaped one-line JSON string. When a complete portable artifact is needed, use the authoring bundle placeholder plus a separate fenced JSX block.
The AI prompt is generated from the same registry, examples, request rules, and limits as the validator. Agents should still trust validation output over prompt memory because the installed Homarr version is authoritative.
@@ -0,0 +1,29 @@
---
sidebar_label: Maintainer architecture
---
# Maintainer architecture
Reusable Custom Widget behavior belongs to `@homarr/custom-widgets`:
- `core` owns schemas, descriptors, migrations, import/export, examples, diagnostics metadata, and prompt generation.
- `jsx` owns AST parsing, shared policy, interpreter, bindings, registry, wrappers, and analysis.
- `runtime` owns renderers and named-request controls through injected ports.
- `server` owns manifests, network execution, caching, limits, preview sessions, and typed domain errors.
- `workbench` owns form contracts, editor analysis, preview UI, response tree, and import review through injected ports.
The API package retains persistence and board authorization adapters. Widgets retain board registration and runtime transport wiring. Next.js routes retain navigation and shells. The domain package must never import API, widgets, or route modules.
## Add a display type
Extend the typed descriptor and discriminated schema, defaults/hydration/serialization, renderer, workbench section, preview, import round-trip fixtures, docs, and translations. Create, edit, import, export, MCP, and preview must all use the same transform.
## Add a JSX component
Classify the upstream export, document safety mode and supported props, add a wrapper if behavior needs constraining, and update registry tests. CI fails when a Mantine upgrade introduces an unclassified export.
## Change networking
Implement policy below the framework-neutral server boundary and expose infrastructure through a typed port. Add tests for permissions, typed substitution, DNS classes, redirects, headers, timeouts, limits, redaction, cache behavior, and action non-retry semantics before wiring the tRPC adapter.
Production files target 300 lines. Algorithmic AST/network modules may use a documented 400-line ceiling. Dependency-boundary, cycle, file-size, translation, generated-artifact, component-classification, focused unit, integration, E2E, accessibility, and docs-build gates protect the architecture. After a Next.js production build, `pnpm check:custom-widget-bundle` also enforces a 768 KiB gzip ceiling across the client chunks referenced by Custom JSX.
@@ -0,0 +1,36 @@
---
sidebar_label: Authoring workbench
---
# Authoring workbench
The Custom Widget form is a continuous workflow: choose a display, configure the HTTP connection, configure the display, then test it in the adjacent preview. Each section stays visible so you can revisit earlier choices without losing context.
## Choose a display
The display grid is the first product decision. Built-in displays are best for common metrics, tables, statuses, and actions. Choose Custom JSX when the layout needs multiple Mantine components, conditional presentation, charts, dates, or named requests.
Changing display type preserves shared connection fields and initializes safe display defaults. A Custom JSX template is never blank; new and migrated forms receive the tested starter fixture.
## Test and preview
The preview has four tabs:
- **Widget** renders at compact, standard, or wide board-like sizes.
- **Response** uses a tree to inspect data, copy paths, and insert paths into JSX. The external-response action opens a temporary local blob and never publishes the response.
- **Request** shows the redacted base request, named capabilities, status, duration, and preview journal.
- **Diagnostics** shows template size, named requests, network scope, and blocking or advisory issues.
Only GET base requests run automatically. Preview actions are simulated until an administrator explicitly enables live actions for the five-minute preview session. DELETE still requires confirmation.
## Keyboard and clipboard
The JSX and JSON editors support native Ctrl/Cmd+Z and redo, plus visible toolbar controls. Cursor position, character count, and the 50,000-character limit remain visible.
On the list page, paste a complete v3 export with Ctrl+V or Command+V to import it as new. On an edit page, pasting opens a replacement review. Nothing is applied before the origin, methods, credential inheritance, and permissions are shown.
For AI output, prefer the authoring bundle format: a JSON block containing `"template": "__HOMARR_TEMPLATE__"`, followed by a JSX block. This keeps multiline JSX readable and avoids JSON escape errors.
## Save behavior
Errors block save; warnings do not. Server validation runs again on every create or update. Leaving with dirty changes triggers protection. Secrets omitted during edit remain unchanged, and secrets are never exported.
@@ -0,0 +1,49 @@
---
sidebar_label: Custom JSX language
---
# Custom JSX language
Custom JSX is a declarative expression language, not browser JavaScript. Homarr parses an explicit AST subset and emits React elements from a classified component registry. Templates cannot access browser globals, native prototypes, constructors, refs, portals, raw event handlers, or direct `fetch`.
## Data and rendering
The base GET response is available as `data`. Safe member access, conditionals, arrays, object literals, and interpreter-created collection callbacks can compose displays:
```jsx
<Stack gap="xs">
{data.services
.filter((service) => service.visible)
.map((service) => (
<Group key={service.id} justify="space-between">
<Text>{service.name}</Text>
<Badge color={service.online ? "green" : "red"}>{service.status}</Badge>
</Group>
))}
</Stack>
```
Properties are resolved own-only. Reflective names such as `constructor`, `prototype`, `__proto__`, `caller`, `callee`, `arguments`, `bind`, `call`, and `apply` are rejected even when computed or Unicode escaped.
## Components and props
Every Mantine export is classified as allowed, wrapped, or denied. The [generated component reference](./#component-reference) is authoritative. Props are allowlisted per component; unsupported props produce warnings and are ignored. Polymorphic roots, unsafe URLs, portals, positioning escapes, refs, and function props are stripped.
`SubData` is a convenience for simple response paths. Prefer the render-child pattern for rich nested results:
```jsx
<SubFetch requestId="detail" params={{ id: data.selectedId }}>
{(detail, meta) => (
<Stack>
<Title>{detail.name}</Title>
<Text>HTTP {meta.status}</Text>
</Stack>
)}
</SubFetch>
```
## Safe helpers and budgets
Bindings include restricted conversion, string, array, object, JSON, math, URI, and pure date helpers. Native `Date` objects and native prototypes are not exposed.
Templates are limited to 50,000 characters, 10,000 rendered nodes, and explicit AST-depth, operation, collection, and string budgets. Budget errors stop only the Custom JSX renderer and are contained by its error boundary.
@@ -0,0 +1,66 @@
---
sidebar_label: Extension recipes
---
# Custom Widget extension recipes
These recipes keep every authoring and runtime path on the same typed source of truth. Run `pnpm check:custom-widgets` after each structural change and add focused package tests before changing an adapter.
## Add a built-in display type
1. Add the discriminated configuration schema in `@homarr/custom-widgets/core`.
2. Add one descriptor with its icon, category, localized metadata keys, defaults, hydration, serialization, and validation.
3. Add the renderer to the runtime display map. It must consume normalized display data and injected actions only.
4. Add a bounded workbench section that edits the descriptor-owned form fields.
5. Add one fixture and assert create, edit, v2/v3 import, export, MCP, and preview round trips against it.
6. Add translation keys and update the display table in the user guide.
Do not add route-specific conversion code. `buildDisplayFormValues` and `buildDisplayConfigFromFormValues` are the only form conversion boundary.
## Add or update a JSX component
1. Classify the Mantine export as `allowed`, `wrapped`, or `denied` in the component registry. Denied entries require a reason.
2. Declare supported props and subcomponents. Function props, refs, portals, polymorphic roots, and unsafe positioning stay denied unless a wrapper removes the unsafe behavior.
3. Implement wrappers in the JSX package and register them through the package runtime-component factory.
4. Add analyzer and renderer tests for accepted props, stripped props, reflective-property attacks, and the rendered-node budget.
5. Regenerate the docs reference and verify the editor completion and AI prompt include the same entry.
The component-classification test must pass against the locked Mantine version. Never expose an upstream namespace or native prototype as a shortcut.
## Add a named-request capability
1. Extend the request schema and manifest renderer in `core` and `server` without accepting a raw runtime URL, method, header, or body.
2. Enforce permission, network scope, redirect, DNS, header, body, response, concurrency, and rate policies in the framework-neutral server service.
3. Expose infrastructure through a typed server port. Translate domain errors only in the API adapter.
4. Add the control to runtime through the injected request port. Queries use query semantics; actions require a gesture and use mutation semantics.
5. Add preview simulation and redacted-journal coverage. Live actions remain an explicit administrator-only browser choice.
6. Update MCP validation and simulation tools without exposing stored secrets or unrestricted action execution.
Tests must prove actions are never mounted automatically, polled, retried, cached, or redirected. DELETE must retain full permission and confirmation.
## Add a workbench feature
1. Put reusable state, analysis, or presentation in `@homarr/custom-widgets/workbench`.
2. Receive localized messages and typed callbacks as props. The package cannot import the tRPC client, Next.js navigation, notifications, or Homarr widget adapters.
3. Keep the Next.js component as a transport and page-shell adapter.
4. Cover keyboard operation, focus order, reduced motion, 320/768/desktop layouts, dirty-state behavior, and error-boundary recovery.
5. Keep each production module below 300 lines and preserve the continuous two-pane form behavior.
## Change examples or AI authoring
Examples live as typed shared fixtures. Tests, snippets, documentation data, and the AI prompt consume those fixtures. AI output uses an authoring bundle: a JSON block with `__HOMARR_TEMPLATE__`, followed by a readable multiline JSX block. Do not require agents to escape an entire template into one JSON string.
After changing a fixture, validate every example, render it, regenerate the component reference, and run the prompt snapshot tests. MCP agents must be able to read a widget, patch template lines with a revision, validate, preview named queries, simulate actions, inspect the journal, and update the definition without receiving secret values.
## Required checks
```bash
pnpm check:custom-widgets
pnpm test
pnpm test:e2e
pnpm turbo typecheck --affected
pnpm turbo lint --affected
pnpm turbo build --filter=@homarr/docs
```
Also run the component-classification, generated-reference drift, bundle-budget, accessibility, and focused package/API suites configured by the repository. A change is incomplete if any create, edit, import, export, MCP, preview, or board-runtime path uses a separate transform.
+148 -152
View File
@@ -6,203 +6,199 @@ tags:
- JSX
---
import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-component-reference";
# Custom Widgets
:::warning
Custom widgets connect a dashboard item to an HTTP API without compiling a new Homarr widget. Administrators choose the endpoint and authentication, then use a built-in display type or a Custom JSX template to render the response.
Custom widgets are a new feature. Share feedback, ask questions, and discuss ideas in our [GitHub Discussions](https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets).
## Guides
- [Authoring workbench](./authoring): create, edit, preview, paste, undo, import, and export.
- [Custom JSX language](./custom-jsx): supported syntax, components, bindings, diagnostics, and limits.
- [Named requests and security](./requests-and-security): permissions, network scopes, request manifests, and server protections.
- [AI and MCP authoring](./agent-authoring): an iterative agent workflow that validates and previews before saving.
- [Compatibility and migration](./migration): v1/v2 JSX and v2/v3 import behavior.
- [Troubleshooting](./troubleshooting): common editor, preview, permission, and networking failures.
- [Maintainer architecture](./architecture): package boundaries, extension points, and required tests.
- [Extension recipes](./extension-recipes): step-by-step changes for displays, JSX components, requests, and workbench features.
:::caution
A custom widget can expose data or perform actions with the credentials configured for it. Import definitions only from sources you trust and review every network capability before using the widget on a shared board.
:::
Custom widgets allow you to fetch data from any HTTP API and display it on your Homarr dashboard. You define the API endpoint, authentication, and how the response should be rendered - no code compilation or server restart required.
## Create a widget
## Creating a Custom Widget
Open **Management → Custom Widgets**, select **Add**, and configure:
To create a custom widget, navigate to the **Custom Widgets** section in the management sidebar and click the **Add** button. Fill in the configuration:
- **Name, description, and icon**: identify the widget in management and the widget picker.
- **URL**: the full HTTP or HTTPS base endpoint.
- **Authentication**: none, bearer token, basic authentication, API-key header, or API-key query parameter.
- **Display type**: a built-in renderer or Custom JSX.
- **Name** - Display name shown in the widget picker and on the dashboard
- **Description** - Optional description shown in tooltips and management UI
- **Icon URL** - Optional URL to an icon image for the widget
- **URL** - The API endpoint to fetch data from (must include protocol, e.g. `https://api.example.com/stats`)
- **Authentication** - How the API request is authenticated:
- `none` - Public APIs with no authentication
- `bearer` - Bearer token authentication
- `basic` - HTTP Basic auth (username + password)
- `apiKeyHeader` - API key sent as a custom HTTP header
- `apiKeyQuery` - API key sent as a query parameter
- **Header Name** - Custom header or query parameter name for API key auth (e.g. `X-API-Key`)
- **HTTP Method** - `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`
- **Request Body** - JSON string body for `POST`/`PUT`/`PATCH` requests
- **Display Type** - How the API response is rendered (see below)
Use **Test** to run the current GET request. The preview workbench separates the rendered widget, sanitized response tree, redacted request journal, and diagnostics. It marks sample data as stale whenever the connection or display configuration changes after a test.
## Display Types
Testing creates a five-minute, administrator-bound preview session for unsaved named queries. Action controls are simulated by default. An administrator can explicitly enable live actions for that session; DELETE still requires confirmation.
| Display Type | Description | Best For |
| -------------------- | ---------------------------------------------------- | ---------------------------------------- |
| **Single Value** | Extracts a single value with a label and unit | Metrics: temperature, uptime, user count |
| **Key Value** | Multiple labeled key-value pairs with optional units | System stats: CPU, memory, disk |
| **Table** | API array data rendered as a sortable table | Lists of items with columns |
| **Stat Grid** | Labeled values in colored stat cards | Counts: movies, series, albums |
| **Progress Bars** | Values as progress bars with percentages | Usage: disk, quota, capacity |
| **Status Indicator** | Green/red status dots for health checks | Service monitoring |
| **Count Grid** | Simple counts in a grid (no colors) | Library counts |
| **Raw** | Pretty-prints the raw JSON response | Debugging |
| **Action Button** | Clickable button executing the API request | Triggers: restart, clear cache |
| **Custom JSX** | Custom JSX template with Mantine v9 components | Full layout control |
## Display types
### AI-Powered JSX Generation
| Display type | Use it for |
| ---------------- | --------------------------------------------------------- |
| Single Value | One metric with a label and unit |
| Key Value | Several labeled values |
| Table | Arrays of records |
| Stat Grid | Compact colored metrics |
| Progress Bars | Capacity, quota, and completion |
| Status Indicator | Health and availability |
| Count Grid | Compact counters |
| Raw | Sanitized JSON inspection |
| Action Button | One explicit API action |
| Custom JSX | A declarative Mantine layout with optional named requests |
When editing or creating a custom widget with the **Custom JSX** display type, you'll find a **Copy Prompt** button. Clicking it copies a pre-formatted prompt describing your widget's name, API endpoint, and available Mantine components. Paste this into your preferred AI assistant (ChatGPT, Claude, Copilot, etc.) to generate a JSX template tailored to your data.
## Custom JSX workbench
<details>
<summary>Example: Pokédex widget using Custom JSX</summary>
The JSX editor provides syntax highlighting, formatting, undo/redo buttons and Ctrl/Cmd+Z shortcuts, a searchable component reference, character and cursor status, and diagnostics. Errors block saving; warnings identify behavior that still receives server-side validation. Templates can contain up to 50,000 characters and render up to 10,000 nodes.
The template reads the base GET response from `data`:
```jsx
<Stack gap="xs" p="sm">
<Text fw={600}>{data.name}</Text>
<Badge color={data.online ? "green" : "red"}>{data.status}</Badge>
</Stack>
```
The **Copy AI prompt** action is available on desktop and mobile. Its prompt, component list, and examples are generated from the same metadata used by the renderer, so the reference stays aligned with the installed Homarr version.
### JSX API versions
- **Version 2** is the default. Network access is declared in a named-request manifest and enforced by the server.
- **Version 1** remains available for display-only templates. Inline network components are disabled until the definition is reviewed and migrated.
Definitions without `jsxApiVersion` are treated as version 1. Display-only templates can be migrated by selecting version 2 and saving an empty request list.
## Named requests
Version 2 templates never choose an arbitrary URL, method, header, or body at runtime. An administrator declares each allowed operation once, and the template references its `requestId` with typed parameters.
```json
{
"$schema": "homarr-custom-widget-v2",
"name": "Pokédex",
"description": "Browseable Pokémon list",
"iconUrl": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/poke-ball.png",
"url": "https://pokeapi.co/api/v2/pokemon?limit=75",
"authType": "none",
"method": "GET",
"displayType": "customJsx",
"displayConfig": {
"type": "customJsx",
"template": "<Stack gap=\"md\" p=\"xs\">\n <Card withBorder radius=\"xl\" p=\"md\" shadow=\"md\">\n <Group justify=\"space-between\">\n <Title order={3}>Pokédex</Title>\n </Group>\n </Card>\n <PaginatedList pageSize={12}>\n <Grid gutter=\"sm\">\n {data.results.map((pokemon, i) =>\n <Grid.Col span={1.5}>\n <Anchor href={pokemon.url} target=\"_blank\" underline=\"never\">\n <Card withBorder radius=\"xl\" p=\"xs\" shadow=\"md\">\n <Stack gap=\"sm\" align=\"center\">\n <Group justify=\"space-between\" style={{ width: \"100%\" }}>\n <Badge size=\"sm\" color=\"red\">#{i + 1}</Badge>\n <Text fw={800} tt=\"capitalize\">{pokemon.name}</Text>\n </Group>\n <Avatar\n src={\"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/\" + (i + 1) + \".png\"}\n size={92}\n radius=\"xl\"\n />\n </Stack>\n </Card>\n </Anchor>\n </Grid.Col>\n )}\n </Grid>\n </PaginatedList>\n</Stack>"
}
"type": "customJsx",
"jsxApiVersion": 2,
"networkScope": "public",
"template": "<SubFetch requestId=\"service-detail\" params={{ id: data.id }}><SubData path=\"status\" as=\"Badge\" /></SubFetch>",
"requests": [
{
"id": "service-detail",
"kind": "query",
"method": "GET",
"pathTemplate": "/api/services/{id}",
"parameters": { "id": "string" },
"auth": "inherit",
"minimumBoardPermission": "view",
"cacheTtlSeconds": 30
}
]
}
```
</details>
Path placeholders use `{parameter}` and are URL-encoded by the server. JSON body placeholders use `{ "$param": "parameter" }` and preserve the declared string, number, or boolean type.
## Importing and Exporting
`SubData` remains available for simple paths. For richer nested output, pass an interpreter-created render child; it receives the response data and safe status metadata:
Custom widgets can be exported and imported as JSON files. To export, select the widget in the list and click the export button. To import, click the import button and paste the JSON. Secrets (API keys, passwords) are never included in exports and must be configured after import.
```jsx
<SubFetch requestId="service-detail" params={{ id: data.id }}>
{(detail, meta) => (
<Stack gap="xs">
<Badge color={detail.online ? "green" : "red"}>{detail.status}</Badge>
<Text size="xs">HTTP {meta.status}</Text>
</Stack>
)}
</SubFetch>
```
The import format uses `"$schema": "homarr-custom-widget-v2"` to identify the widget version.
| Component | Request kind | Behavior |
| --------------- | ------------ | ------------------------------------------------------------- |
| `SubFetch` | `query` | Loads a named GET request automatically or manually |
| `ActionButton` | `action` | Runs a named POST, PUT, PATCH, or DELETE after a user gesture |
| `ToggleSwitch` | `action` | Passes declared on/off parameters and rolls back on failure |
| `RefreshButton` | None | Refreshes the parent GET response |
## Available Mantine Components for Custom JSX
Queries require board **view** permission. POST, PUT, and PATCH actions require **modify** by default. DELETE requires **full** permission and confirmation. Allowing viewers to execute a non-DELETE action requires explicit administrator approval. Anonymous viewers cannot execute actions.
When using the `customJsx` display type, your template has access to a whitelist of [Mantine v9](https://mantine.dev) components. Unrecognized components, event handlers, and dangerous tags are stripped for security.
### Network scopes
### Layout
| Scope | Allowed destinations |
| ---------- | ------------------------------------------------------------ |
| `public` | Public IP addresses only |
| `private` | Public addresses plus RFC1918 and IPv6 ULA private addresses |
| `loopback` | Public, private, and loopback addresses |
| Component | Doc |
| ------------------- | --------------------------------------------- |
| `Stack` | [docs](https://mantine.dev/core/stack) |
| `Group` | [docs](https://mantine.dev/core/group) |
| `Flex` | [docs](https://mantine.dev/core/flex) |
| `Grid` / `Grid.Col` | [docs](https://mantine.dev/core/grid) |
| `SimpleGrid` | [docs](https://mantine.dev/core/simple-grid) |
| `Center` | [docs](https://mantine.dev/core/center) |
| `Container` | [docs](https://mantine.dev/core/container) |
| `Space` | [docs](https://mantine.dev/core/space) |
| `AspectRatio` | [docs](https://mantine.dev/core/aspect-ratio) |
Link-local, cloud-metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped bypass addresses remain blocked in every scope.
### Typography & Content
### Example with a named query
| Component | Doc |
| ----------------- | ------------------------------------------------- |
| `Text` | [docs](https://mantine.dev/core/text) |
| `Title` | [docs](https://mantine.dev/core/title) |
| `Code` | [docs](https://mantine.dev/core/code) |
| `Highlight` | [docs](https://mantine.dev/core/highlight) |
| `Mark` | [docs](https://mantine.dev/core/mark) |
| `Kbd` | [docs](https://mantine.dev/core/kbd) |
| `Blockquote` | [docs](https://mantine.dev/core/blockquote) |
| `Anchor` | [docs](https://mantine.dev/core/anchor) |
| `NumberFormatter` | [docs](https://mantine.dev/core/number-formatter) |
The editor and AI prompt include a shared Pokédex fixture that loads details only for the visible page. Its named request is equivalent to:
### Data Display
```json
{
"id": "pokemon-detail",
"kind": "query",
"method": "GET",
"pathTemplate": "/api/v2/pokemon/{name}",
"parameters": { "name": "string" },
"auth": "none",
"minimumBoardPermission": "view",
"cacheTtlSeconds": 300
}
```
| Component | Doc |
| ------------------------- | ------------------------------------------------- |
| `Badge` | [docs](https://mantine.dev/core/badge) |
| `Card` / `Card.Section` | [docs](https://mantine.dev/core/card) |
| `Paper` | [docs](https://mantine.dev/core/paper) |
| `Alert` | [docs](https://mantine.dev/core/alert) |
| `ThemeIcon` | [docs](https://mantine.dev/core/theme-icon) |
| `ColorSwatch` | [docs](https://mantine.dev/core/color-swatch) |
| `Avatar` / `Avatar.Group` | [docs](https://mantine.dev/core/avatar) |
| `Image` | [docs](https://mantine.dev/core/image) |
| `BackgroundImage` | [docs](https://mantine.dev/core/background-image) |
| `Indicator` | [docs](https://mantine.dev/core/indicator) |
| `Pill` | [docs](https://mantine.dev/core/pill) |
| `Spoiler` | [docs](https://mantine.dev/core/spoiler) |
## Agent authoring through MCP
### Tables
Administrators can use Homarr's MCP server to iterate on custom widgets without bypassing validation or network controls. A typical agent workflow is:
| Component | Doc |
| --------------------------------------- | -------------------------------------- |
| `Table` / `Table.Thead` / `Table.Tbody` | [docs](https://mantine.dev/core/table) |
| `Table.Tr` / `Table.Th` / `Table.Td` | [docs](https://mantine.dev/core/table) |
1. Call `customWidget_schema` to inspect the current import format.
2. Generate a complete `homarr-custom-widget-v3` draft.
3. Call `customWidget_validate` and correct every returned issue.
4. Call `customWidget_preview` to test the base GET request through Homarr's hardened executor.
5. Use `customWidget_previewQuery` for named queries and `customWidget_simulatePreviewAction` to verify action parameters without sending mutations.
6. Inspect `customWidget_previewJournal`, then validate again.
7. Call `customWidget_import`, or read an existing widget with `customWidget_byId` and apply a focused `customWidget_update`.
### Lists
MCP preview actions are simulation-only. Enabling live preview actions remains an explicit administrator choice in the browser workbench. Stored secret values are never returned by MCP; omit `secrets` when updating an existing widget to preserve them.
| Component | Doc |
| ---------------------------- | ----------------------------------------- |
| `List` / `List.Item` | [docs](https://mantine.dev/core/list) |
| `Timeline` / `Timeline.Item` | [docs](https://mantine.dev/core/timeline) |
## Component reference
### Feedback & Loading
Custom JSX exposes a classified subset of Mantine Core, Charts, Dates, and Homarr components. Allowed components render directly. Wrapped components receive safe props or read-only behavior. Denied components remain in the registry so dependency upgrades cannot silently expand the template surface.
| Component | Doc |
| ------------------------------- | ---------------------------------------------- |
| `Progress` / `Progress.Section` | [docs](https://mantine.dev/core/progress) |
| `RingProgress` | [docs](https://mantine.dev/core/ring-progress) |
| `Skeleton` | [docs](https://mantine.dev/core/skeleton) |
| `Loader` | [docs](https://mantine.dev/core/loader) |
<CustomJsxComponentReference />
### Navigation & Overlay
This table and the searchable component list in the editor are generated from Homarr's runtime registry.
| Component | Doc |
| ------------------------------------------------------------------------ | -------------------------------------------- |
| `TabsContainer` / `TabPanel` | Custom interactive component |
| `Accordion` / `Accordion.Item` / `Accordion.Control` / `Accordion.Panel` | [docs](https://mantine.dev/core/accordion) |
| `ScrollArea` | [docs](https://mantine.dev/core/scroll-area) |
| `Tooltip` | [docs](https://mantine.dev/core/tooltip) |
Component props are filtered. Polymorphic roots, portals, refs, unsafe positioning, raw event handlers, and `dangerouslySetInnerHTML` are not exposed. Direct browser globals and direct `fetch` calls are unavailable, while ordinary text containing the word “fetch” is valid.
### Charts
Safe bindings include conversion helpers, a restricted `Math` and `JSON` surface, object and array helpers, URI encoding helpers, and pure date formatting helpers. Collection callbacks and rendered output are subject to interpreter budgets.
| Component | Doc |
| ---------------- | --------------------------------------------------- |
| `AreaChart` | [docs](https://mantine.dev/charts/area-chart) |
| `BarChart` | [docs](https://mantine.dev/charts/bar-chart) |
| `LineChart` | [docs](https://mantine.dev/charts/line-chart) |
| `DonutChart` | [docs](https://mantine.dev/charts/donut-chart) |
| `PieChart` | [docs](https://mantine.dev/charts/pie-chart) |
| `RadarChart` | [docs](https://mantine.dev/charts/radar-chart) |
| `RadialBarChart` | [docs](https://mantine.dev/charts/radial-bar-chart) |
| `Sparkline` | [docs](https://mantine.dev/charts/sparkline) |
## HTTP protections and limits
### Interactive Components
All base, preview, query, and action requests use the same server executor:
| Component | Description |
| --------------- | ---------------------------------------- |
| `PaginatedList` | Client-side pagination wrapper |
| `Collapsible` | Expandable/collapsible section |
| `StatBar` | Horizontal stat bar |
| `TypeBadge` | Type-styled badge |
| `Divider` | [docs](https://mantine.dev/core/divider) |
- Only HTTP and HTTPS are accepted. URL credentials and fragments are rejected.
- Named paths must stay on the base endpoint origin.
- DNS results are validated against the selected network scope and pinned for the request.
- Queries can follow up to three revalidated same-origin redirects. Actions do not follow redirects.
- Authorization, cookie, host, forwarding, proxy, content-length, and hop-by-hop headers cannot be supplied by a template.
- Request bodies are limited to 10 KiB. Decompressed responses are limited to 1 MiB, 32 JSON levels, and 50,000 JSON nodes.
- GET requests can be cached and deduplicated. Actions are never cached, polled, or retried automatically.
## Template Bindings
## Import and export
Your template receives:
New exports use `"$schema": "homarr-custom-widget-v3"`. Version 2 imports remain accepted. Secrets are never exported and must be entered again after import.
- `data` - The sanitized API response object. Access fields via `data.fieldName` or `data.results[0].name`.
- `String(v)` - Type-safe string conversion
- `Number(v)` - Type-safe number conversion
- `Boolean(v)` - Type-safe boolean conversion
- `Math` - Safe subset: `round`, `floor`, `ceil`, `abs`, `min`, `max`, `pow`, `sqrt`, `PI`
- `JSON.stringify(v)` - JSON serialization
- `Array.isArray(v)` - Array type check
- `Object.keys(v)`, `Object.values(v)`, `Object.entries(v)` - Object utilities
Review the base origin, network scope, named request methods, inherited authentication, and required permissions before saving an imported widget. A version 1 template that contains inline network components stays disabled until it is converted to named requests.
## Security Constraints
AI-generated Custom JSX can also be pasted as a Homarr authoring bundle: one fenced JSON block with `"template": "__HOMARR_TEMPLATE__"`, followed by one fenced JSX block containing the readable multiline template. Homarr combines the blocks before validation, avoiding JSON newline and quote escaping. Raw JSON imports remain supported.
- Event handlers (`onClick`, `onChange`, etc.) are stripped
- Dangerous HTML attributes (`dangerouslySetInnerHTML`) are stripped
- `script`, `iframe`, `object`, `embed`, `form`, `style`, `link`, `meta`, `base` tags are forbidden
- Template strings containing `eval`, `Function`, `import`, `require`, `globalThis`, `window`, `document`, `fetch` are rejected
- Anchor `href` attributes are validated to only allow `https://`, relative (`/`), and hash (`#`) URLs
On the custom-widget list, paste a complete export with Ctrl+V or Command+V to open the import review and create it as a new definition. On an edit page, the same shortcut opens a replacement review and fills the existing form after confirmation. Pasted definitions never include secrets.
@@ -0,0 +1,19 @@
---
sidebar_label: Compatibility and migration
---
# Compatibility and migration
Homarr preserves existing display definitions while moving network-enabled Custom JSX to the named-request model.
## JSX API versions
Missing `jsxApiVersion` means v1. Display-only v1 templates can be opened and saved as v2 with an empty request manifest. Inline `url`, `method`, `headers`, and body props are inert in v2.
Static v1 network components can be converted by creating a named request and replacing inline capability props with `requestId` and typed `params`. Dynamic target construction requires an administrator to design an explicit path template and parameter list. Existing non-GET base definitions stop polling and show a network-review state.
## Import versions
Exports use `homarr-custom-widget-v3`; v2 imports remain accepted. Import migration normalizes display configuration, preserves v1 semantics, and never imports secret values. The review screen must be accepted before persistence.
Test migration with the same base response and compare rendered output, query results, action simulation, permissions, and journal entries before enabling the migrated definition on shared boards.
@@ -0,0 +1,32 @@
---
sidebar_label: Requests and security
---
# Named requests and security
Custom JSX v2 uses a saved request manifest. Runtime components pass only a `requestId` and declared primitive parameters; they cannot change the URL, method, headers, authentication, or body shape.
## Capability kinds
`SubFetch` references a GET `query`. `ActionButton` and `ToggleSwitch` reference a POST, PUT, PATCH, or DELETE `action`. Base data is GET-only. Actions require an explicit gesture and are never polled, cached, retried automatically, or run on mount.
Default board permissions are view for queries, modify for POST/PUT/PATCH, and full for DELETE. Viewer actions require explicit high-risk approval; anonymous users cannot execute actions. DELETE always requires confirmation.
## Parameter substitution
Path placeholders use `{name}` and are URL-encoded by the server. Runtime parameters must exactly match all declared names and types. JSON bodies substitute `{ "$param": "name" }` recursively and preserve string, number, or boolean types.
## Network boundary
Named paths stay on the base URL origin. HTTP and HTTPS are accepted; URL credentials and fragments are rejected. DNS answers are classified and pinned. Public, private, and loopback scopes progressively allow destinations, but link-local, metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped addresses are always blocked.
Queries may follow three revalidated same-origin redirects. Actions do not follow redirects. Credentials never cross origins. Reserved authentication, cookie, host, forwarding, proxy, content-length, security, and hop-by-hop headers cannot be declared.
## Limits
- Request body: 10 KiB UTF-8.
- Decompressed response: 1 MiB, 32 JSON levels, 50,000 JSON nodes.
- Per user/item: 60 queries, 10 actions, and 3 deletes per minute.
- Concurrency: 4 per user/item and 8 per definition.
Successful GETs can use manifest cache TTLs and single-flight deduplication. Logs contain redacted origin/method/error metadata, never secrets, request bodies, or query values.
@@ -0,0 +1,33 @@
---
sidebar_label: Troubleshooting
---
# Troubleshooting
## The preview says the template is required
New Custom JSX forms always include the starter fixture. If an imported definition has an empty template, reopen its import review; malformed imports must be corrected rather than previewed as JSX.
## A prop is ignored
The diagnostics name the component and prop. Check the generated component reference. Unsupported props are intentionally removed; use a supported layout primitive or wrapper prop instead of polymorphic, portal, ref, positioning, or raw handler props.
## A request does not run
Confirm that the component kind matches the manifest: `SubFetch` needs a query; action controls need an action. Parameter names and primitive types must match exactly. Base requests and queries require GET. Actions only run from a gesture, and edit mode disables controls.
## Network scope errors
Resolve the hostname from the Homarr server environment. Select private or loopback scope only when required and understood. Link-local and metadata destinations cannot be enabled by any scope. Redirects must remain on the base origin.
## Permission errors
The placed board item is the authorization anchor. A definition ID alone cannot authorize credential use. Verify board view/modify/full access, explicit viewer-action approval, and that the caller is not anonymous.
## Stale or failed preview data
Connection, authentication, body, method, secret, or manifest changes mark samples stale. Run the GET test again. Background query failures keep prior content visible and offer retry; actions roll toggles back and show error feedback.
## Import paste is ignored
Paste a complete v2/v3 JSON export or authoring bundle while the list or edit page is focused. Ordinary text and partial JSON are ignored so normal typing is not intercepted.
+79 -24
View File
@@ -73,13 +73,7 @@ Replace `<your-api-key>` with your actual API key. Never commit API keys to vers
"mcpServers": {
"homarr": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://your-homarr-instance.com/api/mcp/mcp",
"--header",
"ApiKey:<your-api-key>"
]
"args": ["-y", "mcp-remote", "https://your-homarr-instance.com/api/mcp/mcp", "--header", "ApiKey:<your-api-key>"]
}
}
}
@@ -126,23 +120,26 @@ curl -s -X POST \
Homarr exposes 50+ tools organized by namespace. Each tool is either a **query** (GET, read-only) or **mutation** (POST, write action).
| Namespace | Example Tools | Type |
| ------------------ | -------------------------------------------------------- | ---------- |
| `app` | List all apps, get app by ID, create/update/delete app | GET + POST |
| `apiKeys` | List API keys | GET |
| `board` | List boards, get board by ID, create/update/delete board | GET + POST |
| `docker` | List containers, inspect container, start/stop/restart | GET + POST |
| `icon` | Search icons | GET |
| `info` | Server health, version info | GET |
| `integration` | List integrations, test connection, delete integration | GET + POST |
| `invite` | List invitations, delete invite | GET + POST |
| `calendar` | Calendar events from media servers | GET |
| `dnsHole` | DNS hole summary, toggle blocking | GET + POST |
| `downloads` | Active downloads, pause/resume/delete | GET + POST |
| `healthMonitoring` | Resource usage stats | GET |
| `mediaRequests` | Approve/decline media requests | GET + POST |
| `mediaServer` | Media server sessions and stats | GET |
| `smartHome` | Execute automations, device states | GET + POST |
| Namespace | Example Tools | Type |
| ------------------ | --------------------------------------------------------- | ---------- |
| `app` | List all apps, get app by ID, create/update/delete app | GET + POST |
| `apiKeys` | List API keys | GET |
| `beszel` | Systems status, alerts, historical system metrics | GET |
| `board` | List boards, get board by ID, create/update/delete board | GET + POST |
| `docker` | List containers, inspect container, start/stop/restart | GET + POST |
| `icon` | Search icons | GET |
| `info` | Server health, version info | GET |
| `integration` | List integrations, test connection, delete integration | GET + POST |
| `invite` | List invitations, delete invite | GET + POST |
| `calendar` | Calendar events from media servers | GET |
| `customWidget` | Validate, preview, create, and iteratively update widgets | GET + POST |
| `dnsHole` | DNS hole summary, toggle blocking | GET + POST |
| `downloads` | Active downloads, pause/resume/delete | GET + POST |
| `healthMonitoring` | Resource usage stats | GET |
| `patchmon` | Host patch and security update statistics | GET |
| `mediaRequests` | Approve/decline media requests | GET + POST |
| `mediaServer` | Media server sessions and stats | GET |
| `smartHome` | Execute automations, device states | GET + POST |
The tools table on the MCP tab is dynamically generated - it always reflects the latest available procedures from your Homarr instance.
@@ -152,6 +149,8 @@ The tools table on the MCP tab is dynamically generated - it always reflects the
- Tools inherit the permissions of the API key's owner
- OAuth endpoints are rate-limited to prevent abuse
- Tool execution errors are contained - a failing tool call does not crash the MCP server
- Custom-widget MCP actions are simulation-only; live preview actions must be enabled explicitly in the browser workbench
- Custom-widget secret values are never returned through MCP
- All communication uses HTTPS (configure HTTP → HTTPS redirects on your reverse proxy)
## Use Cases
@@ -162,3 +161,59 @@ The tools table on the MCP tab is dynamically generated - it always reflects the
- **"Create a new app pointing to my Grafana instance"** - AI creates an app with the correct icon and URL
- **"How many users are on my Homarr instance?"** - AI lists all users (admin only)
- **"Toggle Pi-hole blocking for 5 minutes"** - AI interacts with DNS controls
- **"How are my servers doing?"** - AI fetches Beszel system stats including CPU, memory, disk, and alerts
- **"Build and refine a custom widget for this API"** - AI validates the draft, tests GET data and named queries, simulates actions, and creates or updates the widget
## Custom Widget Workflow for AI Agents
AI agents can create and iterate on custom widgets using these tools in sequence.
:::tip Editing an existing widget?
Skip to step 5. Use `customWidget_byId` to fetch it and `customWidget_readTemplate` / `customWidget_writeTemplate` to edit its template directly.
:::
### 1. Get the schema
Call `customWidget_schema` to receive the JSON Schema for the `homarr-custom-widget-v3` format. This tells the agent what fields are required and their types.
### 2. Draft and validate
Build a widget JSON object and call `customWidget_validate` with it. The tool returns structured issues (path + message) so the agent can fix and re-validate until the draft is clean.
### 3. Create the widget
Call `customWidget_import` with the validated `homarr-custom-widget-v3` draft. This returns the new widget `id`. Use `customWidget_create` only when you intentionally need its lower-level creation input, including secret fields.
### 4. Test with real data
Call `customWidget_preview` with the widget's connection info. This makes a real GET request through Homarr's hardened executor and returns the sanitized response plus a preview session ID.
For Custom JSX v2 widgets with named requests, use `customWidget_previewQuery` to test each named GET query, and `customWidget_simulatePreviewAction` to dry-run actions without network calls.
### 5. Iterate on the template
Use `customWidget_readTemplate` to get the current JSX template as plain text, then `customWidget_writeTemplate` to push updates. The write tool accepts either:
- `template` — a single string (newlines as `\n`)
- `templateLines` — an array of strings (joined with newlines server-side, avoids `\n` escaping)
Both options validate the template AST before saving. This avoids re-sending the full widget JSON for template-only edits.
For small changes, prefer `customWidget_patchTemplate`. Pass the `revision` returned by `customWidget_readTemplate` and one or more line edits. Homarr rejects stale revisions, applies all edits atomically, and validates the complete template against its named request manifest before saving.
### 6. Update non-template fields
Call `customWidget_byId` to get the full widget, modify fields, and call `customWidget_update` with only the changed fields.
### Available components in templates
Custom JSX templates support 100+ Mantine components plus:
- `<TablerIcon name="server" size={20} color="blue" />` — 100+ curated Tabler icons by kebab-case name (e.g. `"player-play"`, `"alert-triangle"`)
- `<SubFetch>` / `<ActionButton>` / `<ToggleSwitch>` — network-capable components for named requests
- Array methods: `map`, `filter`, `find`, `findIndex`, `some`, `every`, `reduce`, `sort`, `slice`, `flat`, `reverse`, `at`, `pop`, `join`, `includes`, `indexOf` (note: `pop` returns last element without mutating)
- String methods: `toUpperCase`, `toLowerCase`, `trim`, `trimStart`, `trimEnd`, `slice`, `substring`, `includes`, `startsWith`, `endsWith`, `split`, `replace`, `replaceAll`, `repeat`, `padStart`, `padEnd`, `charAt`, `indexOf`, `lastIndexOf`
:::note
All `customWidget_*` tools require admin-level API key permissions. The preview executor enforces rate limits (60 queries/min, 10 actions/min).
:::
@@ -1,5 +0,0 @@
{
"label": "Tools",
"collapsed": true,
"position": 10
}
@@ -1,66 +0,0 @@
---
title: SQLite Backup & Restore
tags:
- Management
- Backup
- Restore
- SQLite
---
The SQLite Backup & Restore feature allows you to export and import your entire Homarr database as a portable ZIP file. This is useful for creating backups, migrating between instances, or recovering from failures.
:::info SQLite only
This feature is only available when using the SQLite database driver (`DB_DRIVER=better-sqlite3`). MySQL and PostgreSQL users should use their respective database backup tools.
:::
## Accessing Backup & Restore
Navigate to **Management → Tools → Backup** from the sidebar. This page is only visible when running with SQLite.
## Exporting a Backup
Click the **Export** button to download a ZIP archive containing:
- **`db.sqlite`** — A consistent snapshot of your database (created via `VACUUM INTO`)
- **`metadata.json`** — Contains:
- `homarrVersion` — The version of Homarr that created the backup
- `exportedAt` — Timestamp of the export
- `dbDialect` — The database dialect (`sqlite`)
- `encryptionKey` — Your `SECRET_ENCRYPTION_KEY` (needed to decrypt integration secrets)
:::caution
The exported ZIP contains your encryption key and database. Store it securely and do not share it publicly.
:::
A confirmation dialog will ask you to type **"I understand"** before the export proceeds.
## Importing a Backup
1. Click the **Import** button and upload a previously exported ZIP file
2. Homarr analyzes the backup **entirely in your browser** using WebAssembly (sql.js) — no data is sent to the server during preview
3. The preview shows:
- **Entity counts** — How many boards, apps, users, integrations, etc. are in the backup
- **Board names** — A list of all boards contained in the backup
- **Migration status** — If the backup is from an older version, Homarr shows each pending database migration as animated timeline steps
4. Review the preview and type **"I understand"** to confirm
5. Homarr applies any pending migrations, re-encrypts integration secrets if the `SECRET_ENCRYPTION_KEY` differs between instances, and replaces the live database
6. The server automatically restarts to pick up the new database
:::tip
The import process is **backward-compatible** with older backups that used a separate `encryption-key.txt` file instead of embedding the key in `metadata.json`.
:::
## Onboarding Integration
When setting up a fresh Homarr instance with SQLite, the onboarding wizard includes a **"Restore from SQLite backup"** option. This lets you upload a backup ZIP to bootstrap your new instance with data from a previous installation.
## Docker Restart Behavior
After a successful import, only the Next.js process restarts — nginx and Redis remain running. The `run.sh` script automatically restarts Next.js in a loop, so the application comes back online within seconds. Running `docker stop` still works gracefully.
## Best Practices
- **Back up regularly** — Create exports before major updates or configuration changes
- **Test restores** — Periodically verify your backups by importing them on a test instance
- **Secure storage** — Keep backups in an encrypted or access-controlled location since they contain your encryption key
- **Check versions** — Backups from newer versions cannot be imported into older Homarr instances; always import into the same or newer version
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+41
View File
@@ -0,0 +1,41 @@
---
title: 'Bazarr'
description: 'Displays missing subtitle counts and health indicators from your Bazarr instance.'
hide_title: true
---
import { WidgetHeader } from '@site/src/components/widgets/header';
import { AddingWidget } from '@site/src/components/widgets/adding';
import { WidgetConfig } from '@site/src/components/widgets/configuration';
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
import { bazarrWidget } from '.';
import { bazarrIntegration } from '@site/docs/integrations/bazarr';
<WidgetHeader
widget={bazarrWidget}
categories={['Subtitles', 'Media']}
/>
This widget shows subtitle status from your Bazarr instance, including missing episode and movie subtitles, provider issues, and health warnings.
### Screenshots
import widgetOverview from './img/widget-overview.png';
<div className="flex flex-col gap-2">
<img src={widgetOverview} alt="bazarr-widget-overview" width={128 * 4} />
</div>
### Supported Integrations
<WidgetIntegrations items={[{
integration: bazarrIntegration,
note: "Displays missing subtitle counts and health indicators"
}]} />
### Adding the widget
<AddingWidget />
### Configuration
<WidgetConfig configuration={bazarrWidget.configuration} />
+37
View File
@@ -0,0 +1,37 @@
import { WidgetDefinition } from "@site/src/types";
import { IconBadgeCc } from "@tabler/icons-react";
export const bazarrWidget: WidgetDefinition = {
icon: IconBadgeCc,
name: "Bazarr",
description: "Displays missing subtitle counts and health indicators from your Bazarr instance.",
path: "../../widgets/bazarr",
configuration: {
items: [
{
name: "Show missing episodes",
description: "Displays the number of episodes with missing subtitles",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show missing movies",
description: "Displays the number of movies with missing subtitles",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show provider issues",
description: "Displays the number of subtitle providers with issues",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show health issues",
description: "Displays the number of Bazarr health issues",
values: { type: "boolean" },
defaultValue: "yes",
},
],
},
};
@@ -18,7 +18,9 @@ import beszelSystemGrid from './img/beszel-system-grid.png';
categories={['System Monitoring']}
/>
This widget displays all your Beszel-monitored systems as a responsive card grid. Each card shows real-time metrics like CPU, memory, disk, network, temperature, and more. Cards adapt their layout based on available space.
This widget displays all your Beszel-monitored systems as a responsive card grid. Each card shows real-time metrics like CPU, memory, disk, network, temperature, and more. Cards adapt their layout based on available space. If Beszel reports multiple filesystems, the disk bar exposes a hover breakdown for the root disk and each extra mount.
Select a system card to open the complete statistics view, including system, filesystem, Docker, historical, and realtime charts.
### Screenshots
@@ -1,24 +1,21 @@
---
title: 'Beszel System Stats'
description: 'Time-series charts for CPU, memory, disk, network, and Docker container metrics from Beszel.'
title: "Beszel System Stats"
description: "Time-series charts for CPU, memory, disk, network, and Docker container metrics from Beszel."
hide_title: true
---
import { WidgetHeader } from '@site/src/components/widgets/header';
import { AddingWidget } from '@site/src/components/widgets/adding';
import { WidgetConfig } from '@site/src/components/widgets/configuration';
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
import { beszelSystemStatsWidget } from '.';
import { beszelIntegration } from '@site/docs/integrations/beszel';
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetIntegrations } from "@site/src/components/widgets/integrations";
import { beszelSystemStatsWidget } from ".";
import { beszelIntegration } from "@site/docs/integrations/beszel";
import beszelSystemStats from './img/beszel-system-stats.png';
import beszelSystemStats from "./img/beszel-system-stats.png";
<WidgetHeader
widget={beszelSystemStatsWidget}
categories={['System Monitoring', 'Charts']}
/>
<WidgetHeader widget={beszelSystemStatsWidget} categories={["System Monitoring", "Charts"]} />
This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. Data is cached and refreshed periodically. You can select a time period ranging from 1 hour to 30 days.
This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. The Disk Usage chart combines the root disk and every extra filesystem reported by Beszel. You can use the realtime Live view or select a historical period ranging from 1 hour to 30 days. If the live stream drops, the widget now shows the connection failure state immediately and can switch back to historical data.
### Screenshots
@@ -26,14 +23,19 @@ This widget displays time-series charts for a selected Beszel system, including
### Supported Integrations
<WidgetIntegrations items={[{
integration: beszelIntegration,
note: "Displays time-series charts for a selected system"
}]} />
<WidgetIntegrations
items={[
{
integration: beszelIntegration,
note: "Displays time-series charts for a selected system",
},
]}
/>
### Adding the widget
<AddingWidget />
### Configuration
<WidgetConfig configuration={beszelSystemStatsWidget.configuration} />
@@ -8,12 +8,19 @@ export const beszelSystemStatsWidget: WidgetDefinition = {
path: "../../widgets/beszel-system-stats",
configuration: {
items: [
{
name: "System",
description: "The Beszel system to display stats for. Searchable select populated from the integration.",
values: { type: "string" },
defaultValue: "First available system",
},
{
name: "Time period",
description: "The time range for chart data",
description:
"The time range for chart data. '1 Minute' enables live streaming via PocketBase SSE (requires Beszel agent >= 0.13.0)",
values: {
type: "select",
options: ["1 Hour", "12 Hours", "24 Hours", "1 Week", "30 Days"],
options: ["1 Minute", "1 Hour", "12 Hours", "24 Hours", "1 Week", "30 Days"],
},
defaultValue: "1 Hour",
},
@@ -17,7 +17,9 @@ import { beszelIntegration } from '@site/docs/integrations/beszel';
categories={['System Monitoring']}
/>
This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. Columns can be toggled on or off, and the table supports sorting by any column.
This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. Columns can be toggled on or off, and the table supports sorting by any column. When Beszel reports extra filesystems, the disk column adds a hover breakdown for each mount alongside the root disk.
Select a system row to open the complete statistics view, including system, filesystem, Docker, historical, and realtime charts.
### Supported Integrations
+3 -1
View File
@@ -63,7 +63,9 @@ import withoutIntegrations from './img/without-integrations.png';
### Interactions
- View calendar events by clicking on the marked dates.
- Hover over a marked date to preview its events in a popover.
- Click on a marked date to pin the popover open (it will stay visible even after moving the mouse away).
- Click the date again or click outside to close the pinned popover.
### Adding the widget
+27 -17
View File
@@ -1,29 +1,39 @@
---
title: 'Custom API'
description: 'Displays data from a custom API endpoint using configurable display types and optional JSX templates.'
title: "Custom API"
description: "Displays data from an administrator-defined HTTP API using built-in renderers or safe Custom JSX."
hide_title: true
---
import { WidgetHeader } from '@site/src/components/widgets/header';
import { AddingWidget } from '@site/src/components/widgets/adding';
import { customApiWidget } from '.';
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { customApiWidget } from ".";
<WidgetHeader widget={customApiWidget} categories={["Custom", "API"]} />
<WidgetHeader
widget={customApiWidget}
categories={['Custom', 'API']}
/>
The Custom API widget displays an API definition created by an administrator in [Management → Custom Widgets](/docs/management/custom-widgets). A definition owns the endpoint, credentials, display configuration, and any named network capabilities used by Custom JSX.
This widget fetches data from a custom API endpoint defined in [Custom Widgets management](/docs/management/custom-widgets) and renders it using configurable display types.
Custom API widgets are created and configured through the admin management page. Each widget definition specifies the API endpoint, authentication, display type, and refresh interval.
See the [Custom Widgets documentation](/docs/management/custom-widgets) for full details on creating and configuring custom widgets.
### Adding the widget
### Add the widget
<AddingWidget />
Choose a custom-widget definition and a refresh interval. The board item stores only the selected definition and display options; credentials remain server-side.
:::note
Custom API widgets must first be created in **Management → Custom Widgets** before they appear in the widget picker.
A definition must be enabled and available to the board before it appears in the widget picker.
:::
### Permissions and actions
The base response and named query requests require permission to view the board. Named POST, PUT, and PATCH actions require permission to modify the board by default. DELETE requires full board permission and a confirmation. Anonymous viewers cannot execute actions.
Interactive controls are disabled while the board is in edit mode. Actions run only after an explicit user gesture and are never triggered by mounting, refresh polling, or automatic retries. Authoring previews simulate actions unless an administrator explicitly enables live actions for the short-lived preview session.
### Refresh behavior
The refresh interval applies to the base GET response. A Custom JSX query can declare its own cache duration and can be refreshed manually. Named actions can invalidate the parent response or related named queries after they succeed.
If the definition needs a non-GET base request, migrate it to a GET base response plus a named action. Existing non-GET Custom JSX definitions require review before network access is enabled.
See [Custom Widgets management](/docs/management/custom-widgets) for the editor, display types, named-request manifest, network scopes, security limits, and migration guidance.
@@ -1,16 +1,17 @@
import { WidgetDefinition } from "@site/src/types";
import { IconDeviceGamepad } from "@tabler/icons-react";
import { integrations } from "../dns-hole-summary";
export const dnsHoleControlsWidget: WidgetDefinition = {
icon: IconDeviceGamepad,
name: "DNS Hole Controls",
description: "Control PiHole or AdGuard from your dashboard",
description: `Control the blocking feature of your ${integrations} from your dashboard`,
path: "../../widgets/dns-hole-controls",
configuration: {
items: [
{
name: "Show Toggle All Buttons",
description: "Show or not the 2 buttons that may be redundant when having only one DNS-hole.",
description: "Show or not the 2 buttons that may be redundant when having only one DNS Hole.",
values: { type: "boolean" },
defaultValue: "yes",
},
@@ -1,10 +1,12 @@
import { WidgetDefinition } from "@site/src/types";
import { IconAd } from "@tabler/icons-react";
export const integrations = "Pi-hole, AdGuard Home or Technitium DNS";
export const dnsHoleSummaryWidget: WidgetDefinition = {
icon: IconAd,
name: "DNS Hole Summary",
description: "Displays the summary of your DNS Hole",
description: `Displays the summary of your ${integrations} blocks and queries statistics`,
path: "../../widgets/dns-hole-summary",
configuration: {
items: [
@@ -19,7 +21,7 @@ export const dnsHoleSummaryWidget: WidgetDefinition = {
},
{
name: "Use Pi-Hole colors",
description: "Color the tiles using the background color, that is used by default in PiHole.",
description: `Color the tiles using the background color, that is used by default in ${integrations}.`,
values: { type: "boolean" },
defaultValue: "yes",
},
@@ -1,61 +1,71 @@
---
title: 'System Health Monitoring'
description: 'Displays information showing the health and status of your system(s).'
title: "System Health Monitoring"
description: "Displays information showing the health and status of your system(s)."
hide_title: true
---
import { WidgetHeader } from '@site/src/components/widgets/header';
import { AddingWidget } from '@site/src/components/widgets/adding';
import { WidgetConfig } from '@site/src/components/widgets/configuration';
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetIntegrations } from "@site/src/components/widgets/integrations";
import { IconAd } from "@tabler/icons-react";
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { healthMonitoringWidget } from '.';
import { dashDotIntegration } from '@site/docs/integrations/dash-dot';
import { openMediaVaultIntegration } from '@site/docs/integrations/open-media-vault';
import { proxmoxIntegration } from '@site/docs/integrations/proxmox';
import { glancesIntegration } from '@site/docs/integrations/glances';
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import { healthMonitoringWidget } from ".";
import { dashDotIntegration } from "@site/docs/integrations/dash-dot";
import { openMediaVaultIntegration } from "@site/docs/integrations/open-media-vault";
import { proxmoxIntegration } from "@site/docs/integrations/proxmox";
import { glancesIntegration } from "@site/docs/integrations/glances";
<WidgetHeader
widget={healthMonitoringWidget}
categories={['Health monitoring']}
/>
<WidgetHeader widget={healthMonitoringWidget} categories={["Health monitoring"]} />
This widget displays information about the health and status of your system(s), including CPU, memory, disk usage, and more.
### Screenshots
import systemView from './img/system-view.png';
import clusterView from './img/cluster-view.png';
import { truenasIntegration } from '@site/docs/integrations/truenas';
import systemView from "./img/system-view.png";
import clusterView from "./img/cluster-view.png";
import { truenasIntegration } from "@site/docs/integrations/truenas";
import { synologyIntegration } from "@site/docs/integrations/synology";
<img src={systemView} alt="health-monitoring system-view" width={256} height={256} />
<img src={clusterView} alt="health-monitoring cluster-view" width={256} height={256} />
### Supported Integrations
<WidgetIntegrations items={[{
integration: dashDotIntegration,
note: "Will display information on the system tab"
}, {
integration: openMediaVaultIntegration,
note: "Will display information on the system tab"
}, {
integration: truenasIntegration,
note: "Will display information on the system tab"
}, {
integration: glancesIntegration,
note: "Will display information on the system tab"
}, {
integration: proxmoxIntegration,
note: "Will display information on the cluster tab, only supports one integration at a time"
}]} />
<WidgetIntegrations
items={[
{
integration: dashDotIntegration,
note: "Will display information on the system tab",
},
{
integration: openMediaVaultIntegration,
note: "Will display information on the system tab",
},
{
integration: truenasIntegration,
note: "Will display information on the system tab",
},
{
integration: synologyIntegration,
note: "Will display information on the system tab",
},
{
integration: glancesIntegration,
note: "Will display information on the system tab",
},
{
integration: proxmoxIntegration,
note: "Will display information on the cluster tab, only supports one integration at a time",
},
]}
/>
### Adding the widget
<AddingWidget />
### Configuration
<WidgetConfig configuration={healthMonitoringWidget.configuration} />
@@ -38,6 +38,13 @@ export const healthMonitoringWidget: WidgetDefinition = {
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Visible storage volumes",
description:
"Only available when all selected integrations are Synology. Leave empty to show all detected storage volumes.",
values: "List of storage volumes",
defaultValue: "Empty list",
},
{
name: "Default tab",
description: "Tab that should be visible by default. Only used when multiple integrations are available.",
@@ -9,9 +9,8 @@ export const immichAlbumCarouselWidget: WidgetDefinition = {
configuration: {
items: [
{
name: "Album ID",
description:
"This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'",
name: "Album",
description: "Select the Immich album to show in the slideshow.",
values: { type: "string" },
defaultValue: "",
},
@@ -25,7 +24,13 @@ export const immichAlbumCarouselWidget: WidgetDefinition = {
name: "Show photo info",
description: "Display date of the picture and the current index at the bottom of the slideshow",
values: { type: "boolean" },
defaultValue: "yes",
defaultValue: "no",
},
{
name: "Randomize photos",
description: "Show photos in a random order instead of always starting with the same album order.",
values: { type: "boolean" },
defaultValue: "no",
},
],
},
Loaded 100 of 811 files, more files were not shown because too many files have changed in this diff. Show more