Compare commits

...
206 Commits
Author SHA1 Message Date
Releases Homarr 499bdf110a chore(release): 1.39.0 [skip ci] 2025-09-26 19:15:07 +00:00
homarr-releases[bot] 993a0d78ef chore(release): automatic release v1.39.0 2025-09-26 19:13:45 +00:00
Meier Lukas 5b32f1eb79 feat(weather): add periodic live updates (#4155) 2025-09-26 20:42:13 +02:00
castielwaverly 0b15561f20 fix: Recalculate time ago on re-render (#4107) 2025-09-26 20:30:56 +02:00
Meier Lukas 2443d7300b feat(calendar): add periodic live updates (#4154) 2025-09-26 20:03:34 +02:00
Meier Lukas 0a908de1e7 feat(calendar): add homeassistant support (#4152) 2025-09-26 17:34:33 +02:00
homarr-update-contributors[bot] e1a232b37a chore: update contributors (#4153)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-26 12:39:02 +00:00
Meier Lukas c8a6cdae81 fix(calendar): support multi-day events (#4151) 2025-09-25 21:55:20 +02:00
homarr-renovate[bot] f0a0b9bca1 fix(deps): update dependency zod to ^4.1.11 (#4128)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-25 19:27:40 +00:00
Meier Lukas 3aea77a64f chore(docs): update sitemap 2025-09-25 21:08:37 +02:00
Meier Lukas b7d1d4ceef fix(calendar): missing badge for sonarr series (#4150) 2025-09-25 20:19:21 +02:00
homarr-renovate[bot] aced8c77bb fix(deps): update dependency sass to ^1.93.0 (#4147)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-25 15:23:12 +00:00
homarr-renovate[bot] 717d4ccff2 chore(deps): update dependency semantic-release to ^24.2.9 (#4146)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 20:34:42 +00:00
homarr-renovate[bot] 441e1ceba2 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.52.2 (#4144)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 21:50:50 +02:00
homarr-renovate[bot] 9ad8c6dbf1 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.7 (#4140)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 21:49:15 +02:00
homarr-renovate[bot] c17e5e8774 chore(deps): update dependency eslint to ^9.36.0 (#4145)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 16:39:21 +00:00
homarr-renovate[bot] 0c577a6f11 fix(deps): update dependency zod-validation-error to ^4.0.2 (#4142)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 10:35:41 +00:00
homarr-renovate[bot] a5ec293dda chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.51.0 (#4112)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-24 09:33:50 +00:00
homarr-renovate[bot] 484f0bdca4 fix(deps): update dependency dns-caching to ^0.2.7 (#4137)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 11:21:20 +00:00
homarr-renovate[bot] 7e4bde03e4 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.8 (#4141)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 09:33:29 +00:00
homarr-renovate[bot] 2c3f7c02d6 chore(deps): update dependency @types/node to ^22.18.6 (#4127)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 05:30:14 +00:00
homarr-renovate[bot] 1892987976 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.6 (#4139)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-23 04:36:03 +00:00
homarr-renovate[bot] bd5065ba88 chore(deps): update dependency esbuild to ^0.25.10 (#4134)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 17:28:44 +00:00
homarr-renovate[bot] 9b63fb6df6 chore(deps): update dependency @vitejs/plugin-react to ^5.0.3 (#4129)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 17:25:57 +00:00
homarr-renovate[bot] 588f8a4faa chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.25.10 (#4135)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:42:37 +00:00
homarr-renovate[bot] cf892f517f fix(deps): update dependency mysql2 to v3.15.0 (#4125)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:25:37 +00:00
homarr-renovate[bot] 3eb2b3795b chore(deps): update pnpm to v10.17.0 (#4133)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:19:21 +00:00
homarr-renovate[bot] 2c3856a2a9 fix(deps): update dependency dns-caching to ^0.2.6 (#4131)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 16:14:26 +00:00
homarr-renovate[bot] 83df2ec58b fix(deps): update testcontainers-node monorepo to ^11.6.0 (#4130)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-22 10:34:51 +00:00
homarr-renovate[bot] 303067b0c8 fix(deps): update dependency zod to ^4.1.9 (#4094)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:29:44 +00:00
homarr-renovate[bot] 9b8ec975e4 chore(deps): update dependency @types/dockerode to ^3.3.44 (#4126)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:29:15 +00:00
homarr-renovate[bot] 92811db400 chore(deps): update dependency @types/node to ^22.18.5 (#4117)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 22:28:12 +00:00
homarr-renovate[bot] eb85804cff fix(deps): update dependency @ctrl/deluge to ^7.3.0 (#4121)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 16:38:14 +00:00
homarr-renovate[bot] 25e32e0010 chore(deps): update dependency tar-fs@&gt;&#x3d;3.0.0 &lt;3.0.9 to >=3.1.1 (#4124)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 16:34:31 +00:00
homarr-renovate[bot] 4af13a8d32 chore(deps): update dependency tar-fs@&gt;&#x3d;2.0.0 &lt;2.1.3 to >=3.1.1 (#4123)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 15:28:08 +00:00
homarr-renovate[bot] 11f29d8def fix(deps): update tanstack-query monorepo to ^5.89.0 (#4122)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 09:28:40 +00:00
homarr-renovate[bot] c0ed041bdf fix(deps): update dependency @ctrl/qbittorrent to ^9.8.0 (#4118)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 07:30:25 +00:00
homarr-renovate[bot] 2eb82108a8 fix(deps): update dependency @ctrl/transmission to ^7.4.0 (#4120)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-21 06:41:05 +00:00
homarr-renovate[bot] 034dcacb08 fix(deps): update dependency next-intl to v4.3.9 (#4096)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 21:26:27 +00:00
homarr-renovate[bot] 966e1ac81e chore(deps): update dependency @types/node to ^22.18.4 (#4116)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 21:25:25 +00:00
homarr-renovate[bot] bcc71e6eaa fix(deps): update dependency node-ical to ^0.21.0 (#4111)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 20:33:50 +00:00
homarr-renovate[bot] 61ae703014 fix(deps): update dependency @tabler/icons-react to ^3.35.0 (#4115)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 19:24:29 +00:00
homarr-renovate[bot] 7ea746bed1 fix(deps): update dependency typescript-eslint to ^8.44.0 (#4114)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 17:25:55 +00:00
homarr-renovate[bot] c0197d0e96 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.50.2 (#4110)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-20 07:27:51 +00:00
Meier Lukas 75f9ead209 chore: update bug report template 2025-09-19 21:34:27 +02:00
Releases Homarr c9983ad5a9 chore(release): 1.38.0 [skip ci] 2025-09-19 19:14:52 +00:00
homarr-releases[bot] 5045c64fe2 chore(release): automatic release v1.38.0 2025-09-19 19:13:45 +00:00
homarr-renovate[bot] 69d6ffd9ce chore(deps): update dependency jsdom to v27 (#4102)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-19 17:03:36 +02:00
Meier Lukas 3c2e6a852f feat(system-resources-widget): add has-shadow option (#4093) 2025-09-19 16:58:46 +02:00
Meier Lukas 312e084e2c feat(system-resources-widget): add label display mode option (#4086) 2025-09-19 16:19:58 +02:00
homarr-renovate[bot] 2a67d2f9da chore(deps): update dependency @types/react to v19.1.13 (#4100)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-19 16:16:06 +02:00
homarr-renovate[bot] a81634d239 chore(deps): update dependency @types/node to ^22.18.3 (#4101)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-19 16:15:30 +02:00
homarr-renovate[bot] 288e37553c chore(deps): update pnpm to v10.16.1 (#4098)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-19 16:14:31 +02:00
homarr-renovate[bot] 03545cee52 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.12.2 (#4104)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-19 16:13:34 +02:00
homarr-update-contributors[bot] 88c718eb41 chore: update contributors (#4103)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-19 16:12:49 +02:00
homarr-update-contributors[bot] ccf7e540dc chore: update integrations list readme (#4099)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-17 19:51:13 +02:00
Meier Lukas 9f2b868fce feat(redis): add database index environment variable (#4092) 2025-09-17 19:08:00 +02:00
Nicolas NewmanandMeier Lukas fedbff3fd1 feat(integrations): add ICal (#3980)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-09-17 18:57:50 +02:00
homarr-renovate[bot] 0a1a75dc5f chore(deps): update pnpm to v10.16.0 (#4097)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-17 13:39:05 +00:00
homarr-renovate[bot] 2d3af7afca fix(deps): update dependency next-intl to v4.3.8 (#4095)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-17 11:26:44 +00:00
homarr-renovate[bot] 6e31e25671 fix(deps): update dependency zod to ^4.1.8 (#4090)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-16 21:21:51 +00:00
homarr-renovate[bot] 12b2b82b4e fix(deps): update tanstack-query monorepo to ^5.87.4 (#4071)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-16 07:22:08 +00:00
homarr-renovate[bot] 94fb9b2502 fix(deps): update nextjs monorepo to v15.5.3 (#4087)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-16 06:29:06 +00:00
homarr-renovate[bot] 687cfd4cc7 fix(deps): update dependency zod to ^4.1.7 (#4089)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-16 06:28:48 +00:00
homarr-renovate[bot] 5babf0b240 chore(deps): update dependency @semantic-release/github to ^11.0.6 (#4088)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-16 05:21:44 +00:00
homarr-renovate[bot] 4287d1743b fix(deps): update dependency undici to v7.16.0 (#4083)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-14 17:18:49 +00:00
homarr-renovate[bot] b749dc110d chore(deps): update dependency proxmox-api&gt;undici to v7.16.0 (#4082)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-14 17:18:22 +00:00
homarr-renovate[bot] 0e563fc47f fix(deps): update dependency swagger-ui-react to ^5.29.0 (#4081)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-14 14:28:15 +00:00
Meier Lukas 1d485cf26c docs(readme): add screenshot of example dashboard (#4077) 2025-09-14 15:59:16 +02:00
Meier Lukas 69b46959aa ci(on-release): update bug report template failing (#4073) 2025-09-14 00:49:52 +02:00
Meier Lukas fe676daad2 feat(tasks): add one second interval (#4074) 2025-09-14 00:45:23 +02:00
homarr-renovate[bot] 43d5a27552 fix(deps): update dependency dockerode to ^4.0.8 (#4076)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 22:19:17 +00:00
homarr-renovate[bot] 809fbd5176 fix(deps): update dependency typescript-eslint to ^8.43.0 (#4075)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 17:17:35 +00:00
Meier Lukas 5c65e1a75f chore: update bug report template 2025-09-13 17:20:11 +02:00
Meier Lukas 86cf290c31 refactor(ci): migrate tibdex/github-app-token to actions/create-github-app-token (#4072) 2025-09-13 17:15:33 +02:00
Meier Lukas b398be0303 ci(stability-days): still runs for renovate branches 2025-09-13 14:37:18 +02:00
homarr-renovate[bot] 1412b6c0d3 fix(deps): update mantine monorepo to ^8.3.1 (#4070)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 12:31:01 +00:00
Meier Lukas 6b3672a04d fix(system-resources-widget): chart resizing not working well (#4069) 2025-09-13 14:13:34 +02:00
Meier Lukas b30cc21dbe feat(system-resources-widget): add visible charts option (#4069) 2025-09-13 14:13:18 +02:00
homarr-renovate[bot] 8c24da8732 fix(deps): update mantine monorepo to ^8.3.0 (#4068)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 11:16:56 +00:00
Meier Lukas cb7a6abca7 feat(notebook-widget): add confirmation on dismiss (#4064) 2025-09-13 12:32:21 +02:00
homarr-renovate[bot] cd4c236e5e fix(deps): update dependency mysql2 to v3.14.5 (#4067)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 09:19:52 +00:00
homarr-renovate[bot] 6880d61d6b fix(deps): update dependency jotai to ^2.14.0 (#4066)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-13 06:24:47 +00:00
homarr-crowdin[bot] 764d5eb0ea chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-13 00:46:30 +00:00
Meier Lukas b7d7767343 chore(docs): update sitemap 2025-09-13 01:11:55 +02:00
homarr-renovate[bot] f7d6f3d9e1 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.7 [security] (#4063)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-12 22:24:32 +00:00
Meier Lukas 3b7e3da17c ci: add missing if for pnpm action-setup (#4062) 2025-09-12 23:28:39 +02:00
Meier Lukasandhomarr-renovate[bot] f360c9768a chore(deps): update actions/setup-node action to v5 (#4060)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-12 21:59:37 +02:00
homarr-update-contributors[bot] 8df7fdcab4 chore: update contributors (#4061)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-12 19:54:34 +00:00
Releases Homarr b8f0d9a21f chore(release): 1.37.0 [skip ci] 2025-09-12 19:19:10 +00:00
homarr-releases[bot] 73a50fa687 chore(release): automatic release v1.37.0 2025-09-12 19:18:10 +00:00
Meier Lukas 78626121dd revert(deps): update actions/setup-node action to v5 (#4058) 2025-09-12 21:16:25 +02:00
homarr-releases[bot] c69602ff07 chore(release): automatic release v1.37.0 2025-09-12 19:13:23 +00:00
homarr-update-contributors[bot] 7c22e1d05d chore: update integrations list readme (#4056)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-12 19:22:10 +02:00
ManuelandMeier Lukas dc08bacebf feat(integrations): add truenas (#3745)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-09-12 19:18:20 +02:00
Meier Lukas 26eb2c08c9 fix(update-checker): cached through updates (#4046) 2025-09-12 18:50:41 +02:00
Meier Lukas f7471b6c63 fix(boards): issues with old-import board name containing spaces (#4052) 2025-09-12 18:44:37 +02:00
homarr-renovate[bot] 3703f428b0 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.12.1 [security] (#4055)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-12 16:43:21 +00:00
homarr-renovate[bot] 1019657a63 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.50.1 (#4054)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-12 16:52:58 +02:00
homarr-crowdin[bot] 789ac65cbd chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-11 00:48:42 +00:00
homarr-renovate[bot] d7902f80c2 chore(deps): update dependency semantic-release to ^24.2.8 (#4048)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 23:20:34 +00:00
homarr-renovate[bot] 44d4cec3ff chore(deps): update dependency @semantic-release/release-notes-generator to ^14.1.0 (#4047)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 22:20:12 +00:00
Meier Lukas a843f6043b fix(api): page is crashing (#4039) 2025-09-10 21:18:59 +02:00
Meier Lukas bfcbffbdc6 feat(integration): add github app authentication (#3968) 2025-09-10 21:17:36 +02:00
Meier Lukas 4d57c7ca13 fix(plex-integration): unknown badge shown for added tv-shows (#4032) 2025-09-10 20:46:42 +02:00
homarr-renovate[bot] a7976e9d70 fix(deps): update trpc monorepo to ^11.5.1 (#4045)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 17:19:30 +00:00
homarr-renovate[bot] a149d8c3c1 fix(deps): update dependency fastify to ^5.6.0 (#4042)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 16:25:35 +00:00
homarr-renovate[bot] 3e1285cacd chore(deps): update dependency eslint to ^9.35.0 (#4044)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 16:25:12 +00:00
homarr-renovate[bot] 5b24eb3074 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.5 [security] (#4041)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 16:24:34 +00:00
homarr-renovate[bot] 6389fd8807 chore(deps): update babel monorepo to >=7.28.4 (#4043)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-10 15:21:47 +00:00
homarr-renovate[bot] 1d9eb9bf24 chore(deps): update actions/setup-node action to v5 (#4033)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-09 21:36:11 +02:00
homarr-renovate[bot] 3c9f7ecbf8 chore(deps): update dependency @types/node to ^22.18.1 (#4036)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-09 19:30:11 +00:00
homarr-renovate[bot] cd90eaae9d fix(deps): update dependency next-intl to v4.3.7 (#4038)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-09 19:19:22 +00:00
homarr-renovate[bot] fbb86c452d fix(deps): update dependency @extractus/feed-extractor to v7.1.7 (#4034)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-09 19:10:14 +00:00
homarr-renovate[bot] a1b5f5ceea fix(deps): update dependency next-intl to v4.3.6 (#4035)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-09 19:09:12 +00:00
Meier Lukas eb5af11643 fix(adguard-integration): parse error with protection_disabled_duration field (#4018) 2025-09-08 16:09:42 +02:00
Meier Lukas 7ac2a81f94 fix(iframe): disable scrolling not working (#4017) 2025-09-08 16:09:12 +02:00
homarr-renovate[bot] ecf1bd98b7 fix(deps): update dependency swagger-ui-react to ^5.28.1 (#4029)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-08 10:22:50 +00:00
homarr-renovate[bot] 8231efa281 fix(deps): update dependency sass to ^1.92.1 (#4031)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-08 10:22:21 +00:00
homarr-renovate[bot] 1323bb276f fix(deps): update dependency dotenv to ^17.2.2 (#4024)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-08 10:01:47 +00:00
homarr-renovate[bot] 2eef4a8304 fix(deps): update dependency sass to ^1.92.0 (#4025)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-08 10:01:15 +00:00
homarr-crowdin[bot] 842f4f6284 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-08 10:01:00 +00:00
homarr-renovate[bot] 4fc38bcd5b fix(deps): update dependency @gitbeaker/rest to ^43.5.0 (#4023)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-07 16:21:29 +00:00
homarr-renovate[bot] 99179e4e7d fix(deps): update dependency typescript-eslint to ^8.42.0 (#4022)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-07 13:24:28 +00:00
homarr-crowdin[bot] 8fa245910c chore(lang): update translations from crowdin (#4021)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-07 14:28:54 +02:00
homarr-renovate[bot] b50ec029fa chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.1.4 (#4016)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-06 11:17:10 +00:00
homarr-renovate[bot] 8bf278f603 fix(deps): update dependency mysql2 to v3.14.4 (#4013)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-06 10:42:44 +00:00
Meier Lukas de33439b22 fix(video-widget): hls videos not working (#4015) 2025-09-06 12:29:30 +02:00
homarr-renovate[bot] b65ec27c0d chore(deps): update pnpm to v10.15.1 (#4014)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-06 10:19:12 +00:00
homarr-crowdin[bot] 1f4a9037d7 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-06 00:46:43 +00:00
homarr-renovate[bot] e70074b219 fix(deps): update dependency @ctrl/deluge to ^7.2.0 (#4009)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 23:19:40 +00:00
homarr-renovate[bot] bc94e34ada fix(deps): update dependency @ctrl/transmission to ^7.3.0 (#4011)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 22:19:45 +00:00
Meier Lukas d9440087b7 chore: update bug report template 2025-09-05 23:23:09 +02:00
homarr-renovate[bot] 32b2a09ab4 fix(deps): update dependency @ctrl/qbittorrent to ^9.7.0 (#4010)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 21:19:04 +00:00
Releases Homarr 5d8e2a3c7b chore(release): 1.36.1 [skip ci] 2025-09-05 21:14:37 +00:00
Meier Lukas dd67dd794a chore(release): hotfix for dns-caching (#4008) 2025-09-05 23:13:20 +02:00
Meier Lukas 10668ee359 fix(dns): disable caching by default (#4007) 2025-09-05 23:05:30 +02:00
Meier Lukas d5eb812b72 chore: update bug report template 2025-09-05 22:55:05 +02:00
Releases Homarr 2ac96a11b7 chore(release): 1.36.0 [skip ci] 2025-09-05 19:14:50 +00:00
homarr-releases[bot] 52f0b86e7b chore(release): automatic release v1.36.0 2025-09-05 19:13:51 +00:00
homarr-renovate[bot] d7146d82d1 fix(deps): update tanstack-query monorepo to ^5.87.1 (#3997)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 17:19:13 +00:00
homarr-renovate[bot] eef6b86ad6 fix(deps): update mantine monorepo to ^8.2.8 (#4004)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 16:23:46 +00:00
Meier Lukas 9cb8261ddc fix(header): avatar menu flashes when opening on mobile (#3999) 2025-09-05 18:17:30 +02:00
Meier Lukas d4f73d2b39 ci: add job to skip renovate stability days for non renovate prs (#3967) 2025-09-05 18:17:07 +02:00
homarr-update-contributors[bot] e4f14140a3 chore: update contributors (#4003)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-09-05 18:16:33 +02:00
homarr-crowdin[bot] b77d8114ab chore(lang): update translations from crowdin (#3995)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-05 11:51:50 +02:00
homarr-renovate[bot] 0df1555b50 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.50.0 (#4002)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-05 08:25:05 +00:00
b2eb2c32f6 feat(rss-widget): add hide description option (#4000)
Co-authored-by: HeapReaper <kelivn@heapreaper.nl>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2025-09-04 22:57:24 +02:00
homarr-renovate[bot] b34440470f chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.6 [security] (#3994)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-04 20:53:48 +02:00
Meier Lukas 047654351a fix(deps): tsx breaks imports like dns-caching (#3998) 2025-09-04 20:07:13 +02:00
homarr-renovate[bot] 4e4483c42d fix(deps): update tanstack-query monorepo to ^5.85.6 (#3996)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-04 17:06:31 +00:00
homarr-renovate[bot] 5e92e66803 fix(deps): update dependency dayjs to ^1.11.18 (#3982)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-04 04:17:26 +00:00
homarr-renovate[bot] 83fec579e7 chore(deps): update dependency @semantic-release/github to ^11.0.5 (#3993)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-03 20:54:28 +00:00
Meier Lukas 1a30ccc857 Merge branch 'main' into dev 2025-09-03 21:08:58 +02:00
Meier Lukas 0ee023adf3 chore: update bug report template 2025-09-03 21:06:34 +02:00
Meier Lukas fb6b5e88ff fix: add dns caching (#3736) 2025-09-03 17:37:18 +02:00
homarr-crowdin[bot] ca488f8210 chore(lang): update translations from crowdin (#3989)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-03 17:21:36 +02:00
Meier Lukas d317cacbe8 feat(widget): show name in edit modal (#3970) 2025-09-02 21:53:57 +02:00
Meier Lukas 65b9ddd771 fix(widgets): improve integration selection (#3969) 2025-09-02 21:52:47 +02:00
oussama Dahmazandoussama Dahmaz 3e59c20dba feat(kubernetes): add Talos providers (#3979)
Co-authored-by: oussama Dahmaz <dahmaz@MacBook-Pro-de-odahmaz.local>
2025-09-02 20:29:18 +02:00
homarr-renovate[bot] d2e9b5e983 fix(deps): update dependency swagger-ui-react to ^5.28.0 (#3985)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-02 17:18:51 +00:00
homarr-renovate[bot] 75683c206d fix(deps): update dependency zod-form-data to ^3.0.1 (#3986)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-02 16:24:06 +00:00
homarr-renovate[bot] a3dde2e7ee fix(deps): update dependency @ianvs/prettier-plugin-sort-imports to ^4.7.0 (#3977)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-02 12:29:49 +00:00
homarr-renovate[bot] 7b26a386eb chore(deps): update dependency @vitejs/plugin-react to ^5.0.2 (#3981)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-02 06:29:04 +00:00
homarr-renovate[bot] 032f7ffd29 fix(deps): update dependency dayjs to ^1.11.15 (#3975)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-02 05:21:51 +00:00
homarr-renovate[bot] 2b1a3de8df fix(deps): update dependency @ianvs/prettier-plugin-sort-imports to ^4.6.3 (#3976)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-09-01 14:21:33 +00:00
homarr-crowdin[bot] 4823c0db8b chore(lang): update translations from crowdin (#3973)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-09-01 06:28:14 +02:00
homarr-renovate[bot] b0fe8a9f65 fix(deps): update nextjs monorepo to v15.5.2 (#3972)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-31 23:14:02 +00:00
homarr-renovate[bot] a1def95852 fix(deps): update nextjs monorepo to v15.5.1 (#3971)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-31 18:25:39 +00:00
homarr-renovate[bot] 9c2d50de91 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to v7 (#3954)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-31 11:51:35 +02:00
homarr-renovate[bot] 0e5f43c1c3 fix(deps): update dependency sass to ^1.91.0 (#3965)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-31 09:46:55 +00:00
homarr-renovate[bot] 42071e36ca chore(deps): update dependency tar-fs@&gt;&#x3d;2.0.0 &lt;2.1.3 to v3 (#3953)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-31 11:42:41 +02:00
homarr-crowdin[bot] de7699689f chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-08-31 11:18:34 +02:00
homarr-renovate[bot] be0e78371e fix(deps): update dependency typescript-eslint to ^8.41.0 (#3963)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 18:54:54 +00:00
homarr-renovate[bot] ea4960e113 fix(deps): update dependency drizzle-orm to ^0.44.5 (#3962)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 16:35:30 +00:00
homarr-renovate[bot] 916542210d fix(deps): update dependency zod to ^4.1.5 (#3961)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 13:26:09 +00:00
homarr-renovate[bot] 94eb6772b9 fix(deps): update dependency zod to ^4.1.1 (#3950)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 12:35:09 +00:00
homarr-renovate[bot] afe10b8715 chore(deps): update node.js (#3959)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 12:34:03 +00:00
homarr-renovate[bot] 99bfc4a0b6 chore(deps): update react monorepo (#3955)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 12:32:23 +00:00
homarr-renovate[bot] d58faf5976 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.5 (#3958)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 12:31:21 +00:00
homarr-renovate[bot] cf98156bdc chore(deps): update dependency brace-expansion@&gt;&#x3d;2.0.0 &lt;&#x3d;2.0.1 to v4 (#3952)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 13:49:20 +02:00
homarr-renovate[bot] f166d153fe fix(deps): update dependency undici to v7.15.0 (#3949)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 11:30:46 +00:00
homarr-renovate[bot] dc9a83ba3c chore(deps): update dependency proxmox-api&gt;undici to v7.15.0 (#3945)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 11:30:15 +00:00
homarr-renovate[bot] 0e3c6f0f97 chore(deps): update dependency @types/node to ^22.18.0 (#3956)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 11:08:51 +00:00
homarr-renovate[bot] 51b06922bd chore(deps): update babel monorepo to >=7.28.3 (#3940)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 11:08:30 +00:00
homarr-renovate[bot] 9f3e68fdfe fix(deps): update dependency @testcontainers/postgresql to ^11.5.1 (#3948)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 11:07:59 +00:00
homarr-renovate[bot] 88eed15faf fix(deps): update trpc monorepo to ^11.5.0 (#3951)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:59:36 +00:00
homarr-renovate[bot] f277ceb302 chore(deps): update dependency tar-fs@&gt;&#x3d;3.0.0 &lt;3.0.9 to >=3.1.0 (#3947)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:55:42 +00:00
homarr-renovate[bot] e0c9c0fab7 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.49.0 (#3957)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:47:51 +00:00
homarr-renovate[bot] 21bde0f3ea chore(deps): update dependency eslint to ^9.34.0 (#3942)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:47:27 +00:00
homarr-renovate[bot] 5d908093ce fix(deps): update mantine monorepo to ^8.2.7 (#3939)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:39:01 +00:00
homarr-renovate[bot] 6081a5af13 chore(deps): update dependency hono@&lt;4.6.5 to >=4.9.4 (#3943)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:31:01 +00:00
homarr-renovate[bot] a488efc6f8 chore(deps): update dependency nanoid@&gt;&#x3d;4.0.0 &lt;5.0.9 to >=5.1.5 (#3944)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:29:14 +00:00
homarr-renovate[bot] 24e4d718e2 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.48.1 (#3946)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:26:07 +00:00
homarr-renovate[bot] 71c75a3507 chore(deps): update dependency esbuild@&lt;&#x3d;0.24.2 to >=0.25.9 (#3935)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:24:11 +00:00
homarr-renovate[bot] 5bd6757e22 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.11.0 (#3941)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:21:29 +00:00
homarr-renovate[bot] 81a7f3e93d fix(deps): update dependency next-intl to v4.3.5 (#3937)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:19:47 +00:00
homarr-renovate[bot] 93186c3be1 fix(deps): update dependency tsx to v4.20.5 (#3938)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:18:23 +00:00
homarr-renovate[bot] e695bccfc0 chore(deps): update dependency @types/react to v19.1.11 (#3933)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:17:37 +00:00
homarr-crowdin[bot] a12a86b672 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-08-30 10:16:49 +00:00
homarr-renovate[bot] feee4b3ad7 chore(deps): update dependency tmp@&lt;&#x3d;0.2.3 to >=0.2.5 (#3936)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:14:49 +00:00
homarr-renovate[bot] 36f2181643 chore(deps): update dependency concurrently to ^9.2.1 (#3934)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:11:46 +00:00
homarr-renovate[bot] bc746b67b4 chore(deps): update dependency @types/dockerode to ^3.3.43 (#3931)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:10:17 +00:00
homarr-renovate[bot] 0fb7fd4292 chore(deps): update dependency @types/pg to ^8.15.5 (#3932)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-30 10:07:30 +00:00
Meier Lukas 51f3cd2ce7 chore: update bug report template 2025-08-29 21:28:56 +02:00
homarr-renovate[bot] 8dea07250f chore(deps): update dependency brace-expansion@&gt;&#x3d;1.0.0 &lt;&#x3d;1.1.11 to v4 [security] (#3927)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-08-29 21:28:19 +02:00
179 changed files with 6618 additions and 4268 deletions

No files matched your search

+6
View File
@@ -33,6 +33,12 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.38.0
- 1.37.0
- 1.36.1
- 1.36.0
- 1.35.1
- 1.35.0
- 1.34.0
- 1.33.0
- 1.32.0
+5
View File
@@ -6,6 +6,11 @@
matchPackagePatterns: ["^@homarr/"],
enabled: false,
},
// Reenable once https://github.com/privatenumber/tsx/issues/737 is fixed
{
matchPackagePatterns: ["tsx"],
enabled: false,
},
{
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
automerge: true,
+7 -3
View File
@@ -3,6 +3,8 @@ on:
pull_request:
types: [opened, synchronize]
permissions: {}
jobs:
approve-automatic-prs:
runs-on: ubuntu-latest
@@ -12,10 +14,12 @@ jobs:
uses: actions/checkout@v5
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app_id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to approve pull request
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Approve automatic PRs
@@ -5,6 +5,9 @@ on:
schedule:
- cron: "0 0 * * *" # every day at midnight
permissions:
contents: read # required for code checkout
jobs:
download-crowdin-translations:
runs-on: ubuntu-latest
@@ -15,10 +18,12 @@ jobs:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
app_id: ${{ secrets.CROWDIN_APP_ID }}
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
app-id: ${{ secrets.CROWDIN_APP_ID }}
permission-contents: write # required to commit to crowdin branch
permission-pull-requests: write # required to create pull request
- name: Download Crowdin translations
id: crowdin-download
+10 -5
View File
@@ -49,18 +49,23 @@ jobs:
- name: Obtain token
if: env.SKIP_RELEASE == 'false'
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit package.json & changelog changes, merge them to dev and publish the release
- uses: actions/checkout@v5
if: env.SKIP_RELEASE == 'false'
with:
persist-credentials: false
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
if: env.SKIP_RELEASE == 'false'
- uses: actions/setup-node@v5
if: env.SKIP_RELEASE == 'false'
with:
node-version: 22.18.0
node-version: 22.19.0
cache: "pnpm"
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
- name: Install dependencies
@@ -49,10 +49,11 @@ jobs:
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
- name: Obtain token
id: obtainApprovalToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app_id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write
- name: Approve PR
env:
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
@@ -60,10 +61,12 @@ jobs:
gh pr review --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # write to main branch (due to merge)
permission-pull-requests: write # merge pull request
- id: automerge
if: ${{ steps.semver.outputs.bump != 'major' }}
name: automerge
+13 -8
View File
@@ -11,12 +11,14 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
app_id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
installation_retrieval_mode: repository
installation_retrieval_payload: homarr-labs/documentation
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
owner: homarr-labs
repositories: |
documentation
permission-contents: write # required to dispatch repository workflow
- name: Trigger documentation release
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
@@ -40,10 +42,12 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
uses: actions/checkout@v5
with:
@@ -57,6 +61,7 @@ jobs:
run: |
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
git config --global user.name "Releases Homarr"
git checkout -b update-bug-report-template
git add .
git commit -m "chore: update bug report template"
@@ -0,0 +1,26 @@
permissions:
statuses: write
on:
pull_request:
types: [opened, synchronize]
jobs:
skip-stability-days:
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
name: Skip Stability Days
runs-on: ubuntu-latest
steps:
- name: Add status check
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
-f 'state=success' \
-f 'target_url=https://docs.renovatebot.com/configuration-options#minimumreleaseage' \
-f 'description=Skipped for non renovate branches' \
-f 'context=renovate/stability-days'
+8 -7
View File
@@ -9,9 +9,6 @@ env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
permissions:
contents: write
jobs:
update-contributors:
runs-on: ubuntu-latest
@@ -21,20 +18,24 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout repository
uses: actions/checkout@v5
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Run update script
run: node ./scripts/update-contributors.mjs
@@ -1,7 +1,7 @@
name: Update integration list
on:
workflow_dispatch: { }
workflow_dispatch: {}
push:
paths:
- packages/definitions/src/integration.ts
@@ -20,10 +20,12 @@ jobs:
steps:
- name: Obtain token
id: obtainToken
uses: tibdex/github-app-token@v2
uses: actions/create-github-app-token@v2
with:
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
uses: actions/checkout@v5
env:
@@ -59,4 +61,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
+1 -1
View File
@@ -1 +1 @@
22.18.0
22.19.0
+138
View File
@@ -1,3 +1,141 @@
## [1.39.0](https://github.com/homarr-labs/homarr/compare/v1.38.0...v1.39.0) (2025-09-26)
### Features
* **calendar:** add homeassistant support ([#4152](https://github.com/homarr-labs/homarr/issues/4152)) ([0a908de](https://github.com/homarr-labs/homarr/commit/0a908de1e7768d4524170297e5d20bb1e786d824))
* **calendar:** add periodic live updates ([#4154](https://github.com/homarr-labs/homarr/issues/4154)) ([2443d73](https://github.com/homarr-labs/homarr/commit/2443d7300b958edabceb95ab8663b9bebb30e4d0))
* **weather:** add periodic live updates ([#4155](https://github.com/homarr-labs/homarr/issues/4155)) ([5b32f1e](https://github.com/homarr-labs/homarr/commit/5b32f1eb79d132291992e102f1f2fc72805742a9))
### Bug Fixes
* **calendar:** missing badge for sonarr series ([#4150](https://github.com/homarr-labs/homarr/issues/4150)) ([b7d1d4c](https://github.com/homarr-labs/homarr/commit/b7d1d4ceef110761ede7e2c7e54e86945d6b4fb0))
* **calendar:** support multi-day events ([#4151](https://github.com/homarr-labs/homarr/issues/4151)) ([c8a6cda](https://github.com/homarr-labs/homarr/commit/c8a6cdae8194bad1bfcadfcb06853ab56437b6d4))
* **deps:** update dependency @ctrl/deluge to ^7.3.0 ([#4121](https://github.com/homarr-labs/homarr/issues/4121)) ([eb85804](https://github.com/homarr-labs/homarr/commit/eb85804cff5c5b61697ee270411e75735fc0c32f))
* **deps:** update dependency @ctrl/qbittorrent to ^9.8.0 ([#4118](https://github.com/homarr-labs/homarr/issues/4118)) ([c0ed041](https://github.com/homarr-labs/homarr/commit/c0ed041bdfb3bbb246cdc048f7f4d27818e21348))
* **deps:** update dependency @ctrl/transmission to ^7.4.0 ([#4120](https://github.com/homarr-labs/homarr/issues/4120)) ([2eb8210](https://github.com/homarr-labs/homarr/commit/2eb82108a8d1447652eed840fab0757ff0168c77))
* **deps:** update dependency @tabler/icons-react to ^3.35.0 ([#4115](https://github.com/homarr-labs/homarr/issues/4115)) ([61ae703](https://github.com/homarr-labs/homarr/commit/61ae7030145bee4a4d20afdd176f3e8486668c5f))
* **deps:** update dependency dns-caching to ^0.2.6 ([#4131](https://github.com/homarr-labs/homarr/issues/4131)) ([2c3856a](https://github.com/homarr-labs/homarr/commit/2c3856a2a91c7566f951ec5238b6c4c5c0a26f40))
* **deps:** update dependency dns-caching to ^0.2.7 ([#4137](https://github.com/homarr-labs/homarr/issues/4137)) ([484f0bd](https://github.com/homarr-labs/homarr/commit/484f0bdca45f949999f974d4d119e656d6f7206e))
* **deps:** update dependency mysql2 to v3.15.0 ([#4125](https://github.com/homarr-labs/homarr/issues/4125)) ([cf892f5](https://github.com/homarr-labs/homarr/commit/cf892f517f9b5e06420541650599ec977942387f))
* **deps:** update dependency next-intl to v4.3.9 ([#4096](https://github.com/homarr-labs/homarr/issues/4096)) ([034dcac](https://github.com/homarr-labs/homarr/commit/034dcacb0818ed9daa1e29474ca0f6e39a5c8198))
* **deps:** update dependency node-ical to ^0.21.0 ([#4111](https://github.com/homarr-labs/homarr/issues/4111)) ([bcc71e6](https://github.com/homarr-labs/homarr/commit/bcc71e6eaa0dedd3a5609ef5384219afe01ea13a))
* **deps:** update dependency sass to ^1.93.0 ([#4147](https://github.com/homarr-labs/homarr/issues/4147)) ([aced8c7](https://github.com/homarr-labs/homarr/commit/aced8c77bb33cc3df2d9db3f5c3513632d8e51c2))
* **deps:** update dependency typescript-eslint to ^8.44.0 ([#4114](https://github.com/homarr-labs/homarr/issues/4114)) ([7ea746b](https://github.com/homarr-labs/homarr/commit/7ea746bed12ff66b84ffd993268e47db18c40a88))
* **deps:** update dependency zod to ^4.1.11 ([#4128](https://github.com/homarr-labs/homarr/issues/4128)) ([f0a0b9b](https://github.com/homarr-labs/homarr/commit/f0a0b9bca197cd970b5ad279e50f05806fe51809))
* **deps:** update dependency zod to ^4.1.9 ([#4094](https://github.com/homarr-labs/homarr/issues/4094)) ([303067b](https://github.com/homarr-labs/homarr/commit/303067b0c8fdc92647fbaa574f29a633c63c40d3))
* **deps:** update dependency zod-validation-error to ^4.0.2 ([#4142](https://github.com/homarr-labs/homarr/issues/4142)) ([0c577a6](https://github.com/homarr-labs/homarr/commit/0c577a6f11990b94dc524c6e41ab624e4fdf5c9b))
* **deps:** update tanstack-query monorepo to ^5.89.0 ([#4122](https://github.com/homarr-labs/homarr/issues/4122)) ([11f29d8](https://github.com/homarr-labs/homarr/commit/11f29d8def97286cc5f4b2aa45a345a79ab3630b))
* **deps:** update testcontainers-node monorepo to ^11.6.0 ([#4130](https://github.com/homarr-labs/homarr/issues/4130)) ([83df2ec](https://github.com/homarr-labs/homarr/commit/83df2ec58b3d6bb78660a3ec79690b51a1cd395c))
* Recalculate time ago on re-render ([#4107](https://github.com/homarr-labs/homarr/issues/4107)) ([0b15561](https://github.com/homarr-labs/homarr/commit/0b15561f20abe94908ee62f99a33ff3f030c49e2))
## [1.38.0](https://github.com/homarr-labs/homarr/compare/v1.37.0...v1.38.0) (2025-09-19)
### Features
* **integrations:** add ICal ([#3980](https://github.com/homarr-labs/homarr/issues/3980)) ([fedbff3](https://github.com/homarr-labs/homarr/commit/fedbff3fd162835bd97c3de3a4d3ba676fe13899))
* **notebook-widget:** add confirmation on dismiss ([#4064](https://github.com/homarr-labs/homarr/issues/4064)) ([cb7a6ab](https://github.com/homarr-labs/homarr/commit/cb7a6abca7b6908d41bf50c71f7fce491af548e6))
* **redis:** add database index environment variable ([#4092](https://github.com/homarr-labs/homarr/issues/4092)) ([9f2b868](https://github.com/homarr-labs/homarr/commit/9f2b868fcedf440892b3f0f3ee38388daa291f2e))
* **system-resources-widget:** add has-shadow option ([#4093](https://github.com/homarr-labs/homarr/issues/4093)) ([3c2e6a8](https://github.com/homarr-labs/homarr/commit/3c2e6a852fb8464e98fdb16a58e35b87d5925a1e))
* **system-resources-widget:** add label display mode option ([#4086](https://github.com/homarr-labs/homarr/issues/4086)) ([312e084](https://github.com/homarr-labs/homarr/commit/312e084e2c731ce1bff9c9dc3e1d8f7a88df5429))
* **system-resources-widget:** add visible charts option ([#4069](https://github.com/homarr-labs/homarr/issues/4069)) ([b30cc21](https://github.com/homarr-labs/homarr/commit/b30cc21dbe51aef598740b9320675023e4281820))
* **tasks:** add one second interval ([#4074](https://github.com/homarr-labs/homarr/issues/4074)) ([fe676da](https://github.com/homarr-labs/homarr/commit/fe676daad25e674f95e4fa8b949a77b34d376987))
### Bug Fixes
* **deps:** update dependency dockerode to ^4.0.8 ([#4076](https://github.com/homarr-labs/homarr/issues/4076)) ([43d5a27](https://github.com/homarr-labs/homarr/commit/43d5a27552b9fb89009645e7e711cb62a482e3c7))
* **deps:** update dependency jotai to ^2.14.0 ([#4066](https://github.com/homarr-labs/homarr/issues/4066)) ([6880d61](https://github.com/homarr-labs/homarr/commit/6880d61d6b6392b10911d35ffada8102d5b0d6f7))
* **deps:** update dependency mysql2 to v3.14.5 ([#4067](https://github.com/homarr-labs/homarr/issues/4067)) ([cd4c236](https://github.com/homarr-labs/homarr/commit/cd4c236e5e6bf5f38f86e8e70a04cdd29bc484a3))
* **deps:** update dependency next-intl to v4.3.8 ([#4095](https://github.com/homarr-labs/homarr/issues/4095)) ([2d3af7a](https://github.com/homarr-labs/homarr/commit/2d3af7afca26dac05c602c17d65d31e71d79fb03))
* **deps:** update dependency swagger-ui-react to ^5.29.0 ([#4081](https://github.com/homarr-labs/homarr/issues/4081)) ([0e563fc](https://github.com/homarr-labs/homarr/commit/0e563fc47f4c894f0df502afdb9818c7361bbae3))
* **deps:** update dependency typescript-eslint to ^8.43.0 ([#4075](https://github.com/homarr-labs/homarr/issues/4075)) ([809fbd5](https://github.com/homarr-labs/homarr/commit/809fbd51766ae88b814d69043d452f8959bdb98b))
* **deps:** update dependency undici to v7.16.0 ([#4083](https://github.com/homarr-labs/homarr/issues/4083)) ([4287d17](https://github.com/homarr-labs/homarr/commit/4287d1743b2e90c7957b9c74dcd975ab3d494112))
* **deps:** update dependency zod to ^4.1.7 ([#4089](https://github.com/homarr-labs/homarr/issues/4089)) ([687cfd4](https://github.com/homarr-labs/homarr/commit/687cfd4cc7da3c99f727432df18ecf1ebc9f2967))
* **deps:** update dependency zod to ^4.1.8 ([#4090](https://github.com/homarr-labs/homarr/issues/4090)) ([6e31e25](https://github.com/homarr-labs/homarr/commit/6e31e2567162b1ee88ea0a2fbb965064ff81ba23))
* **deps:** update mantine monorepo to ^8.3.0 ([#4068](https://github.com/homarr-labs/homarr/issues/4068)) ([8c24da8](https://github.com/homarr-labs/homarr/commit/8c24da87320f1d1a54aee5e87254c4bfcc993fb5))
* **deps:** update mantine monorepo to ^8.3.1 ([#4070](https://github.com/homarr-labs/homarr/issues/4070)) ([1412b6c](https://github.com/homarr-labs/homarr/commit/1412b6c0d3a46d32c764758fc3cc5b29415dd532))
* **deps:** update nextjs monorepo to v15.5.3 ([#4087](https://github.com/homarr-labs/homarr/issues/4087)) ([94fb9b2](https://github.com/homarr-labs/homarr/commit/94fb9b25028529a7320c18cf82d0cbdc238d4c52))
* **deps:** update tanstack-query monorepo to ^5.87.4 ([#4071](https://github.com/homarr-labs/homarr/issues/4071)) ([12b2b82](https://github.com/homarr-labs/homarr/commit/12b2b82b4ee34c038e127594fd42216957b41b55))
* **system-resources-widget:** chart resizing not working well ([#4069](https://github.com/homarr-labs/homarr/issues/4069)) ([6b3672a](https://github.com/homarr-labs/homarr/commit/6b3672a04d66acd80ea71da2c5fff4e31c24c1aa))
## [1.37.0](https://github.com/homarr-labs/homarr/compare/v1.36.1...v1.37.0) (2025-09-12)
### Features
* **integration:** add github app authentication ([#3968](https://github.com/homarr-labs/homarr/issues/3968)) ([bfcbffb](https://github.com/homarr-labs/homarr/commit/bfcbffbdc6499ac5f10bfbf2f5db141eef955049))
* **integrations:** add truenas ([#3745](https://github.com/homarr-labs/homarr/issues/3745)) ([dc08bac](https://github.com/homarr-labs/homarr/commit/dc08bacebfe22ee528565259605787720a522b6b))
### Bug Fixes
* **adguard-integration:** parse error with protection_disabled_duration field ([#4018](https://github.com/homarr-labs/homarr/issues/4018)) ([eb5af11](https://github.com/homarr-labs/homarr/commit/eb5af116436ddd11b7ff094e882355f951ca622a))
* **api:** page is crashing ([#4039](https://github.com/homarr-labs/homarr/issues/4039)) ([a843f60](https://github.com/homarr-labs/homarr/commit/a843f6043b0ed90c5a205b697497490631e0dd19))
* **boards:** issues with old-import board name containing spaces ([#4052](https://github.com/homarr-labs/homarr/issues/4052)) ([f7471b6](https://github.com/homarr-labs/homarr/commit/f7471b6c63f80618d0b91b16fb5545e0a3760cbe))
* **deps:** update dependency @ctrl/deluge to ^7.2.0 ([#4009](https://github.com/homarr-labs/homarr/issues/4009)) ([e70074b](https://github.com/homarr-labs/homarr/commit/e70074b2191df1dac1e9aa94dbb0cec7073193c9))
* **deps:** update dependency @ctrl/qbittorrent to ^9.7.0 ([#4010](https://github.com/homarr-labs/homarr/issues/4010)) ([32b2a09](https://github.com/homarr-labs/homarr/commit/32b2a09ab4e9826b0640d47e1d443682b5fbcff5))
* **deps:** update dependency @ctrl/transmission to ^7.3.0 ([#4011](https://github.com/homarr-labs/homarr/issues/4011)) ([bc94e34](https://github.com/homarr-labs/homarr/commit/bc94e34ada04458901389f3d7ba18e76849fe6d4))
* **deps:** update dependency @extractus/feed-extractor to v7.1.7 ([#4034](https://github.com/homarr-labs/homarr/issues/4034)) ([fbb86c4](https://github.com/homarr-labs/homarr/commit/fbb86c452d3b8d8cde5357e369fbc14349b71c81))
* **deps:** update dependency @gitbeaker/rest to ^43.5.0 ([#4023](https://github.com/homarr-labs/homarr/issues/4023)) ([4fc38bc](https://github.com/homarr-labs/homarr/commit/4fc38bcd5bbed0b3ede2e742c0677bdfbab47295))
* **deps:** update dependency dotenv to ^17.2.2 ([#4024](https://github.com/homarr-labs/homarr/issues/4024)) ([1323bb2](https://github.com/homarr-labs/homarr/commit/1323bb276fc8120dd62385b4c79cc630b2078949))
* **deps:** update dependency fastify to ^5.6.0 ([#4042](https://github.com/homarr-labs/homarr/issues/4042)) ([a149d8c](https://github.com/homarr-labs/homarr/commit/a149d8c3c112740c9c563463ab72e89c77b9a4ec))
* **deps:** update dependency mysql2 to v3.14.4 ([#4013](https://github.com/homarr-labs/homarr/issues/4013)) ([8bf278f](https://github.com/homarr-labs/homarr/commit/8bf278f603ff37e68d7bed41c4c5f6e6351fe94e))
* **deps:** update dependency next-intl to v4.3.6 ([#4035](https://github.com/homarr-labs/homarr/issues/4035)) ([a1b5f5c](https://github.com/homarr-labs/homarr/commit/a1b5f5ceea96b18bda8aa7cee7002b10dc4b428c))
* **deps:** update dependency next-intl to v4.3.7 ([#4038](https://github.com/homarr-labs/homarr/issues/4038)) ([cd90eaa](https://github.com/homarr-labs/homarr/commit/cd90eaae9da566393114a02765f57f1e6f55fe1c))
* **deps:** update dependency sass to ^1.92.0 ([#4025](https://github.com/homarr-labs/homarr/issues/4025)) ([2eef4a8](https://github.com/homarr-labs/homarr/commit/2eef4a8304f0ab5b99313e6e8904582303dd1883))
* **deps:** update dependency sass to ^1.92.1 ([#4031](https://github.com/homarr-labs/homarr/issues/4031)) ([8231efa](https://github.com/homarr-labs/homarr/commit/8231efa28195389f4b83a06d137ea6b4b2f49eee))
* **deps:** update dependency swagger-ui-react to ^5.28.1 ([#4029](https://github.com/homarr-labs/homarr/issues/4029)) ([ecf1bd9](https://github.com/homarr-labs/homarr/commit/ecf1bd98b74c8e8fca03bcd210fb118382ce2d36))
* **deps:** update dependency typescript-eslint to ^8.42.0 ([#4022](https://github.com/homarr-labs/homarr/issues/4022)) ([99179e4](https://github.com/homarr-labs/homarr/commit/99179e4e7d957f5616a24c63982af957613db427))
* **deps:** update trpc monorepo to ^11.5.1 ([#4045](https://github.com/homarr-labs/homarr/issues/4045)) ([a7976e9](https://github.com/homarr-labs/homarr/commit/a7976e9d700793d8f48529bc5c1f11f8bca724ce))
* **iframe:** disable scrolling not working ([#4017](https://github.com/homarr-labs/homarr/issues/4017)) ([7ac2a81](https://github.com/homarr-labs/homarr/commit/7ac2a81f945c44745917fe4eb8318fdfff3deb14))
* **plex-integration:** unknown badge shown for added tv-shows ([#4032](https://github.com/homarr-labs/homarr/issues/4032)) ([4d57c7c](https://github.com/homarr-labs/homarr/commit/4d57c7ca13bdf78b5ac9d0710cd486ad1f41d4d4))
* **update-checker:** cached through updates ([#4046](https://github.com/homarr-labs/homarr/issues/4046)) ([26eb2c0](https://github.com/homarr-labs/homarr/commit/26eb2c08c94bd9d440a3c9e914fc9af4ffd48301))
* **video-widget:** hls videos not working ([#4015](https://github.com/homarr-labs/homarr/issues/4015)) ([de33439](https://github.com/homarr-labs/homarr/commit/de33439b22d3f5f2ff5856054cfa31a4f78ee088))
### Reverts
* **deps:** update actions/setup-node action to v5 ([#4058](https://github.com/homarr-labs/homarr/issues/4058)) ([7862612](https://github.com/homarr-labs/homarr/commit/78626121ddfa5462eaad7a6933733ec03a83c04a))
## [1.36.1](https://github.com/homarr-labs/homarr/compare/v1.36.0...v1.36.1) (2025-09-05)
### Bug Fixes
* **dns:** disable caching by default ([#4007](https://github.com/homarr-labs/homarr/issues/4007)) ([10668ee](https://github.com/homarr-labs/homarr/commit/10668ee359940c1e24b543a7a20fe400474e12bf))
## [1.36.0](https://github.com/homarr-labs/homarr/compare/v1.35.1...v1.36.0) (2025-09-05)
### Features
* **kubernetes:** add Talos providers ([#3979](https://github.com/homarr-labs/homarr/issues/3979)) ([3e59c20](https://github.com/homarr-labs/homarr/commit/3e59c20dba543634a6a79359f83c13fa81e7ccf3))
* **rss-widget:** add hide description option ([#4000](https://github.com/homarr-labs/homarr/issues/4000)) ([b2eb2c3](https://github.com/homarr-labs/homarr/commit/b2eb2c32f6995563feb3c8b4a100be3bfe1d3d80))
* **widget:** show name in edit modal ([#3970](https://github.com/homarr-labs/homarr/issues/3970)) ([d317cac](https://github.com/homarr-labs/homarr/commit/d317cacbe83394dd3f6de3e74b53f683654e91d1))
### Bug Fixes
* add dns caching ([#3736](https://github.com/homarr-labs/homarr/issues/3736)) ([fb6b5e8](https://github.com/homarr-labs/homarr/commit/fb6b5e88ff3a5e85d06885aed1f2a99788e04f2c))
* **deps:** tsx breaks imports like dns-caching ([#3998](https://github.com/homarr-labs/homarr/issues/3998)) ([0476543](https://github.com/homarr-labs/homarr/commit/047654351ab50a730551e61ce98065c9bb7ca3fa))
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.6.3 ([#3976](https://github.com/homarr-labs/homarr/issues/3976)) ([2b1a3de](https://github.com/homarr-labs/homarr/commit/2b1a3de8dffdd9f089b82e4bcfb304f156f60670))
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.7.0 ([#3977](https://github.com/homarr-labs/homarr/issues/3977)) ([a3dde2e](https://github.com/homarr-labs/homarr/commit/a3dde2e7ee40fcfddced43f4b5c124010f0410db))
* **deps:** update dependency @testcontainers/postgresql to ^11.5.1 ([#3948](https://github.com/homarr-labs/homarr/issues/3948)) ([9f3e68f](https://github.com/homarr-labs/homarr/commit/9f3e68fdfee5936727c97d0d99e174cd368de260))
* **deps:** update dependency dayjs to ^1.11.15 ([#3975](https://github.com/homarr-labs/homarr/issues/3975)) ([032f7ff](https://github.com/homarr-labs/homarr/commit/032f7ffd29eddd200d5434ba1c65b8cd5800c8ff))
* **deps:** update dependency dayjs to ^1.11.18 ([#3982](https://github.com/homarr-labs/homarr/issues/3982)) ([5e92e66](https://github.com/homarr-labs/homarr/commit/5e92e66803ddf9256fce8a92aec06dabe2491edc))
* **deps:** update dependency drizzle-orm to ^0.44.5 ([#3962](https://github.com/homarr-labs/homarr/issues/3962)) ([ea4960e](https://github.com/homarr-labs/homarr/commit/ea4960e11332af0062df964258d9e24def0984e7))
* **deps:** update dependency next-intl to v4.3.5 ([#3937](https://github.com/homarr-labs/homarr/issues/3937)) ([81a7f3e](https://github.com/homarr-labs/homarr/commit/81a7f3e93dfd52eea2c87cae8f2d66dd312f7055))
* **deps:** update dependency sass to ^1.91.0 ([#3965](https://github.com/homarr-labs/homarr/issues/3965)) ([0e5f43c](https://github.com/homarr-labs/homarr/commit/0e5f43c1c38a7e0186d86c29a73477f943eeb416))
* **deps:** update dependency swagger-ui-react to ^5.28.0 ([#3985](https://github.com/homarr-labs/homarr/issues/3985)) ([d2e9b5e](https://github.com/homarr-labs/homarr/commit/d2e9b5e983e2f277da30e98ad313e1afd35d70d3))
* **deps:** update dependency tsx to v4.20.5 ([#3938](https://github.com/homarr-labs/homarr/issues/3938)) ([93186c3](https://github.com/homarr-labs/homarr/commit/93186c3be1fdcf6c215efcd8d7236fc44fc74d77))
* **deps:** update dependency typescript-eslint to ^8.41.0 ([#3963](https://github.com/homarr-labs/homarr/issues/3963)) ([be0e783](https://github.com/homarr-labs/homarr/commit/be0e78371eb98740f313b1abd20413cbafa68a20))
* **deps:** update dependency undici to v7.15.0 ([#3949](https://github.com/homarr-labs/homarr/issues/3949)) ([f166d15](https://github.com/homarr-labs/homarr/commit/f166d153fee521ef31a5328f082b4f52fbfd7239))
* **deps:** update dependency zod to ^4.1.1 ([#3950](https://github.com/homarr-labs/homarr/issues/3950)) ([94eb677](https://github.com/homarr-labs/homarr/commit/94eb6772b9216b6377f6306b2a88c2bfe58bfb59))
* **deps:** update dependency zod to ^4.1.5 ([#3961](https://github.com/homarr-labs/homarr/issues/3961)) ([9165422](https://github.com/homarr-labs/homarr/commit/916542210d9bfbb40abf27123f4989ec15dcd45b))
* **deps:** update dependency zod-form-data to ^3.0.1 ([#3986](https://github.com/homarr-labs/homarr/issues/3986)) ([75683c2](https://github.com/homarr-labs/homarr/commit/75683c206d06da3b54c5ab0605b2a4b37674bbd6))
* **deps:** update mantine monorepo to ^8.2.7 ([#3939](https://github.com/homarr-labs/homarr/issues/3939)) ([5d90809](https://github.com/homarr-labs/homarr/commit/5d908093ce3c8b395547681ae0a428afd08a255d))
* **deps:** update mantine monorepo to ^8.2.8 ([#4004](https://github.com/homarr-labs/homarr/issues/4004)) ([eef6b86](https://github.com/homarr-labs/homarr/commit/eef6b86ad647fe955bdb125f504b4a98a79baf03))
* **deps:** update nextjs monorepo to v15.5.1 ([#3971](https://github.com/homarr-labs/homarr/issues/3971)) ([a1def95](https://github.com/homarr-labs/homarr/commit/a1def958525816ec41198407d6d84ef9384be58f))
* **deps:** update nextjs monorepo to v15.5.2 ([#3972](https://github.com/homarr-labs/homarr/issues/3972)) ([b0fe8a9](https://github.com/homarr-labs/homarr/commit/b0fe8a9f653e09cb3f861f39f06bb8d4e8dae006))
* **deps:** update tanstack-query monorepo to ^5.85.6 ([#3996](https://github.com/homarr-labs/homarr/issues/3996)) ([4e4483c](https://github.com/homarr-labs/homarr/commit/4e4483c42d9edd3187981e57694721ff5337be8e))
* **deps:** update tanstack-query monorepo to ^5.87.1 ([#3997](https://github.com/homarr-labs/homarr/issues/3997)) ([d7146d8](https://github.com/homarr-labs/homarr/commit/d7146d82d14885cffb24e14e7e5a610fdeec1b03))
* **deps:** update trpc monorepo to ^11.5.0 ([#3951](https://github.com/homarr-labs/homarr/issues/3951)) ([88eed15](https://github.com/homarr-labs/homarr/commit/88eed15fafbc68c1c669c28ff576aca503acd585))
* **header:** avatar menu flashes when opening on mobile ([#3999](https://github.com/homarr-labs/homarr/issues/3999)) ([9cb8261](https://github.com/homarr-labs/homarr/commit/9cb8261ddc5701ff5d5786ea1fba823e1e9317b2))
* **widgets:** improve integration selection ([#3969](https://github.com/homarr-labs/homarr/issues/3969)) ([65b9ddd](https://github.com/homarr-labs/homarr/commit/65b9ddd771a573330b4c40966f10bd1fc36a6e1d))
## [1.35.1](https://github.com/homarr-labs/homarr/compare/v1.35.0...v1.35.1) (2025-09-03)
### Bug Fixes
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:22.18.0-alpine AS base
FROM node:22.19.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
+3 -1
View File
@@ -42,9 +42,11 @@ const nextConfig: NextConfig = {
headers: [
{
key: "Content-Security-Policy",
// worker-src / media-src with blob: is necessary for video.js, see https://github.com/homarr-labs/homarr/issues/3912 and https://stackoverflow.com/questions/65792855/problem-with-video-js-and-content-security-policy-csp
value: `
default-src 'self';
script-src * 'unsafe-inline' 'unsafe-eval';
worker-src * blob:;
base-uri 'self';
connect-src *;
style-src * 'unsafe-inline';
@@ -53,7 +55,7 @@ const nextConfig: NextConfig = {
form-action 'self';
img-src * data:;
font-src * data:;
media-src * data:;
media-src * data: blob:;
`
.replace(/\s{2,}/g, " ")
.trim(),
+26 -26
View File
@@ -50,57 +50,57 @@
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"@mantine/colors-generator": "^8.2.5",
"@mantine/core": "^8.2.5",
"@mantine/dropzone": "^8.2.5",
"@mantine/hooks": "^8.2.5",
"@mantine/modals": "^8.2.5",
"@mantine/tiptap": "^8.2.5",
"@mantine/colors-generator": "^8.3.1",
"@mantine/core": "^8.3.1",
"@mantine/dropzone": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/modals": "^8.3.1",
"@mantine/tiptap": "^8.3.1",
"@million/lint": "1.0.14",
"@tabler/icons-react": "^3.34.1",
"@tanstack/react-query": "^5.85.5",
"@tanstack/react-query-devtools": "^5.85.5",
"@tanstack/react-query-next-experimental": "^5.85.5",
"@trpc/client": "^11.4.4",
"@trpc/next": "^11.4.4",
"@trpc/react-query": "^11.4.4",
"@trpc/server": "^11.4.4",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.89.0",
"@tanstack/react-query-devtools": "^5.89.0",
"@tanstack/react-query-next-experimental": "^5.89.0",
"@trpc/client": "^11.5.1",
"@trpc/next": "^11.5.1",
"@trpc/react-query": "^11.5.1",
"@trpc/server": "^11.5.1",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0",
"chroma-js": "^3.1.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"dotenv": "^17.2.1",
"dayjs": "^1.11.18",
"dotenv": "^17.2.2",
"flag-icons": "^7.5.0",
"glob": "^11.0.3",
"jotai": "^2.13.1",
"jotai": "^2.14.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "15.5.0",
"next": "15.5.3",
"postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-error-boundary": "^6.0.0",
"react-simple-code-editor": "^0.14.1",
"sass": "^1.90.0",
"sass": "^1.93.0",
"superjson": "2.2.2",
"swagger-ui-react": "^5.27.1",
"swagger-ui-react": "^5.29.0",
"use-deep-compare-effect": "^1.8.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/chroma-js": "3.1.1",
"@types/node": "^22.17.2",
"@types/node": "^22.18.6",
"@types/prismjs": "^1.26.5",
"@types/react": "19.1.10",
"@types/react-dom": "19.1.7",
"@types/react": "19.1.13",
"@types/react-dom": "19.1.9",
"@types/swagger-ui-react": "^5.18.0",
"concurrently": "^9.2.0",
"eslint": "^9.33.0",
"concurrently": "^9.2.1",
"eslint": "^9.36.0",
"node-loader": "^2.1.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
@@ -61,6 +61,10 @@ const getBoardAndPermissionsAsync = async (params: Awaited<Props["params"]>) =>
notFound();
}
if (error instanceof TRPCError && error.code === "BAD_REQUEST") {
notFound();
}
throw error;
}
};
@@ -45,6 +45,10 @@ export const createBoardLayout = <TParams extends Params>({
notFound();
}
if (error instanceof TRPCError && error.code === "BAD_REQUEST") {
notFound();
}
throw error;
});
const colorScheme = await getCurrentColorSchemeAsync();
@@ -1,9 +1,12 @@
import {
IconCode,
IconGrid3x3,
IconKey,
IconLink,
IconMessage,
IconPassword,
IconPasswordUser,
IconPlug,
IconServer,
IconUser,
} from "@tabler/icons-react";
@@ -19,6 +22,10 @@ export const integrationSecretIcons = {
tokenId: IconGrid3x3,
personalAccessToken: IconPasswordUser,
topic: IconMessage,
url: IconLink,
opnsenseApiKey: IconKey,
opnsenseApiSecret: IconPassword,
githubAppId: IconCode,
githubInstallationId: IconPlug,
privateKey: IconKey,
} satisfies Record<IntegrationSecretKind, TablerIcon>;
@@ -1,7 +1,7 @@
"use client";
import type { ChangeEventHandler, FocusEventHandler } from "react";
import { PasswordInput, TextInput } from "@mantine/core";
import { PasswordInput, Textarea, TextInput } from "@mantine/core";
import { integrationSecretKindObject } from "@homarr/definitions";
import type { IntegrationSecretKind } from "@homarr/definitions";
@@ -14,9 +14,9 @@ interface IntegrationSecretInputProps {
label?: string;
kind: IntegrationSecretKind;
value?: string;
onChange: ChangeEventHandler<HTMLInputElement>;
onFocus?: FocusEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>;
onChange: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onFocus?: FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onBlur?: FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
error?: string;
}
@@ -31,6 +31,19 @@ export const IntegrationSecretInput = (props: IntegrationSecretInputProps) => {
const PublicSecretInput = ({ kind, ...props }: IntegrationSecretInputProps) => {
const t = useI18n();
const Icon = integrationSecretIcons[kind];
const { multiline } = integrationSecretKindObject[kind];
if (multiline) {
return (
<Textarea
{...props}
label={props.label ?? t(`integration.secrets.kind.${kind}.label`)}
w="100%"
leftSection={<Icon size={20} stroke={1.5} />}
autosize
minRows={2}
/>
);
}
return (
<TextInput
@@ -45,6 +58,21 @@ const PublicSecretInput = ({ kind, ...props }: IntegrationSecretInputProps) => {
const PrivateSecretInput = ({ kind, ...props }: IntegrationSecretInputProps) => {
const t = useI18n();
const Icon = integrationSecretIcons[kind];
const { multiline } = integrationSecretKindObject[kind];
if (multiline) {
return (
<Textarea
{...props}
label={props.label ?? t(`integration.secrets.kind.${kind}.label`)}
description={t("integration.secrets.secureNotice")}
w="100%"
leftSection={<Icon size={20} stroke={1.5} />}
autosize
minRows={2}
/>
);
}
return (
<PasswordInput
@@ -33,6 +33,8 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
integration.secrets.every((secret) => secretKinds.includes(secret.kind)),
) ?? getDefaultSecretKinds(integration.kind);
const hasUrlSecret = secretsKinds.includes("url");
const router = useRouter();
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
initialValues: {
@@ -50,10 +52,14 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret]));
const handleSubmitAsync = async (values: FormType) => {
const url = hasUrlSecret
? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin
: values.url;
await mutateAsync(
{
id: integration.id,
...values,
url,
secrets: values.secrets.map((secret) => ({
kind: secret.kind,
value: secret.value === "" ? null : secret.value,
@@ -92,7 +98,9 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
<Stack>
<TextInput withAsterisk label={t("integration.field.name.label")} {...form.getInputProps("name")} />
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
{hasUrlSecret ? null : (
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
)}
<Fieldset legend={t("integration.secrets.title")}>
<Stack gap="sm">
@@ -55,12 +55,19 @@ const formSchema = integrationCreateSchema.omit({ kind: true }).and(
export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => {
const t = useI18n();
const secretKinds = getAllSecretKindOptions(searchParams.kind);
const hasUrlSecret = secretKinds.some((kinds) => kinds.includes("url"));
const router = useRouter();
const [opened, setOpened] = useState(false);
let url = searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "";
if (hasUrlSecret) {
// Placeholder Url, replaced with origin of the secret Url on submit
url = "http://localhost";
}
const form = useZodForm(formSchema, {
initialValues: {
name: searchParams.name ?? getIntegrationName(searchParams.kind),
url: searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "",
url,
secrets: secretKinds[0].map((kind) => ({
kind,
value: "",
@@ -83,10 +90,14 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
const handleSubmitAsync = async (values: FormType) => {
const url = hasUrlSecret
? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin
: values.url;
await createIntegrationAsync(
{
kind: searchParams.kind,
...values,
url,
},
{
async onSuccess(data) {
@@ -114,10 +125,10 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
await createAppAsync(
{
name: values.name,
href: hasCustomHref ? values.appHref : values.url,
href: hasCustomHref ? values.appHref : url,
iconUrl: getIconUrl(searchParams.kind),
description: null,
pingUrl: values.url,
pingUrl: url,
},
{
async onSettled() {
@@ -149,7 +160,9 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
<Stack>
<TextInput withAsterisk label={t("integration.field.name.label")} autoFocus {...form.getInputProps("name")} />
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
{hasUrlSecret ? null : (
<TextInput withAsterisk label={t("integration.field.url.label")} {...form.getInputProps("url")} />
)}
<Fieldset legend={t("integration.secrets.title")}>
<Stack gap="sm">
@@ -17,6 +17,10 @@ import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
import { IconPowerOff } from "@homarr/ui/icons";
const cronExpressions = [
{
value: "*/1 * * * * *",
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 1 }),
},
{
value: "*/5 * * * * *",
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
@@ -10,7 +10,7 @@ import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
import { useModalAction } from "@homarr/modals";
import { showSuccessNotification } from "@homarr/notifications";
import { useSettings } from "@homarr/settings";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
import { loadWidgetDynamic, reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets";
import { WidgetError } from "@homarr/widgets/errors";
@@ -31,7 +31,7 @@ interface WidgetPreviewPageContentProps {
export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPreviewPageContentProps) => {
const settings = useSettings();
const t = useScopedI18n("widgetPreview");
const t = useI18n();
const { openModal: openWidgetEditModal } = useModalAction(WidgetEditModal);
const { openModal: openPreviewDimensionsModal } = useModalAction(PreviewDimensionsModal);
const currentDefinition = useMemo(() => widgetImports[kind].definition, [kind]);
@@ -55,20 +55,27 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
});
const handleOpenEditWidgetModal = useCallback(() => {
openWidgetEditModal({
kind,
value: state,
onSuccessfulEdit: (value) => {
setState(value);
openWidgetEditModal(
{
kind,
value: state,
onSuccessfulEdit: (value) => {
setState(value);
},
integrationData: integrationData.filter(
(integration) =>
"supportedIntegrations" in currentDefinition &&
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
),
integrationSupport: "supportedIntegrations" in currentDefinition,
settings,
},
integrationData: integrationData.filter(
(integration) =>
"supportedIntegrations" in currentDefinition &&
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
),
integrationSupport: "supportedIntegrations" in currentDefinition,
settings,
});
{
title(t) {
return `${t("item.edit.title")} - ${t(`widget.${kind}.name`)}`;
},
},
);
}, [currentDefinition, integrationData, kind, openWidgetEditModal, settings, state]);
const Comp = loadWidgetDynamic(kind);
@@ -76,7 +83,7 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
const toggleEditMode = useCallback(() => {
setEditMode((editMode) => !editMode);
showSuccessNotification({
message: editMode ? t("toggle.disabled") : t("toggle.enabled"),
message: editMode ? t("widgetPreview.toggle.disabled") : t("widgetPreview.toggle.enabled"),
});
}, [editMode, t]);
@@ -49,36 +49,43 @@ export const BoardItemMenu = ({
if (!isEditMode || isPending) return null;
const openEditModal = () => {
openModal({
kind: item.kind,
value: {
advancedOptions: item.advancedOptions,
options: item.options,
integrationIds: item.integrationIds,
openModal(
{
kind: item.kind,
value: {
advancedOptions: item.advancedOptions,
options: item.options,
integrationIds: item.integrationIds,
},
onSuccessfulEdit: ({ options, integrationIds, advancedOptions }) => {
updateItemOptions({
itemId: item.id,
newOptions: options,
});
updateItemAdvancedOptions({
itemId: item.id,
newAdvancedOptions: advancedOptions,
});
updateItemIntegrations({
itemId: item.id,
newIntegrations: integrationIds,
});
refResetErrorBoundaryOnNextRender.current = true;
},
integrationData: (integrationData ?? []).filter(
(integration) =>
"supportedIntegrations" in currentDefinition &&
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
),
integrationSupport: "supportedIntegrations" in currentDefinition,
settings,
},
onSuccessfulEdit: ({ options, integrationIds, advancedOptions }) => {
updateItemOptions({
itemId: item.id,
newOptions: options,
});
updateItemAdvancedOptions({
itemId: item.id,
newAdvancedOptions: advancedOptions,
});
updateItemIntegrations({
itemId: item.id,
newIntegrations: integrationIds,
});
refResetErrorBoundaryOnNextRender.current = true;
{
title(t) {
return `${t("item.edit.title")} - ${t(`widget.${item.kind}.name`)}`;
},
},
integrationData: (integrationData ?? []).filter(
(integration) =>
"supportedIntegrations" in currentDefinition &&
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
),
integrationSupport: "supportedIntegrations" in currentDefinition,
settings,
});
);
};
const openRemoveModal = () => {
+1 -1
View File
@@ -17,7 +17,7 @@ interface Props {
export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
return (
<AppShellHeader>
<AppShellHeader maw="100vw" style={{ overflowX: "hidden" }}>
<Group h="100%" gap="xl" px="md" justify="apart" wrap="nowrap">
<Group h="100%" align="center" style={{ flex: 1 }} wrap="nowrap">
{hasNavigation && <ClientBurger />}
+9 -7
View File
@@ -21,6 +21,7 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/analytics": "workspace:^0.1.0",
"@homarr/auth": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/cron-job-api": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0",
@@ -32,23 +33,24 @@
"@homarr/log": "workspace:^",
"@homarr/ping": "workspace:^0.1.0",
"@homarr/redis": "workspace:^0.1.0",
"@homarr/request-handler": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.13",
"dotenv": "^17.2.1",
"fastify": "^5.5.0",
"dayjs": "^1.11.18",
"dotenv": "^17.2.2",
"fastify": "^5.6.0",
"superjson": "2.2.2",
"undici": "7.14.0"
"undici": "7.16.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^22.17.2",
"@types/node": "^22.18.6",
"dotenv-cli": "^10.0.0",
"esbuild": "^0.25.9",
"eslint": "^9.33.0",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"prettier": "^3.6.2",
"tsx": "4.20.4",
"typescript": "^5.9.2"
-2
View File
@@ -26,7 +26,6 @@ export class JobManager implements IJobManager {
logger.info(`Updating cron job interval name="${name}" expression="${cron}"`);
const job = this.jobGroup.getJobRegistry().get(name);
if (!job) throw new Error(`Job ${name} not found`);
if (job.cronExpression === "never") throw new Error(`Job ${name} cannot be updated as it is set to "never"`);
if (!validateCron(cron)) {
throw new Error(`Invalid cron expression: ${cron}`);
}
@@ -45,7 +44,6 @@ export class JobManager implements IJobManager {
logger.info(`Disabling cron job name="${name}"`);
const job = this.jobGroup.getJobRegistry().get(name);
if (!job) throw new Error(`Job ${name} not found`);
if (job.cronExpression === "never") throw new Error(`Job ${name} cannot be disabled as it is set to "never"`);
await this.updateConfigurationAsync(name, { isEnabled: false });
await this.jobGroup.stopAsync(name);
+3 -1
View File
@@ -1,5 +1,5 @@
// This import has to be the first import in the file so that the agent is overridden before any other modules are imported.
import "./undici-log-agent-override";
import "./overrides";
import type { FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify";
import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
@@ -13,6 +13,7 @@ import { db } from "@homarr/db";
import { logger } from "@homarr/log";
import { JobManager } from "./job-manager";
import { onStartAsync } from "./on-start";
const server = fastify({
maxParamLength: 5000,
@@ -32,6 +33,7 @@ server.register(fastifyTRPCPlugin, {
});
void (async () => {
await onStartAsync();
await jobGroup.initializeAsync();
await jobGroup.startAllAsync();
+7
View File
@@ -0,0 +1,7 @@
import { invalidateUpdateCheckerCacheAsync } from "./invalidate-update-checker-cache";
import { cleanupSessionsAsync } from "./session-cleanup";
export async function onStartAsync() {
await cleanupSessionsAsync();
await invalidateUpdateCheckerCacheAsync();
}
@@ -0,0 +1,18 @@
import { logger } from "@homarr/log";
import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker";
const localLogger = logger.child({ module: "invalidateUpdateCheckerCache" });
/**
* Invalidates the update checker cache on startup to ensure fresh data.
* It is important as we want to avoid showing pending updates after the update to latest version.
*/
export async function invalidateUpdateCheckerCacheAsync() {
try {
const handler = updateCheckerRequestHandler.handler({});
await handler.invalidateAsync();
localLogger.debug("Update checker cache invalidated");
} catch (error) {
localLogger.error(new Error("Failed to invalidate update checker cache", { cause: error }));
}
}
@@ -0,0 +1,39 @@
import { env } from "@homarr/auth/env";
import { db, eq, inArray } from "@homarr/db";
import { sessions, users } from "@homarr/db/schema";
import { supportedAuthProviders } from "@homarr/definitions";
import { logger } from "@homarr/log";
const localLogger = logger.child({ module: "sessionCleanup" });
/**
* Deletes sessions for users that have inactive auth providers.
* Sessions from other providers are deleted so they can no longer be used.
*/
export async function cleanupSessionsAsync() {
try {
const currentAuthProviders = env.AUTH_PROVIDERS;
const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider));
const subQuery = db
.select({ id: users.id })
.from(users)
.where(inArray(users.provider, inactiveAuthProviders))
.as("sq");
const sessionsWithInactiveProviders = await db
.select({ userId: sessions.userId })
.from(sessions)
.rightJoin(subQuery, eq(sessions.userId, subQuery.id));
const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null);
await db.delete(sessions).where(inArray(sessions.userId, userIds));
if (sessionsWithInactiveProviders.length > 0) {
localLogger.info(`Deleted sessions for inactive providers count=${userIds.length}`);
} else {
localLogger.debug("No sessions to delete");
}
} catch (error) {
localLogger.error(new Error("Failed to clean up sessions", { cause: error }));
}
}
File renamed without changes.
+3 -3
View File
@@ -25,7 +25,7 @@
"@homarr/log": "workspace:^",
"@homarr/redis": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^17.2.1",
"dotenv": "^17.2.2",
"tsx": "4.20.4",
"ws": "^8.18.3"
},
@@ -34,8 +34,8 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1",
"esbuild": "^0.25.9",
"eslint": "^9.33.0",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
}
+28 -12
View File
@@ -30,6 +30,8 @@
</a>
</p>
![](img/screenshot.png)
![](img/headers/features.png)
- 🖌️ Highly customizable with an extensive drag and drop grid system
@@ -132,6 +134,13 @@
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/ical" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg" alt="iCal" width="90" height="90" />
<br/>
<p align="center">iCal</p>
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
<br/>
@@ -144,15 +153,15 @@
<br/>
<p align="center">Jellyseerr</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/lidarr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg" alt="Lidarr" width="90" height="90" />
<br/>
<p align="center">Lidarr</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/linux-server-io" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg" alt="LinuxServer.io" width="90" height="90" />
<br/>
@@ -193,15 +202,15 @@
<br/>
<p align="center">OpenMediaVault</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/opnsense" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg" alt="OPNsense" width="90" height="90" />
<br/>
<p align="center">OPNsense</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/overseerr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg" alt="Overseerr" width="90" height="90" />
<br/>
@@ -242,15 +251,15 @@
<br/>
<p align="center">qBittorrent</p>
</a>
</td>
<td align="center">
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/quay" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png" alt="Quay" width="90" height="90" />
<br/>
<p align="center">Quay</p>
</a>
</td></tr>
<tr><td align="center">
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/radarr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg" alt="Radarr" width="90" height="90" />
<br/>
@@ -291,6 +300,13 @@
<br/>
<p align="center">Transmission</p>
</a>
</td></tr>
<tr><td align="center">
<a href="https://homarr.dev/docs/integrations/truenas" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg" alt="TrueNAS" width="90" height="90" />
<br/>
<p align="center">TrueNAS</p>
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener">
Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

+25 -24
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.35.1",
"version": "1.39.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -39,28 +39,28 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^11.0.4",
"@semantic-release/github": "^11.0.6",
"@semantic-release/npm": "^12.0.2",
"@semantic-release/release-notes-generator": "^14.0.3",
"@testcontainers/redis": "^11.5.1",
"@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.6.0",
"@turbo/gen": "^2.5.6",
"@vitejs/plugin-react": "^5.0.1",
"@vitejs/plugin-react": "^5.0.3",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"conventional-changelog-conventionalcommits": "^9.1.0",
"cross-env": "^10.0.0",
"jsdom": "^26.1.0",
"jsdom": "^27.0.0",
"prettier": "^3.6.2",
"semantic-release": "^24.2.7",
"testcontainers": "^11.5.1",
"semantic-release": "^24.2.9",
"testcontainers": "^11.6.0",
"turbo": "^2.5.6",
"typescript": "^5.9.2",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
},
"packageManager": "pnpm@10.15.0",
"packageManager": "pnpm@10.17.0",
"engines": {
"node": ">=22.18.0"
"node": ">=22.19.0"
},
"pnpm": {
"onlyBuiltDependencies": [
@@ -75,24 +75,25 @@
"tree-sitter-json"
],
"overrides": {
"@babel/helpers@<7.26.10": ">=7.26.10",
"@babel/runtime@<7.26.10": ">=7.26.10",
"axios@>=1.0.0 <1.8.2": ">=1.8.2",
"brace-expansion@>=2.0.0 <=2.0.1": ">=2.0.2",
"brace-expansion@>=1.0.0 <=1.1.11": ">=1.1.12",
"esbuild@<=0.24.2": ">=0.25.0",
"@babel/helpers@<7.26.10": ">=7.28.4",
"@babel/runtime@<7.26.10": ">=7.28.4",
"axios@>=1.0.0 <1.8.2": ">=1.12.2",
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.25.10",
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
"hono@<4.6.5": ">=4.6.5",
"hono@<4.6.5": ">=4.9.8",
"linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.0.9",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.5",
"prismjs@<1.30.0": ">=1.30.0",
"proxmox-api>undici": "7.14.0",
"rollup@>=4.0.0 <4.22.4": ">=4.22.4",
"proxmox-api>undici": "7.16.0",
"react-is": "^19.1.1",
"rollup@>=4.0.0 <4.22.4": ">=4.52.2",
"sha.js@<=2.4.11": ">=2.4.12",
"tar-fs@>=3.0.0 <3.0.9": ">=3.0.9",
"tar-fs@>=2.0.0 <2.1.3": ">=2.1.3",
"tmp@<=0.2.3": ">=0.2.4",
"vite@>=5.0.0 <=5.4.18": ">=5.4.19"
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
"tmp@<=0.2.3": ">=0.2.5",
"vite@>=5.0.0 <=5.4.18": ">=7.1.7"
},
"patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch",
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+8 -8
View File
@@ -42,24 +42,24 @@
"@homarr/server-settings": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.3.0",
"@tanstack/react-query": "^5.85.5",
"@trpc/client": "^11.4.4",
"@trpc/react-query": "^11.4.4",
"@trpc/server": "^11.4.4",
"@trpc/tanstack-react-query": "^11.4.4",
"@tanstack/react-query": "^5.89.0",
"@trpc/client": "^11.5.1",
"@trpc/react-query": "^11.5.1",
"@trpc/server": "^11.5.1",
"@trpc/tanstack-react-query": "^11.5.1",
"lodash.clonedeep": "^4.5.0",
"next": "15.5.0",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"superjson": "2.2.2",
"trpc-to-openapi": "^3.0.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
}
@@ -25,16 +25,6 @@ export const testConnectionAsync = async (
integrationUrl: integration.url,
});
const formSecrets = integration.secrets
.filter((secret) => secret.value !== null)
.map((secret) => ({
...secret,
// We ensured above that the value is not null
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
value: secret.value!,
source: "form" as const,
}));
const decryptedDbSecrets = dbSecrets
.map((secret) => {
try {
@@ -55,6 +45,15 @@ export const testConnectionAsync = async (
})
.filter((secret) => secret !== null);
const formSecrets = integration.secrets
.map((secret) => ({
...secret,
// If the value is not defined in the form (because we only changed other values) we use the existing value from the db if it exists
value: secret.value ?? decryptedDbSecrets.find((dbSecret) => dbSecret.kind === secret.kind)?.value ?? null,
source: "form" as const,
}))
.filter((secret): secret is SourcedIntegrationSecret<"form"> => secret.value !== null);
const sourcedSecrets = [...formSecrets, ...decryptedDbSecrets];
const secretKinds = getSecretKindOption(integration.kind, sourcedSecrets);
@@ -89,10 +88,10 @@ export const testConnectionAsync = async (
return result;
};
interface SourcedIntegrationSecret {
interface SourcedIntegrationSecret<TSource extends string = "db" | "form"> {
kind: IntegrationSecretKind;
value: string;
source: "db" | "form";
source: TSource;
}
const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedIntegrationSecret[]) => {
@@ -111,7 +110,9 @@ const getSecretKindOption = (kind: IntegrationKind, sourcedSecrets: SourcedInteg
}
const onlyFormSecretsKindOptions = matchingSecretKindOptions.filter((secretKinds) =>
sourcedSecrets.filter((secret) => secretKinds.includes(secret.kind)).every((secret) => secret.source === "form"),
secretKinds.every((secretKind) =>
sourcedSecrets.find((secret) => secret.kind === secretKind && secret.source === "form"),
),
);
if (onlyFormSecretsKindOptions.length >= 1) {
@@ -184,12 +184,23 @@ function getProviders(versionInfo: VersionInfo, nodes: V1NodeList) {
if (versionInfo.gitVersion.includes("aks")) providers.add("AKS");
nodes.items.forEach((node) => {
const nodeProviderLabel =
node.metadata?.labels?.["node.kubernetes.io/instance-type"] ?? node.metadata?.labels?.provider ?? "";
const labels = node.metadata?.labels ?? {};
const nodeProviderLabel = labels["node.kubernetes.io/instance-type"] ?? labels.provider ?? "";
if (nodeProviderLabel.includes("aws")) providers.add("EKS");
if (nodeProviderLabel.includes("azure")) providers.add("AKS");
if (nodeProviderLabel.includes("gce")) providers.add("GKE");
if (nodeProviderLabel.includes("k3s")) providers.add("k3s");
const nodeInfo = node.status?.nodeInfo;
if (nodeInfo) {
const osImage = nodeInfo.osImage.toLowerCase();
const kernelVersion = nodeInfo.kernelVersion.toLowerCase();
if (osImage.includes("talos") || kernelVersion.includes("talos")) {
providers.add("Talos");
}
}
});
return Array.from(providers).join(", ");
@@ -265,4 +265,77 @@ describe("testConnectionAsync should run test connection of integration", () =>
],
});
});
test("with input of existing github app", async () => {
// Arrange
const factorySpy = vi.spyOn(homarrIntegrations, "createIntegrationAsync");
const optionsSpy = vi.spyOn(homarrDefinitions, "getAllSecretKindOptions");
factorySpy.mockReturnValue(
Promise.resolve({
testConnectionAsync: async () => await Promise.resolve({ success: true }),
} as homarrIntegrations.PiHoleIntegrationV6),
);
optionsSpy.mockReturnValue([[], ["githubAppId", "githubInstallationId", "privateKey"]]);
const integration = {
id: "new",
name: "GitHub",
url: "https://api.github.com",
kind: "github" as const,
secrets: [
{
kind: "githubAppId" as const,
value: "345",
},
{
kind: "githubInstallationId" as const,
value: "456",
},
{
kind: "privateKey" as const,
value: null,
},
],
};
const dbSecrets = [
{
kind: "githubAppId" as const,
value: "123.encrypted" as const,
},
{
kind: "githubInstallationId" as const,
value: "234.encrypted" as const,
},
{
kind: "privateKey" as const,
value: "privateKey.encrypted" as const,
},
];
// Act
await testConnectionAsync(integration, dbSecrets);
// Assert
expect(factorySpy).toHaveBeenCalledWith({
id: "new",
name: "GitHub",
url: "https://api.github.com",
kind: "github" as const,
decryptedSecrets: [
expect.objectContaining({
kind: "githubAppId",
value: "345",
}),
expect.objectContaining({
kind: "githubInstallationId",
value: "456",
}),
expect.objectContaining({
kind: "privateKey",
value: "privateKey",
}),
],
});
});
});
+51 -4
View File
@@ -1,6 +1,11 @@
import { observable } from "@trpc/server/observable";
import { z } from "zod/v4";
import type { Modify } from "@homarr/common/types";
import type { Integration } from "@homarr/db/schema";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import type { CalendarEvent } from "@homarr/integrations/types";
import { radarrReleaseTypes } from "@homarr/integrations/types";
import { calendarMonthRequestHandler } from "@homarr/request-handler/calendar";
@@ -19,14 +24,56 @@ export const calendarRouter = createTRPCRouter({
)
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.query(async ({ ctx, input }) => {
const results = await Promise.all(
return await Promise.all(
ctx.integrations.map(async (integration) => {
const innerHandler = calendarMonthRequestHandler.handler(integration, input);
const { integrationIds: _integrationIds, ...handlerInput } = input;
const innerHandler = calendarMonthRequestHandler.handler(integration, handlerInput);
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
return {
events: data,
integration: {
id: integration.id,
name: integration.name,
kind: integration.kind,
},
};
}),
);
return results.flat();
}),
subscribeToEvents: publicProcedure
.input(
z.object({
year: z.number(),
month: z.number(),
releaseType: z.array(z.enum(radarrReleaseTypes)),
showUnmonitored: z.boolean(),
}),
)
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
.subscription(({ ctx, input }) => {
return observable<{
integration: Modify<Integration, { kind: IntegrationKindByCategory<"calendar"> }>;
events: CalendarEvent[];
}>((emit) => {
const unsubscribes: (() => void)[] = [];
for (const integrationWithSecrets of ctx.integrations) {
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
const { integrationIds: _integrationIds, ...handlerInput } = input;
const innerHandler = calendarMonthRequestHandler.handler(integrationWithSecrets, handlerInput);
const unsubscribe = innerHandler.subscribe((events) => {
emit.next({
integration,
events,
});
});
unsubscribes.push(unsubscribe);
}
return () => {
unsubscribes.forEach((unsubscribe) => {
unsubscribe();
});
};
});
}),
});
@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
export const healthMonitoringRouter = createTRPCRouter({
getSystemHealthStatus: publicProcedure
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "mock"))
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock"))
.query(async ({ ctx }) => {
return await Promise.all(
ctx.integrations.map(async (integration) => {
@@ -26,7 +26,7 @@ export const healthMonitoringRouter = createTRPCRouter({
);
}),
subscribeSystemHealthStatus: publicProcedure
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "mock"))
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock"))
.subscription(({ ctx }) => {
return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => {
const unsubscribes: (() => void)[] = [];
+15 -39
View File
@@ -1,6 +1,8 @@
import { observable } from "@trpc/server/observable";
import { z } from "zod/v4";
import { fetchWithTimeout } from "@homarr/common";
import type { Weather } from "@homarr/request-handler/weather";
import { weatherRequestHandler } from "@homarr/request-handler/weather";
import { createTRPCRouter, publicProcedure } from "../../trpc";
@@ -9,45 +11,19 @@ const atLocationInput = z.object({
latitude: z.number(),
});
const atLocationOutput = z.object({
current_weather: z.object({
weathercode: z.number(),
temperature: z.number(),
windspeed: z.number(),
}),
daily: z.object({
time: z.array(z.string()),
weathercode: z.array(z.number()),
temperature_2m_max: z.array(z.number()),
temperature_2m_min: z.array(z.number()),
sunrise: z.array(z.string()),
sunset: z.array(z.string()),
wind_speed_10m_max: z.array(z.number()),
wind_gusts_10m_max: z.array(z.number()),
}),
});
export const weatherRouter = createTRPCRouter({
atLocation: publicProcedure.input(atLocationInput).query(async ({ input }) => {
const res = await fetchWithTimeout(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`,
);
const json: unknown = await res.json();
const weather = await atLocationOutput.parseAsync(json);
return {
current: weather.current_weather,
daily: weather.daily.time.map((value, index) => {
return {
time: value,
weatherCode: weather.daily.weathercode[index] ?? 404,
maxTemp: weather.daily.temperature_2m_max[index],
minTemp: weather.daily.temperature_2m_min[index],
sunrise: weather.daily.sunrise[index],
sunset: weather.daily.sunset[index],
maxWindSpeed: weather.daily.wind_speed_10m_max[index],
maxWindGusts: weather.daily.wind_gusts_10m_max[index],
};
}),
};
const handler = weatherRequestHandler.handler(input);
return await handler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then((result) => result.data);
}),
subscribeAtLocation: publicProcedure.input(atLocationInput).subscription(({ input }) => {
return observable<Weather>((emit) => {
const handler = weatherRequestHandler.handler(input);
const unsubscribe = handler.subscribe((data) => {
emit.next(data);
});
return unsubscribe;
});
}),
});
+3 -3
View File
@@ -35,11 +35,11 @@
"bcrypt": "^6.0.0",
"cookies": "^0.9.1",
"ldapts": "8.0.9",
"next": "15.5.0",
"next": "15.5.3",
"next-auth": "5.0.0-beta.29",
"react": "19.1.1",
"react-dom": "19.1.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -47,7 +47,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"@types/cookies": "0.9.1",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
}
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+2 -2
View File
@@ -24,13 +24,13 @@
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"undici": "7.14.0"
"undici": "7.16.0"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+3 -3
View File
@@ -28,14 +28,14 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"dotenv": "^17.2.1"
"dotenv": "^17.2.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"esbuild": "^0.25.9",
"eslint": "^9.33.0",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+2
View File
@@ -24,10 +24,12 @@ export const env = createEnv({
message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`,
}),
NO_EXTERNAL_CONNECTION: createBooleanSchema(false),
ENABLE_DNS_CACHING: createBooleanSchema(false),
},
runtimeEnv: {
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
NODE_ENV: process.env.NODE_ENV,
NO_EXTERNAL_CONNECTION: process.env.NO_EXTERNAL_CONNECTION,
ENABLE_DNS_CACHING: process.env.ENABLE_DNS_CACHING,
},
});
+9 -6
View File
@@ -8,6 +8,7 @@
".": "./index.ts",
"./types": "./src/types.ts",
"./server": "./src/server.ts",
"./init-dns": "./src/dns.ts",
"./client": "./src/client.ts",
"./env": "./env.ts"
},
@@ -29,19 +30,21 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^2.2.2",
"dayjs": "^1.11.13",
"next": "15.5.0",
"dayjs": "^1.11.18",
"dns-caching": "^0.2.7",
"next": "15.5.3",
"octokit": "^5.0.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"undici": "7.14.0",
"zod": "^4.0.17",
"zod-validation-error": "^4.0.1"
"undici": "7.16.0",
"zod": "^4.1.11",
"zod-validation-error": "^4.0.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+26
View File
@@ -0,0 +1,26 @@
import { DnsCacheManager } from "dns-caching";
import { logger } from "@homarr/log";
import { env } from "../env";
// Add global type augmentation for homarr
declare global {
var homarr: {
dnsCacheManager?: DnsCacheManager;
// add other properties if needed
};
}
// Initialize global.homarr if not present
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
global.homarr ??= {};
global.homarr.dnsCacheManager ??= new DnsCacheManager({
cacheMaxEntries: 1000,
forceMinTtl: 5 * 60 * 1000, // 5 minutes
logger,
});
if (env.ENABLE_DNS_CACHING) {
global.homarr.dnsCacheManager.initialize();
}
@@ -3,3 +3,4 @@ export * from "./fetch-http-error-handler";
export * from "./ofetch-http-error-handler";
export * from "./axios-http-error-handler";
export * from "./tsdav-http-error-handler";
export * from "./octokit-http-error-handler";
@@ -0,0 +1,24 @@
import { RequestError as OctokitRequestError } from "octokit";
import type { AnyRequestError } from "../request-error";
import { ResponseError } from "../response-error";
import { HttpErrorHandler } from "./http-error-handler";
export class OctokitHttpErrorHandler extends HttpErrorHandler {
/**
* I wasn't able to get a request error triggered. Therefore we ignore them for now
* and just forward them as unknown errors
*/
handleRequestError(_: unknown): AnyRequestError | undefined {
return undefined;
}
handleResponseError(error: unknown): ResponseError | undefined {
if (!(error instanceof OctokitRequestError)) return undefined;
return new ResponseError({
status: error.status,
url: error.response?.url,
});
}
}
+8 -1
View File
@@ -3,13 +3,20 @@ import { Agent } from "undici";
import { logger } from "@homarr/log";
// The below import statement initializes dns-caching
import "./dns";
export class LoggingAgent extends Agent {
constructor(...props: ConstructorParameters<typeof Agent>) {
super(...props);
}
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
const url = new URL(`${options.origin as string}${options.path}`);
const path = options.path
.split("/")
.map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment))
.join("/");
const url = new URL(`${options.origin as string}${path}`);
// The below code should prevent sensitive data from being logged as
// some integrations use query parameters for auth
+6 -2
View File
@@ -11,13 +11,17 @@ const calculateTimeAgo = (timestamp: Date) => {
};
export const useTimeAgo = (timestamp: Date, updateFrequency = 1000) => {
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp));
const [timeAgo, setTimeAgo] = useState(() => calculateTimeAgo(timestamp));
useEffect(() => {
setTimeAgo(calculateTimeAgo(timestamp));
}, [timestamp]);
useEffect(() => {
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), updateFrequency);
return () => clearInterval(intervalId); // clear interval on hook unmount
}, [timestamp]);
}, [timestamp, updateFrequency]);
return timeAgo;
};
@@ -66,6 +66,7 @@ describe("LoggingAgent should log all requests", () => {
["/?one=a1&two=b2&three=c3", `/?one=${REDACTED}&two=${REDACTED}&three=${REDACTED}`],
["/?numberWith13Chars=1234567890123", `/?numberWith13Chars=${REDACTED}`],
[`/?stringWith13Chars=${"a".repeat(13)}`, `/?stringWith13Chars=${REDACTED}`],
[`/${"a".repeat(32)}/?param=123`, `/${REDACTED}/?param=123`],
])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => {
// Arrange
const infoLogSpy = vi.spyOn(logger, "debug");
+2 -2
View File
@@ -26,13 +26,13 @@
"dependencies": {
"@t3-oss/env-nextjs": "^0.13.8",
"ioredis": "5.7.0",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
@@ -15,6 +15,7 @@ export const createRedisClient = () =>
...defaultRedisOptions,
host: redisEnv.HOST,
port: redisEnv.PORT,
db: redisEnv.DATABASE_INDEX,
tls: redisEnv.TLS_CA
? {
ca: redisEnv.TLS_CA,
@@ -12,6 +12,7 @@ export const redisEnv = createEnv({
TLS_CA: z.string().optional(),
USERNAME: z.string().optional(),
PASSWORD: z.string().optional(),
DATABASE_INDEX: z.coerce.number().optional(),
},
runtimeEnv: runtimeEnvWithPrefix("REDIS_"),
});
+7 -7
View File
@@ -29,21 +29,21 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@tanstack/react-query": "^5.85.5",
"@trpc/client": "^11.4.4",
"@trpc/server": "^11.4.4",
"@trpc/tanstack-react-query": "^11.4.4",
"@tanstack/react-query": "^5.89.0",
"@trpc/client": "^11.5.1",
"@trpc/server": "^11.5.1",
"@trpc/tanstack-react-query": "^11.5.1",
"node-cron": "^4.2.1",
"react": "19.1.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"@types/react": "19.1.10",
"eslint": "^9.33.0",
"@types/react": "19.1.13",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-cron": "^3.0.11",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+2 -6
View File
@@ -72,8 +72,6 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
where: (cronJobConfigurations, { eq }) => eq(cronJobConfigurations.name, name),
});
if (defaultCronExpression === "never") return null;
const scheduledTask = createTask(
configuration?.cronExpression ?? defaultCronExpression,
() => void catchingCallbackAsync(),
@@ -120,7 +118,7 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
options: CreateCronJobOptions = { runOnStart: false },
) => {
creatorOptions.logger.logDebug(`Validating cron expression '${defaultCronExpression}' for job: ${name}`);
if (defaultCronExpression !== "never" && !validate(defaultCronExpression)) {
if (!validate(defaultCronExpression)) {
throw new Error(`Invalid cron expression '${defaultCronExpression}' for job '${name}'`);
}
creatorOptions.logger.logDebug(`Cron job expression '${defaultCronExpression}' for job ${name} is valid`);
@@ -132,8 +130,6 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
// This is a type guard to check if the cron expression is valid and give the user a type hint
return returnValue as unknown as ValidateCron<TExpression> extends true
? typeof returnValue
: TExpression extends "never"
? typeof returnValue
: "Invalid cron expression";
: "Invalid cron expression";
};
};
@@ -8,4 +8,3 @@ export const EVERY_10_MINUTES = checkCron("*/10 * * * *") satisfies string;
export const EVERY_HOUR = checkCron("0 * * * *") satisfies string;
export const EVERY_DAY = checkCron("0 0 * * */1") satisfies string;
export const EVERY_WEEK = checkCron("0 0 * * 1") satisfies string;
export const NEVER = "never";
-1
View File
@@ -45,7 +45,6 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
}
const scheduledTask = await job.createTaskAsync();
if (!scheduledTask) continue;
tasks.set(job.name, scheduledTask);
}
+1 -1
View File
@@ -44,7 +44,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+2 -2
View File
@@ -21,8 +21,8 @@ import { refreshNotificationsJob } from "./jobs/integrations/notifications";
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
import { pingJob } from "./jobs/ping";
import { rssFeedsJob } from "./jobs/rss-feeds";
import { sessionCleanupJob } from "./jobs/session-cleanup";
import { updateCheckerJob } from "./jobs/update-checker";
import { weatherJob } from "./jobs/weather";
import { createCronJobGroup } from "./lib";
export const jobGroup = createCronJobGroup({
@@ -39,7 +39,6 @@ export const jobGroup = createCronJobGroup({
rssFeeds: rssFeedsJob,
indexerManager: indexerManagerJob,
healthMonitoring: healthMonitoringJob,
sessionCleanup: sessionCleanupJob,
updateChecker: updateCheckerJob,
mediaTranscoding: mediaTranscodingJob,
minecraftServerStatus: minecraftServerStatusJob,
@@ -50,6 +49,7 @@ export const jobGroup = createCronJobGroup({
firewallVersion: firewallVersionJob,
firewallInterfaces: firewallInterfacesJob,
refreshNotifications: refreshNotificationsJob,
weather: weatherJob,
});
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
@@ -1,38 +0,0 @@
import { env } from "@homarr/auth/env";
import { NEVER } from "@homarr/cron-jobs-core/expressions";
import { db, eq, inArray } from "@homarr/db";
import { sessions, users } from "@homarr/db/schema";
import { supportedAuthProviders } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { createCronJob } from "../lib";
/**
* Deletes sessions for users that have inactive auth providers.
* Sessions from other providers are deleted so they can no longer be used.
*/
export const sessionCleanupJob = createCronJob("sessionCleanup", NEVER, {
runOnStart: true,
}).withCallback(async () => {
const currentAuthProviders = env.AUTH_PROVIDERS;
const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider));
const subQuery = db
.select({ id: users.id })
.from(users)
.where(inArray(users.provider, inactiveAuthProviders))
.as("sq");
const sessionsWithInactiveProviders = await db
.select({ userId: sessions.userId })
.from(sessions)
.rightJoin(subQuery, eq(sessions.userId, subQuery.id));
const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null);
await db.delete(sessions).where(inArray(sessions.userId, userIds));
if (sessionsWithInactiveProviders.length > 0) {
logger.info(`Deleted sessions for inactive providers count=${userIds.length}`);
} else {
logger.debug("No sessions to delete");
}
});
+33
View File
@@ -0,0 +1,33 @@
import SuperJSON from "superjson";
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db";
import { items } from "@homarr/db/schema";
import { logger } from "@homarr/log";
import { weatherRequestHandler } from "@homarr/request-handler/weather";
import type { WidgetComponentProps } from "../../../widgets";
import { createCronJob } from "../lib";
export const weatherJob = createCronJob("weather", EVERY_10_MINUTES).withCallback(async () => {
const weatherItems = await db.query.items.findMany({
where: eq(items.kind, "weather"),
});
const parsedItems = weatherItems.map((item) => ({
id: item.id,
options: SuperJSON.parse<WidgetComponentProps<"weather">["options"]>(item.options),
}));
for (const item of parsedItems) {
try {
const innerHandler = weatherRequestHandler.handler({
longitude: item.options.location.longitude,
latitude: item.options.location.latitude,
});
await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
} catch (error) {
logger.error("Failed to update weather", { id: item.id, error });
}
}
});
+9 -9
View File
@@ -49,16 +49,16 @@
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/log": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.2.5",
"@mantine/core": "^8.3.1",
"@paralleldrive/cuid2": "^2.2.2",
"@testcontainers/mysql": "^11.5.1",
"@testcontainers/postgresql": "^11.4.0",
"@testcontainers/mysql": "^11.6.0",
"@testcontainers/postgresql": "^11.6.0",
"better-sqlite3": "^12.2.0",
"dotenv": "^17.2.1",
"dotenv": "^17.2.2",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.4",
"drizzle-orm": "^0.44.5",
"drizzle-zod": "^0.8.3",
"mysql2": "3.14.3",
"mysql2": "3.15.0",
"pg": "^8.16.3",
"superjson": "2.2.2"
},
@@ -67,10 +67,10 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.15.4",
"@types/pg": "^8.15.5",
"dotenv-cli": "^10.0.0",
"esbuild": "^0.25.9",
"eslint": "^9.33.0",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"prettier": "^3.6.2",
"tsx": "4.20.4",
"typescript": "^5.9.2"
+2 -2
View File
@@ -25,13 +25,13 @@
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"fast-xml-parser": "^5.2.5",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"tsx": "4.20.4",
"typescript": "^5.9.2"
}
@@ -87,6 +87,7 @@ export type HomarrDocumentationPath =
| "/docs/tags/programming"
| "/docs/tags/proxy"
| "/docs/tags/puid"
| "/docs/tags/redis"
| "/docs/tags/responsive"
| "/docs/tags/roles"
| "/docs/tags/search"
@@ -159,6 +160,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/github"
| "/docs/integrations/gitlab"
| "/docs/integrations/home-assistant"
| "/docs/integrations/ical"
| "/docs/integrations/jellyfin"
| "/docs/integrations/jellyseerr"
| "/docs/integrations/kubernetes"
@@ -183,6 +185,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/sonarr"
| "/docs/integrations/tdarr"
| "/docs/integrations/transmission"
| "/docs/integrations/truenas"
| "/docs/integrations/unifi-controller"
| "/docs/management/api"
| "/docs/management/apps"
+31 -13
View File
@@ -4,16 +4,20 @@ import type { AtLeastOneOf } from "@homarr/common/types";
import { createDocumentationLink } from "./docs";
export const integrationSecretKindObject = {
apiKey: { isPublic: false },
username: { isPublic: true },
password: { isPublic: false },
tokenId: { isPublic: true },
realm: { isPublic: true },
personalAccessToken: { isPublic: false },
topic: { isPublic: true },
opnsenseApiKey: { isPublic: false },
opnsenseApiSecret: { isPublic: false },
} satisfies Record<string, { isPublic: boolean }>;
apiKey: { isPublic: false, multiline: false },
username: { isPublic: true, multiline: false },
password: { isPublic: false, multiline: false },
tokenId: { isPublic: true, multiline: false },
realm: { isPublic: true, multiline: false },
personalAccessToken: { isPublic: false, multiline: false },
topic: { isPublic: true, multiline: false },
opnsenseApiKey: { isPublic: false, multiline: false },
opnsenseApiSecret: { isPublic: false, multiline: false },
url: { isPublic: false, multiline: false },
privateKey: { isPublic: false, multiline: true },
githubAppId: { isPublic: true, multiline: false },
githubInstallationId: { isPublic: true, multiline: false },
} satisfies Record<string, { isPublic: boolean; multiline: boolean }>;
export const integrationSecretKinds = objectKeys(integrationSecretKindObject);
@@ -157,7 +161,7 @@ export const integrationDefs = {
name: "Home Assistant",
secretKinds: [["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
category: ["smartHomeServer"],
category: ["smartHomeServer", "calendar"],
documentationUrl: createDocumentationLink("/docs/integrations/home-assistant"),
},
openmediavault: {
@@ -211,7 +215,7 @@ export const integrationDefs = {
},
github: {
name: "Github",
secretKinds: [[], ["personalAccessToken"]],
secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
category: ["releasesProvider"],
defaultUrl: "https://api.github.com",
@@ -259,7 +263,7 @@ export const integrationDefs = {
},
gitHubContainerRegistry: {
name: "GitHub Container Registry",
secretKinds: [[], ["personalAccessToken"]],
secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
category: ["releasesProvider"],
defaultUrl: "https://api.github.com",
@@ -280,6 +284,20 @@ export const integrationDefs = {
category: ["notifications"],
documentationUrl: createDocumentationLink("/docs/integrations/ntfy"),
},
ical: {
name: "iCal",
secretKinds: [["url"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg",
category: ["calendar"],
documentationUrl: createDocumentationLink("/docs/integrations/ical"),
},
truenas: {
name: "TrueNAS",
secretKinds: [["username", "password"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg",
category: ["healthMonitoring"],
documentationUrl: createDocumentationLink("/docs/integrations/truenas"),
},
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
mock: {
name: "Mock",
+3 -3
View File
@@ -26,14 +26,14 @@
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0",
"dockerode": "^4.0.7"
"dockerode": "^4.0.8"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/dockerode": "^3.3.42",
"eslint": "^9.33.0",
"@types/dockerode": "^3.3.44",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+3 -3
View File
@@ -26,15 +26,15 @@
"@homarr/common": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.2.5",
"@mantine/form": "^8.3.1",
"mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+3 -3
View File
@@ -29,15 +29,15 @@
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.2.5",
"@mantine/core": "^8.3.1",
"react": "19.1.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/bcrypt": "6.0.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+10 -8
View File
@@ -25,10 +25,10 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@ctrl/deluge": "^7.1.1",
"@ctrl/qbittorrent": "^9.6.0",
"@ctrl/transmission": "^7.2.0",
"@gitbeaker/rest": "^43.4.0",
"@ctrl/deluge": "^7.3.0",
"@ctrl/qbittorrent": "^9.8.0",
"@ctrl/transmission": "^7.4.0",
"@gitbeaker/rest": "^43.5.0",
"@homarr/certificates": "workspace:^0.1.0",
"@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0",
@@ -40,14 +40,16 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@jellyfin/sdk": "^0.11.0",
"@octokit/auth-app": "^8.1.0",
"ical.js": "^2.2.1",
"maria2": "^0.4.1",
"node-ical": "^0.20.1",
"node-ical": "^0.21.0",
"octokit": "^5.0.3",
"proxmox-api": "1.1.1",
"tsdav": "^2.1.5",
"undici": "7.14.0",
"undici": "7.16.0",
"xml2js": "^0.6.2",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -55,7 +57,7 @@
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/node-unifi": "^2.5.1",
"@types/xml2js": "^0.4.14",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
@@ -23,7 +23,6 @@ export const statusResponseSchema = z.object({
dns_addresses: z.array(z.string()),
dns_port: z.number().positive(),
http_port: z.number().positive(),
protection_disabled_duration: z.number(),
protection_enabled: z.boolean(),
dhcp_available: z.boolean(),
running: z.boolean(),
@@ -18,6 +18,7 @@ import { GitHubContainerRegistryIntegration } from "../github-container-registry
import { GithubIntegration } from "../github/github-integration";
import { GitlabIntegration } from "../gitlab/gitlab-integration";
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
import { ICalIntegration } from "../ical/ical-integration";
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
@@ -38,6 +39,7 @@ import { PlexIntegration } from "../plex/plex-integration";
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
import { QuayIntegration } from "../quay/quay-integration";
import { TrueNasIntegration } from "../truenas/truenas-integration";
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
import type { Integration, IntegrationInput } from "./integration";
@@ -111,9 +113,11 @@ export const integrationCreators = {
codeberg: CodebergIntegration,
linuxServerIO: LinuxServerIOIntegration,
gitHubContainerRegistry: GitHubContainerRegistryIntegration,
ical: ICalIntegration,
quay: QuayIntegration,
ntfy: NTFYIntegration,
mock: MockIntegration,
truenas: TrueNasIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
@@ -1,6 +1,7 @@
import {
AxiosHttpErrorHandler,
FetchHttpErrorHandler,
OctokitHttpErrorHandler,
OFetchHttpErrorHandler,
TsdavHttpErrorHandler,
} from "@homarr/common/server";
@@ -11,3 +12,4 @@ export const integrationFetchHttpErrorHandler = new IntegrationHttpErrorHandler(
export const integrationOFetchHttpErrorHandler = new IntegrationHttpErrorHandler(new OFetchHttpErrorHandler());
export const integrationAxiosHttpErrorHandler = new IntegrationHttpErrorHandler(new AxiosHttpErrorHandler());
export const integrationTsdavHttpErrorHandler = new IntegrationHttpErrorHandler(new TsdavHttpErrorHandler());
export const integrationOctokitHttpErrorHandler = new IntegrationHttpErrorHandler(new OctokitHttpErrorHandler());
@@ -1,11 +1,14 @@
import { createAppAuth } from "@octokit/auth-app";
import { Octokit, RequestError } from "octokit";
import type { fetch } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import { integrationOctokitHttpErrorHandler } from "../base/errors/http";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
@@ -18,23 +21,21 @@ import type {
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
@HandleIntegrationErrors([integrationOctokitHttpErrorHandler])
export class GitHubContainerRegistryIntegration extends Integration implements ReleasesProviderIntegration {
private static readonly userAgent = "Homarr-Lab/Homarr:GitHubContainerRegistryIntegration";
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const headers: RequestInit["headers"] = {
"User-Agent": GitHubContainerRegistryIntegration.userAgent,
};
const api = this.getApi(input.fetchAsync);
if (this.hasSecretValue("personalAccessToken"))
headers.Authorization = `Bearer ${this.getSecretValue("personalAccessToken")}`;
const response = await input.fetchAsync(this.url("/octocat"), {
headers,
});
if (!response.ok) {
return TestConnectionError.StatusResult(response);
if (this.hasSecretValue("personalAccessToken")) {
await api.rest.users.getAuthenticated();
} else if (this.hasSecretValue("githubAppId")) {
await api.rest.apps.getInstallation({
installation_id: Number(this.getSecretValue("githubInstallationId")),
});
} else {
await api.request("GET /octocat");
}
return {
@@ -131,15 +132,38 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
}
}
private getApi() {
private getAuthProperties(): Pick<OctokitOptions, "auth" | "authStrategy"> {
if (this.hasSecretValue("personalAccessToken"))
return {
auth: this.getSecretValue("personalAccessToken"),
};
if (this.hasSecretValue("githubAppId"))
return {
authStrategy: createAppAuth,
auth: {
appId: this.getSecretValue("githubAppId"),
installationId: this.getSecretValue("githubInstallationId"),
privateKey: this.getSecretValue("privateKey"),
} satisfies Parameters<typeof createAppAuth>[0],
};
return {};
}
private getApi(customFetch?: typeof fetch) {
return new Octokit({
baseUrl: this.url("/").origin,
request: {
fetch: fetchWithTrustedCertificatesAsync,
fetch: customFetch ?? fetchWithTrustedCertificatesAsync,
},
userAgent: GitHubContainerRegistryIntegration.userAgent,
throttle: { enabled: false }, // Disable throttling for this integration, Octokit will retry by default after a set time, thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
...(this.hasSecretValue("personalAccessToken") ? { auth: this.getSecretValue("personalAccessToken") } : {}),
// Disable throttling for this integration, Octokit will retry by default after a set time,
// thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
throttle: { enabled: false },
...this.getAuthProperties(),
});
}
}
type OctokitOptions = Exclude<ConstructorParameters<typeof Octokit>[0], undefined>;
@@ -1,11 +1,14 @@
import { Octokit, RequestError } from "octokit";
import { createAppAuth } from "@octokit/auth-app";
import { Octokit, RequestError as OctokitRequestError } from "octokit";
import type { fetch } from "undici";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { logger } from "@homarr/log";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import { integrationOctokitHttpErrorHandler } from "../base/errors/http";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
@@ -18,23 +21,21 @@ import type {
const localLogger = logger.child({ module: "GithubIntegration" });
@HandleIntegrationErrors([integrationOctokitHttpErrorHandler])
export class GithubIntegration extends Integration implements ReleasesProviderIntegration {
private static readonly userAgent = "Homarr-Lab/Homarr:GithubIntegration";
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const headers: RequestInit["headers"] = {
"User-Agent": GithubIntegration.userAgent,
};
const api = this.getApi(input.fetchAsync);
if (this.hasSecretValue("personalAccessToken"))
headers.Authorization = `Bearer ${this.getSecretValue("personalAccessToken")}`;
const response = await input.fetchAsync(this.url("/octocat"), {
headers,
});
if (!response.ok) {
return TestConnectionError.StatusResult(response);
if (this.hasSecretValue("personalAccessToken")) {
await api.rest.users.getAuthenticated();
} else if (this.hasSecretValue("githubAppId")) {
await api.rest.apps.getInstallation({
installation_id: Number(this.getSecretValue("githubInstallationId")),
});
} else {
await api.request("GET /octocat");
}
return {
@@ -91,7 +92,7 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
return getLatestRelease(releasesProviderResponse, repository, details);
} catch (error) {
const errorMessage = error instanceof RequestError ? error.message : String(error);
const errorMessage = error instanceof OctokitRequestError ? error.message : String(error);
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, {
owner,
@@ -131,21 +132,44 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
localLogger.warn(`Failed to get details for ${owner}\\${name} with Github integration`, {
owner,
name,
error: error instanceof RequestError ? error.message : String(error),
error: error instanceof OctokitRequestError ? error.message : String(error),
});
return undefined;
}
}
private getApi() {
private getAuthProperties(): Pick<OctokitOptions, "auth" | "authStrategy"> {
if (this.hasSecretValue("personalAccessToken"))
return {
auth: this.getSecretValue("personalAccessToken"),
};
if (this.hasSecretValue("githubAppId"))
return {
authStrategy: createAppAuth,
auth: {
appId: this.getSecretValue("githubAppId"),
installationId: this.getSecretValue("githubInstallationId"),
privateKey: this.getSecretValue("privateKey"),
} satisfies Parameters<typeof createAppAuth>[0],
};
return {};
}
private getApi(customFetch?: typeof fetch) {
return new Octokit({
baseUrl: this.url("/").origin,
request: {
fetch: fetchWithTrustedCertificatesAsync,
fetch: customFetch ?? fetchWithTrustedCertificatesAsync,
},
userAgent: GithubIntegration.userAgent,
throttle: { enabled: false }, // Disable throttling for this integration, Octokit will retry by default after a set time, thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
...(this.hasSecretValue("personalAccessToken") ? { auth: this.getSecretValue("personalAccessToken") } : {}),
// Disable throttling for this integration, Octokit will retry by default after a set time,
// thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
throttle: { enabled: false },
...this.getAuthProperties(),
});
}
}
type OctokitOptions = Exclude<ConstructorParameters<typeof Octokit>[0], undefined>;
@@ -1,14 +1,19 @@
import z from "zod";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import { ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
import type { ISmartHomeIntegration } from "../interfaces/smart-home/smart-home-integration";
import { entityStateSchema } from "./homeassistant-types";
import type { CalendarEvent } from "../types";
import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homeassistant-types";
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration {
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration {
public async getEntityStateAsync(entityId: string) {
try {
const response = await this.getAsync(`/api/states/${entityId}`);
@@ -62,6 +67,35 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
}
}
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
const calendarsResponse = await this.getAsync("/api/calendars");
if (!calendarsResponse.ok) throw new ResponseError(calendarsResponse);
const calendars = await calendarsSchema.parseAsync(await calendarsResponse.json());
return await Promise.all(
calendars.map(async (calendar) => {
const response = await this.getAsync(`/api/calendars/${calendar.entity_id}`, { start, end });
if (!response.ok) throw new ResponseError(response);
return await z.array(calendarEventSchema).parseAsync(await response.json());
}),
).then((events) =>
events.flat().map(
(event): CalendarEvent => ({
title: event.summary,
subTitle: null,
description: event.description,
// If not reseting it to 0 o'clock it uses utc time and therefore shows as 2 o'clock
startDate: "date" in event.start ? new Date(`${event.start.date}T00:00:00`) : new Date(event.start.dateTime),
endDate: "date" in event.end ? new Date(`${event.end.date}T00:00:00`) : new Date(event.end.dateTime),
image: null,
indicatorColor: "#18bcf2",
links: [],
location: event.location,
}),
),
);
}
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(this.url("/api/config"), {
headers: this.getAuthHeaders(),
@@ -82,8 +116,8 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
* @param path full path to the API endpoint
* @returns the response from the API
*/
private async getAsync(path: `/api/${string}`) {
return await fetchWithTrustedCertificatesAsync(this.url(path), {
private async getAsync(path: `/api/${string}`, queryParams?: Record<string, string | Date | number | boolean>) {
return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), {
headers: this.getAuthHeaders(),
});
}
@@ -12,3 +12,27 @@ export const entityStateSchema = z.object({
});
export type EntityState = z.infer<typeof entityStateSchema>;
export const calendarsSchema = z.array(
z.object({
name: z.string(),
entity_id: z.string(),
}),
);
const calendarMomentSchema = z
.object({
date: z.string(),
})
.or(
z.object({
dateTime: z.string(),
}),
);
export const calendarEventSchema = z.object({
start: calendarMomentSchema,
end: calendarMomentSchema,
summary: z.string(),
description: z.string().nullable(),
location: z.string().nullable(),
});
@@ -0,0 +1,67 @@
import ICAL from "ical.js";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import { TestConnectionError } from "../base/test-connection/test-connection-error";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
export class ICalIntegration extends Integration implements ICalendarIntegration {
async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
const response = await fetchWithTrustedCertificatesAsync(super.getSecretValue("url"));
const result = await response.text();
const jcal = ICAL.parse(result) as unknown[];
const comp = new ICAL.Component(jcal);
return comp.getAllSubcomponents("vevent").reduce((prev, vevent) => {
const event = new ICAL.Event(vevent);
const startDate = event.startDate.toJSDate();
const endDate = event.endDate.toJSDate();
if (startDate > end) return prev;
if (endDate < start) return prev;
return prev.concat({
title: event.summary,
subTitle: null,
description: event.description,
startDate,
endDate,
image: null,
location: event.location,
indicatorColor: "red",
links: [],
});
}, [] as CalendarEvent[]);
}
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(super.getSecretValue("url"));
if (!response.ok) return TestConnectionError.StatusResult(response);
const result = await response.text();
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const jcal = ICAL.parse(result);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const comp = new ICAL.Component(jcal);
return comp.getAllSubcomponents("vevent").length > 0
? { success: true }
: TestConnectionError.ParseResult({
name: "Calendar parse error",
message: "No events found",
cause: new Error("No events found"),
});
} catch (error) {
return TestConnectionError.ParseResult({
name: "Calendar parse error",
message: "Failed to parse calendar",
cause: error as Error,
});
}
}
}
+2
View File
@@ -21,7 +21,9 @@ export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export { PlexIntegration } from "./plex/plex-integration";
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
export { TrueNasIntegration } from "./truenas/truenas-integration";
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
export { ICalIntegration } from "./ical/ical-integration";
// Types
export type { IntegrationInput } from "./base/integration";
@@ -1,24 +1,41 @@
export const radarrReleaseTypes = ["inCinemas", "digitalRelease", "physicalRelease"] as const;
export type RadarrReleaseType = (typeof radarrReleaseTypes)[number];
export interface CalendarEvent {
name: string;
subName: string;
date: Date;
dates?: { type: RadarrReleaseType; date: Date }[];
description?: string;
thumbnail?: string;
mediaInformation?: {
type: "audio" | "video" | "tv" | "movie";
seasonNumber?: number;
episodeNumber?: number;
};
links: {
href: string;
name: string;
color: string | undefined;
notificationColor?: string | undefined;
isDark: boolean | undefined;
logo: string;
}[];
export interface RadarrMetadata {
type: "radarr";
releaseType: RadarrReleaseType;
}
export type CalendarMetadata = RadarrMetadata;
export interface CalendarLink {
name: string;
isDark: boolean;
href: string;
color?: string;
logo?: string;
}
export interface CalendarImageBadge {
content: string;
color: string;
}
export interface CalendarImage {
src: string;
badge?: CalendarImageBadge;
aspectRatio?: { width: number; height: number };
}
export interface CalendarEvent {
title: string;
subTitle: string | null;
description: string | null;
startDate: Date;
endDate: Date | null;
image: CalendarImage | null;
location: string | null;
metadata?: CalendarMetadata;
indicatorColor: string;
links: CalendarLink[];
}
@@ -15,7 +15,7 @@ export interface SystemHealthMonitoring {
"1min": number;
"5min": number;
"15min": number;
};
} | null;
rebootRequired: boolean;
availablePkgUpdates: number;
cpuTemp: number | undefined;
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
import type { TestingResult } from "../../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
export class LidarrIntegration extends Integration implements ICalendarIntegration {
@@ -44,22 +44,28 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
const lidarrCalendarEvents = await z.array(lidarrCalendarEventSchema).parseAsync(await response.json());
return lidarrCalendarEvents.map((lidarrCalendarEvent): CalendarEvent => {
const imageSrc = this.chooseBestImage(lidarrCalendarEvent);
return {
name: lidarrCalendarEvent.title,
subName: lidarrCalendarEvent.artist.artistName,
description: lidarrCalendarEvent.overview,
thumbnail: this.chooseBestImageAsURL(lidarrCalendarEvent),
date: lidarrCalendarEvent.releaseDate,
mediaInformation: {
type: "audio",
},
title: lidarrCalendarEvent.title,
subTitle: lidarrCalendarEvent.artist.artistName,
description: lidarrCalendarEvent.overview ?? null,
startDate: lidarrCalendarEvent.releaseDate,
endDate: null,
image: imageSrc
? {
src: imageSrc.remoteUrl,
aspectRatio: { width: 7, height: 12 },
}
: null,
location: null,
indicatorColor: "cyan",
links: this.getLinksForLidarrCalendarEvent(lidarrCalendarEvent),
};
});
}
private getLinksForLidarrCalendarEvent = (event: z.infer<typeof lidarrCalendarEventSchema>) => {
const links: CalendarEvent["links"] = [];
const links: CalendarLink[] = [];
for (const link of event.artist.links) {
switch (link.name) {
@@ -70,7 +76,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
color: "#f5c518",
isDark: false,
logo: "/images/apps/vgmdb.svg",
notificationColor: "cyan",
});
break;
case "imdb":
@@ -80,7 +85,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
color: "#f5c518",
isDark: false,
logo: "/images/apps/imdb.png",
notificationColor: "cyan",
});
break;
case "last":
@@ -90,7 +94,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
color: "#cf222a",
isDark: false,
logo: "/images/apps/lastfm.svg",
notificationColor: "cyan",
});
break;
}
@@ -1,15 +1,14 @@
import { z } from "zod/v4";
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
import type { AtLeastOneOf } from "@homarr/common/types";
import { logger } from "@homarr/log";
import { Integration } from "../../base/integration";
import type { IntegrationTestingInput } from "../../base/integration";
import { Integration } from "../../base/integration";
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
import type { TestingResult } from "../../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
@@ -34,33 +33,44 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
});
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
return radarrCalendarEvents.map((radarrCalendarEvent): CalendarEvent => {
const dates = radarrReleaseTypes
.map((type) => (radarrCalendarEvent[type] ? { type, date: radarrCalendarEvent[type] } : undefined))
.filter((date) => date) as AtLeastOneOf<Exclude<CalendarEvent["dates"], undefined>[number]>;
return {
name: radarrCalendarEvent.title,
subName: radarrCalendarEvent.originalTitle,
description: radarrCalendarEvent.overview,
thumbnail: this.chooseBestImageAsURL(radarrCalendarEvent),
date: dates[0].date,
dates,
mediaInformation: {
type: "movie",
},
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
};
return radarrCalendarEvents.flatMap((radarrCalendarEvent): CalendarEvent[] => {
const imageSrc = this.chooseBestImageAsURL(radarrCalendarEvent);
return radarrReleaseTypes
.map((releaseType) => ({ type: releaseType, date: radarrCalendarEvent[releaseType] }))
.filter((item) => item.date !== undefined)
.map((item) => ({
title: radarrCalendarEvent.title,
subTitle: radarrCalendarEvent.originalTitle,
description: radarrCalendarEvent.overview ?? null,
// Check is done above in the filter
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
startDate: item.date!,
endDate: null,
image: imageSrc
? {
src: imageSrc,
aspectRatio: { width: 7, height: 12 },
}
: null,
location: null,
metadata: {
type: "radarr",
releaseType: item.type,
},
indicatorColor: "yellow",
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
}));
});
}
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
const links: CalendarEvent["links"] = [
const links: CalendarLink[] = [
{
href: this.url(`/movie/${event.titleSlug}`).toString(),
name: "Radarr",
logo: "/images/apps/radarr.svg",
color: undefined,
notificationColor: "yellow",
isDark: true,
},
];
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
import type { TestingResult } from "../../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
@@ -50,15 +50,22 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
const readarrCalendarEvents = await z.array(readarrCalendarEventSchema).parseAsync(await response.json());
return readarrCalendarEvents.map((readarrCalendarEvent): CalendarEvent => {
const imageSrc = this.chooseBestImageAsURL(readarrCalendarEvent);
return {
name: readarrCalendarEvent.title,
subName: readarrCalendarEvent.author.authorName,
description: readarrCalendarEvent.overview,
thumbnail: this.chooseBestImageAsURL(readarrCalendarEvent),
date: readarrCalendarEvent.releaseDate,
mediaInformation: {
type: "audio",
},
title: readarrCalendarEvent.title,
subTitle: readarrCalendarEvent.author.authorName,
description: readarrCalendarEvent.overview ?? null,
startDate: readarrCalendarEvent.releaseDate,
endDate: null,
image: imageSrc
? {
src: imageSrc,
aspectRatio: { width: 7, height: 12 },
}
: null,
location: null,
indicatorColor: "#f5c518",
links: this.getLinksForReadarrCalendarEvent(readarrCalendarEvent),
};
});
@@ -72,9 +79,8 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
isDark: false,
logo: "/images/apps/readarr.svg",
name: "Readarr",
notificationColor: "#f5c518",
},
] satisfies CalendarEvent["links"];
] satisfies CalendarLink[];
};
private chooseBestImage = (
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } from "../../base/integration";
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
import type { TestingResult } from "../../base/test-connection/test-connection-service";
import type { ICalendarIntegration } from "../../interfaces/calendar/calendar-integration";
import type { CalendarEvent } from "../../interfaces/calendar/calendar-types";
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
import { mediaOrganizerPriorities } from "../media-organizer";
export class SonarrIntegration extends Integration implements ICalendarIntegration {
@@ -33,33 +33,40 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
"X-Api-Key": super.getSecretValue("apiKey"),
},
});
const sonarCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
const sonarrCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
return sonarCalendarEvents.map(
(sonarCalendarEvent): CalendarEvent => ({
name: sonarCalendarEvent.title,
subName: sonarCalendarEvent.series.title,
description: sonarCalendarEvent.series.overview,
thumbnail: this.chooseBestImageAsURL(sonarCalendarEvent),
date: sonarCalendarEvent.airDateUtc,
mediaInformation: {
type: "tv",
episodeNumber: sonarCalendarEvent.episodeNumber,
seasonNumber: sonarCalendarEvent.seasonNumber,
},
links: this.getLinksForSonarCalendarEvent(sonarCalendarEvent),
}),
);
return sonarrCalendarEvents.map((event): CalendarEvent => {
const imageSrc = this.chooseBestImageAsURL(event);
return {
title: event.title,
subTitle: event.series.title,
description: event.series.overview ?? null,
startDate: event.airDateUtc,
endDate: null,
image: imageSrc
? {
src: imageSrc,
aspectRatio: { width: 7, height: 12 },
badge: {
color: "red",
content: `S${event.seasonNumber}/E${event.episodeNumber}`,
},
}
: null,
location: null,
indicatorColor: "blue",
links: this.getLinksForSonarrCalendarEvent(event),
};
});
}
private getLinksForSonarCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
const links: CalendarEvent["links"] = [
private getLinksForSonarrCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
const links: CalendarLink[] = [
{
href: this.url(`/series/${event.series.titleSlug}`).toString(),
name: "Sonarr",
logo: "/images/apps/sonarr.svg",
color: undefined,
notificationColor: "blue",
isDark: true,
},
];
+49 -32
View File
@@ -8,32 +8,46 @@ export class CalendarMockService implements ICalendarIntegration {
}
}
const homarrMeetup = (start: Date, end: Date): CalendarEvent => ({
name: "Homarr Meetup",
subName: "",
description: "Yearly meetup of the Homarr community",
date: randomDateBetween(start, end),
links: [
{
href: "https://homarr.dev",
name: "Homarr",
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
color: "#000000",
notificationColor: "#fa5252",
isDark: true,
},
],
});
const homarrMeetup = (start: Date, end: Date): CalendarEvent => {
const startDate = randomDateBetween(start, end);
const endDate = new Date(startDate.getTime() + 2 * 60 * 60 * 1000); // 2 hours later
return {
title: "Homarr Meetup",
subTitle: "",
description: "Yearly meetup of the Homarr community",
startDate,
endDate,
image: null,
location: "Mountains",
indicatorColor: "#fa5252",
links: [
{
href: "https://homarr.dev",
name: "Homarr",
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
color: "#000000",
isDark: true,
},
],
};
};
const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
name: "Titanic",
subName: "A classic movie",
title: "Titanic",
subTitle: "A classic movie",
description: "A tragic love story set on the ill-fated RMS Titanic.",
date: randomDateBetween(start, end),
thumbnail: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
mediaInformation: {
type: "movie",
startDate: randomDateBetween(start, end),
endDate: null,
image: {
src: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
aspectRatio: { width: 7, height: 12 },
},
location: null,
metadata: {
type: "radarr",
releaseType: "inCinemas",
},
indicatorColor: "cyan",
links: [
{
href: "https://www.imdb.com/title/tt0120338/",
@@ -41,22 +55,26 @@ const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
color: "#f5c518",
isDark: false,
logo: "/images/apps/imdb.svg",
notificationColor: "cyan",
},
],
});
const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
name: "The Mandalorian",
subName: "A Star Wars Series",
title: "The Mandalorian",
subTitle: "A Star Wars Series",
description: "A lone bounty hunter in the outer reaches of the galaxy.",
date: randomDateBetween(start, end),
thumbnail: "https://image.tmdb.org/t/p/original/ztvm7C7hiUpS3CZRXFmJxljICzK.jpg",
mediaInformation: {
type: "tv",
seasonNumber: 1,
episodeNumber: 1,
startDate: randomDateBetween(start, end),
endDate: null,
image: {
src: "https://image.tmdb.org/t/p/original/sWgBv7LV2PRoQgkxwlibdGXKz1S.jpg",
aspectRatio: { width: 7, height: 12 },
badge: {
content: "S1/E1",
color: "red",
},
},
location: null,
indicatorColor: "blue",
links: [
{
href: "https://www.imdb.com/title/tt8111088/",
@@ -64,7 +82,6 @@ const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
color: "#f5c518",
isDark: false,
logo: "/images/apps/imdb.svg",
notificationColor: "blue",
},
],
});
@@ -63,17 +63,20 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
);
return {
name: veventObject.summary,
date,
subName: "",
title: veventObject.summary,
subTitle: null,
description: veventObject.description,
startDate: date,
endDate: veventObject.end,
image: null,
location: veventObject.location || null,
indicatorColor: "#ff8600",
links: [
{
href: url.toString(),
name: "Nextcloud",
logo: "/images/apps/nextcloud.svg",
color: undefined,
notificationColor: "#ff8600",
isDark: true,
},
],
@@ -265,7 +265,9 @@ const mapType = (type: string): "movie" | "tv" | "unknown" => {
switch (type) {
case "movie":
return "movie";
case "tv":
case "show":
case "season":
case "episode":
return "tv";
default:
return "unknown";
@@ -0,0 +1,375 @@
import dayjs from "dayjs";
import z from "zod";
import { createId } from "@homarr/common";
import { RequestError, ResponseError } from "@homarr/common/server";
import { logger } from "@homarr/log";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration";
import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types";
const localLogger = logger.child({ module: "TrueNasIntegration" });
const NETWORK_MULTIPLIER = 100;
export class TrueNasIntegration extends Integration implements ISystemHealthMonitoringIntegration {
private static webSocketMap = new Map<string, WebSocket>();
private wsUrl() {
const url = super.url("/websocket");
url.protocol = url.protocol.replace("http", "ws");
return url;
}
private get webSocket() {
return TrueNasIntegration.webSocketMap.get(this.integration.id) ?? null;
}
protected async testingAsync(_input: IntegrationTestingInput): Promise<TestingResult> {
const webSocket = await this.connectWebSocketAsync();
await this.registerSessionAsync(webSocket);
await this.authenticateWebSocketAsync(webSocket);
// Remove current socket connection so we can authenticate with updated credentials
TrueNasIntegration.webSocketMap.delete(this.integration.id);
return { success: true };
}
/**
* TrueNAS API uses WebSocket. This function connects to the socket
* and resolves the promise if the connection was successful.
* @see https://www.truenas.com/docs/api/scale_websocket_api.html
*/
private async connectWebSocketAsync(): Promise<WebSocket> {
localLogger.debug("Connecting to websocket server", {
url: this.wsUrl(),
});
const webSocket = new WebSocket(this.wsUrl());
return new Promise((resolve, reject) => {
webSocket.onopen = () => {
localLogger.debug("Connected to websocket server", {
url: this.wsUrl(),
});
resolve(webSocket);
};
webSocket.onerror = () => {
reject(new Error("Failed to connect"));
};
});
}
/**
* Before authentication, a session must be obtained from the server using the "connect" event.
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#websocket_protocol
*/
private async registerSessionAsync(webSocket: WebSocket): Promise<void> {
return new Promise((resolve, reject) => {
const subscribe = (event: MessageEvent<string>) => {
const data = JSON.parse(event.data) as { msg: string };
if (data.msg === "connected") {
webSocket.removeEventListener("message", subscribe);
resolve();
} else if (data.msg === "failed") {
webSocket.removeEventListener("message", subscribe);
reject(new Error("Unable to establish connection"));
}
};
webSocket.addEventListener("message", subscribe);
webSocket.send(
JSON.stringify({
msg: "connect",
version: "1", // this must be number, not string
support: ["1"], // this must be number, not string
}),
);
});
}
/**
* After a session was obtained, the session can be authenticated.
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#websocket_protocol
*/
private async authenticateWebSocketAsync(webSocket?: WebSocket): Promise<void> {
localLogger.debug("Authenticating with username and password", {
url: this.wsUrl(),
});
const response = await this.requestAsync(
"auth.login",
[this.getSecretValue("username"), this.getSecretValue("password")],
webSocket,
);
const result = await z.boolean().parseAsync(response);
if (!result) throw new ResponseError({ status: 401 });
localLogger.debug("Authenticated successfully with username and password", {
url: this.wsUrl(),
});
}
/**
* Retrieves data using the reporting method
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#reporting
*/
private async getReportingAsync(): Promise<ReportingItem[]> {
localLogger.debug("Retrieving reporting data", {
url: this.wsUrl(),
});
const response = await this.requestAsync("reporting.get_data", [
[
{
name: "cpu",
},
{
name: "memory",
},
{
name: "cputemp",
},
],
{
aggregate: true,
start: dayjs().add(-5, "minutes").unix(),
end: dayjs().unix(),
},
]);
const result = await z.array(reportingItemSchema).parseAsync(response);
localLogger.debug("Retrieved reporting data", {
url: this.wsUrl(),
count: result.length,
});
return result;
}
/**
* Retrieves a list of all available network interfaces
* @see https://www.truenas.com/docs/core/13.0/api/core_websocket_api.html#interface
*/
private async getNetworkInterfacesAsync(): Promise<z.infer<typeof networkInterfaceSchema>> {
localLogger.debug("Retrieving available network-interfaces", {
url: this.wsUrl(),
});
const response = await this.requestAsync("interface.query", [
[], // no filters
{},
]);
const result = await networkInterfaceSchema.parseAsync(response);
localLogger.debug("Retrieved available network-interfaces", {
url: this.wsUrl(),
count: result.length,
});
return result;
}
/**
* Retrieves reporting network data of the last 5 minutes
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#reporting
*/
private async getReportingNetdataAsync(): Promise<z.infer<typeof reportingNetDataSchema>> {
const networkInterfaces = await this.getNetworkInterfacesAsync();
localLogger.debug("Retrieving reporting network data", {
url: this.wsUrl(),
});
const response = await this.requestAsync("reporting.netdata_get_data", [
networkInterfaces.map((networkInterface) => ({
name: "interface",
identifier: networkInterface.id,
})),
{
start: dayjs().add(-5, "minutes").unix(),
end: dayjs().unix(),
},
]);
const result = await reportingNetDataSchema.parseAsync(response);
localLogger.debug("Retrieved reporting-network-data", {
url: this.wsUrl(),
count: result.length,
});
return result;
}
/**
* Retrieves information about the system
* @see https://www.truenas.com/docs/api/scale_websocket_api.html#system
*/
private async getSystemInformationAsync(): Promise<z.infer<typeof systemInfoSchema>> {
localLogger.debug("Retrieving system-information", {
url: this.wsUrl(),
});
const response = await this.requestAsync("system.info");
const result = await systemInfoSchema.parseAsync(response);
localLogger.debug("Retrieved system-information", {
url: this.wsUrl(),
});
return result;
}
public async getSystemInfoAsync(): Promise<SystemHealthMonitoring> {
const systemInformation = await this.getSystemInformationAsync();
const reporting = await this.getReportingAsync();
const cpuData = this.extractLatestReportingData(reporting, "cpu");
const cpuTempData = this.extractLatestReportingData(reporting, "cputemp");
const memoryData = this.extractLatestReportingData(reporting, "memory");
const netdata = await this.getReportingNetdataAsync();
const upload = this.extractNetworkTrafficData(netdata, 2); // Index 2 is "sent"
const download = this.extractNetworkTrafficData(netdata, 1); // Index 1 is "received"
return {
cpuUtilization: cpuData.reduce((acc, item) => acc + (item > 100 ? 0 : item), 0) / cpuData.length,
cpuTemp: Math.max(...cpuTempData.filter((_item, i) => i > 0)),
memAvailableInBytes: systemInformation.physmem,
memUsedInBytes: memoryData[1] ?? 0, // Index 0 is UNIX timestamp, Index 1 is free space in bytes
fileSystem: [],
availablePkgUpdates: 0,
network: {
up: upload * NETWORK_MULTIPLIER,
down: download * NETWORK_MULTIPLIER,
},
loadAverage: null,
smart: [],
uptime: systemInformation.uptime_seconds,
version: systemInformation.version,
cpuModelName: systemInformation.model,
rebootRequired: false,
};
}
/**
* Send a request through websocket and return response
* Times out after 5 seconds when no response was received.
* @param method json-rpc method to call
* @param params array of parameters
* @param webSocketOverride override of webSocket, helpful for not storing the connection
* @returns result of json-rpc call
*/
private async requestAsync(method: string, params: unknown[] = [], webSocketOverride?: WebSocket) {
let webSocket = webSocketOverride ?? this.webSocket;
if (!webSocket || webSocket.readyState !== WebSocket.OPEN) {
localLogger.debug("Connecting to websocket", {
url: this.wsUrl(),
});
// We can only land here with static webSocket
webSocket = await this.connectWebSocketAsync();
await this.registerSessionAsync(webSocket);
TrueNasIntegration.webSocketMap.set(this.integration.id, webSocket);
await this.authenticateWebSocketAsync();
}
return await new Promise((resolve, reject) => {
const id = createId();
const handler = (event: MessageEvent<string>) => {
const data = JSON.parse(event.data) as Record<string, unknown>;
if (data.msg !== "result") return;
if (data.id !== id) return;
clearTimeout(timeoutId);
webSocket.removeEventListener("message", handler);
localLogger.debug("Received method response", {
id,
method,
url: this.wsUrl(),
});
resolve(data.result);
};
const timeoutId = setTimeout(() => {
webSocket.removeEventListener("message", handler);
reject(
new RequestError(
{
type: "timeout",
reason: "aborted",
code: "ECONNABORTED",
},
{ cause: new Error("Canceled request after 5 seconds") },
),
);
}, 5000);
webSocket.addEventListener("message", handler);
localLogger.debug("Sending method request", {
id,
method,
url: this.wsUrl(),
});
webSocket.send(
JSON.stringify({
id,
msg: "method",
method,
params,
}),
);
});
}
private extractNetworkTrafficData = (data: z.infer<typeof reportingNetDataSchema>, index = 1 | 2) => {
return data.reduce((acc, current) => acc + (current.data.at(-1)?.at(index) ?? 0), 0);
};
private extractLatestReportingData(data: ReportingItem[], key: ReportingItem["identifier"]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const dataObject = data.find((item) => item.identifier === key)!;
// TODO: check why the below sorting is done, because right now it compares number[] with number[]?
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return dataObject.data.sort((item1, item2) => (item1 > item2 ? -1 : 1))[0]!;
}
}
const reportingItemSchema = z.object({
name: z.enum(["cpu", "memory", "cputemp"]),
identifier: z.enum(["cpu", "memory", "cputemp"]),
aggregations: z.object({
min: z.record(z.string(), z.unknown()),
mean: z.record(z.string(), z.unknown()),
max: z.record(z.string(), z.unknown()),
}),
start: z.number().min(0),
end: z.number().min(0),
legend: z.array(z.string()),
data: z.array(z.array(z.number())),
});
type ReportingItem = z.infer<typeof reportingItemSchema>;
const reportingNetDataSchema = z.array(
z.object({
name: z.string(),
identifier: z.string(),
data: z.array(z.array(z.number())),
}),
);
const systemInfoSchema = z.object({
version: z.string(),
hostname: z.string(),
physmem: z.number().min(0), // pysical memory
model: z.string(), // cpu model
uptime_seconds: z.number().min(0),
});
const networkInterfaceSchema = z.array(
z.object({
id: z.string(),
name: z.string(),
}),
);
+2 -2
View File
@@ -27,13 +27,13 @@
"@homarr/core": "workspace:^0.1.0",
"superjson": "2.2.2",
"winston": "3.17.0",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
+6 -6
View File
@@ -33,19 +33,19 @@
"@homarr/translation": "workspace:^0.1.0",
"@homarr/ui": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.2.5",
"@tabler/icons-react": "^3.34.1",
"dayjs": "^1.11.13",
"next": "15.5.0",
"@mantine/core": "^8.3.1",
"@tabler/icons-react": "^3.35.0",
"dayjs": "^1.11.18",
"next": "15.5.3",
"react": "19.1.1",
"react-dom": "19.1.1",
"zod": "^4.0.17"
"zod": "^4.1.11"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"eslint": "^9.33.0",
"eslint": "^9.36.0",
"typescript": "^5.9.2"
}
}
Loaded 100 of 179 files, more files were not shown because too many files have changed in this diff. Show more