Compare commits
262
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd62524db3 | ||
|
|
4f0ba7a191 | ||
|
|
ecda928bef | ||
|
|
bf88f0d091 | ||
|
|
98f8279b61 | ||
|
|
8e960324bc | ||
|
|
732bce72ae | ||
|
|
66ebb5061f | ||
|
|
fa8e704112 | ||
|
|
3f29a453b9 | ||
|
|
36b0f576e5 | ||
|
|
18c3ed559e | ||
|
|
6fca92ac49 | ||
|
|
42301b4a1e | ||
|
|
2394159f8b | ||
|
|
c2305d4e27 | ||
|
|
ba51d71c48 | ||
|
|
1cc6ecbce2 | ||
|
|
cf0d2d3cda | ||
|
|
553f192605 | ||
|
|
7f34c4323e | ||
|
|
49ef822a26 | ||
|
|
c4e5c3002b | ||
|
|
c00110e426 | ||
|
|
d4acb01efd | ||
|
|
ee983e8241 | ||
|
|
742d7bddf8 | ||
|
|
93ead1b53f | ||
|
|
11a42c176f | ||
|
|
47806c180e | ||
|
|
fc207b17dc | ||
|
|
39b025f763 | ||
|
|
e7012b6da4 | ||
|
|
d6ea8e8993 | ||
|
|
71dee5ca4b | ||
|
|
fe018732b5 | ||
|
|
021bb48e1a | ||
|
|
f57ba4c8bc | ||
|
|
6dd695ecf7 | ||
|
|
2726247e1b | ||
|
|
5ecc4db5da | ||
|
|
29884dda97 | ||
|
|
72633bf178 | ||
|
|
27fe82401b | ||
|
|
473a9f47f3 | ||
|
|
7fb7af12f4 | ||
|
|
380055e205 | ||
|
|
39bf0d036a | ||
|
|
5d8126d71e | ||
|
|
9020440193 | ||
|
|
bd8ac62c7a | ||
|
|
5a8653d850 | ||
|
|
dc754232bc | ||
|
|
9af542ee81 | ||
|
|
41a7182133 | ||
|
|
99358109ba | ||
|
|
df5b6fcb6a | ||
|
|
b03c9c1043 | ||
|
|
d68b80e6be | ||
|
|
f1143e54a0 | ||
|
|
54392dcea4 | ||
|
|
661afa72be | ||
|
|
d16e0ba7c1 | ||
|
|
e21b236d37 | ||
|
|
7e92f1bafa | ||
|
|
ee286c9556 | ||
|
|
68593335ec | ||
|
|
fb57d46b57 | ||
|
|
1724f9d26c | ||
|
|
90f6a2dd01 | ||
|
|
3f191da25c | ||
|
|
7c2f8b85bd | ||
|
|
c5db9db5d7 | ||
|
|
6a06a214be | ||
|
|
c08caeac5a | ||
|
|
c793bf1982 | ||
|
|
a369beb248 | ||
|
|
d736cf6daa | ||
|
|
3411354677 | ||
|
|
7c0a7428d8 | ||
|
|
5d394c849f | ||
|
|
9c7cfeb849 | ||
|
|
1eb47311fa | ||
|
|
1fe3450555 | ||
|
|
8b9832b2b8 | ||
|
|
57be43f51b | ||
|
|
824f443949 | ||
|
|
e9a35ba811 | ||
|
|
12f4e90e44 | ||
|
|
2ac75b23a4 | ||
|
|
430dc58068 | ||
|
|
8f1c3d8a2d | ||
|
|
b0bdf28016 | ||
|
|
01fd7b660c | ||
|
|
59b31e97cd | ||
|
|
58e3bf57e3 | ||
|
|
b057059ff1 | ||
|
|
66f663aad4 | ||
|
|
7061a03b2e | ||
|
|
58d5b14c51 | ||
|
|
350a531d32 | ||
|
|
0858a42b81 | ||
|
|
d5181d0c0e | ||
|
|
63e2acefbd | ||
|
|
cf9f2811f3 | ||
|
|
308d235fa9 | ||
|
|
9398dd983c | ||
|
|
95c8aadb0c | ||
|
|
beb031de12 | ||
|
|
0e1f6934f9 | ||
|
|
915eec1b5e | ||
|
|
b375ec977e | ||
|
|
6005784284 | ||
|
|
9da66f5b1d | ||
|
|
6774a17eb3 | ||
|
|
2170c5ca73 | ||
|
|
fe87d978c4 | ||
|
|
964110377c | ||
|
|
6c94d1a10c | ||
|
|
ab9165f2cd | ||
|
|
6fda939a2d | ||
|
|
0f94ac456e | ||
|
|
41494859e5 | ||
|
|
0a0e7f9602 | ||
|
|
97d7f31ffd | ||
|
|
17fe293a7c | ||
|
|
01d0ffb0d1 | ||
|
|
8d53801f67 | ||
|
|
7aed20450c | ||
|
|
857c7d0f16 | ||
|
|
c22a963342 | ||
|
|
ed077d63db | ||
|
|
2ebc9f969b | ||
|
|
e32ab1c0b0 | ||
|
|
7aa6c7794b | ||
|
|
ceacad15cc | ||
|
|
ec368132bf | ||
|
|
4d9edca9b3 | ||
|
|
5346b96984 | ||
|
|
b880de8a79 | ||
|
|
260e1931b1 | ||
|
|
2c2e60aee6 | ||
|
|
a737331d20 | ||
|
|
e9e3d93eea | ||
|
|
670de0f5fb | ||
|
|
d326f3b4b3 | ||
|
|
60786bf9ac | ||
|
|
22e420c9bc | ||
|
|
b027f3a180 | ||
|
|
e83c514c1d | ||
|
|
01fdd79559 | ||
|
|
7477d02cad | ||
|
|
7e8cfc38e9 | ||
|
|
7d0ac46753 | ||
|
|
114de40634 | ||
|
|
0c304c4310 | ||
|
|
1cd9fa8e82 | ||
|
|
98ea5cd2cf | ||
|
|
5e5fecb4a0 | ||
|
|
5e6dfb3a5c | ||
|
|
3889d63651 | ||
|
|
eadec21236 | ||
|
|
34faa9e58c | ||
|
|
dfc1c65a34 | ||
|
|
177fa88998 | ||
|
|
5d9674412e | ||
|
|
e22096c7bc | ||
|
|
468c04da8a | ||
|
|
62c78f191c | ||
|
|
fa421b3778 | ||
|
|
1813d4435e | ||
|
|
e110a84fdd | ||
|
|
95be0391a6 | ||
|
|
6b474c3df4 | ||
|
|
ded39930ca | ||
|
|
86d4585035 | ||
|
|
4e892efab7 | ||
|
|
8ffbbd6bf9 | ||
|
|
17763208df | ||
|
|
07ce1c1a2d | ||
|
|
14c116cef4 | ||
|
|
7b8dc1670d | ||
|
|
da2d22b1b9 | ||
|
|
2c7f5de192 | ||
|
|
319bee99b7 | ||
|
|
4bf69874ec | ||
|
|
add73a1a3c | ||
|
|
3f0e9c3738 | ||
|
|
b8b71da291 | ||
|
|
f677bd6b12 | ||
|
|
e6f05f9d94 | ||
|
|
86608f5a9f | ||
|
|
f6039a5976 | ||
|
|
8ccbe7ff52 | ||
|
|
eede932884 | ||
|
|
a7052bb16f | ||
|
|
caedac490e | ||
|
|
c4db07915e | ||
|
|
795e5f1157 | ||
|
|
f6a99ffedd | ||
|
|
ec4dde64fd | ||
|
|
9e4b4635bc | ||
|
|
7c58e8ac9b | ||
|
|
3759a9831c | ||
|
|
73fff02ad0 | ||
|
|
8e428e20a7 | ||
|
|
02cd7a50da | ||
|
|
430f671434 | ||
|
|
7d6d85d563 | ||
|
|
b5ef7e5194 | ||
|
|
6bcfe78956 | ||
|
|
56ca8b7b0b | ||
|
|
9224540761 | ||
|
|
a8c9c075f0 | ||
|
|
637bfb8b47 | ||
|
|
adbef252c6 | ||
|
|
f97a261973 | ||
|
|
bdca92036a | ||
|
|
9a81689143 | ||
|
|
e6cb35b7c2 | ||
|
|
cad59aed00 | ||
|
|
e56ffb9244 | ||
|
|
5c75960176 | ||
|
|
c2acbdc229 | ||
|
|
6d9992d711 | ||
|
|
eb3454fb75 | ||
|
|
c9097a95e2 | ||
|
|
5dc0ee8842 | ||
|
|
f16efac05a | ||
|
|
c5cc8f1d37 | ||
|
|
153c2ef8a0 | ||
|
|
3bf4f9e058 | ||
|
|
747d034114 | ||
|
|
79519ec0eb | ||
|
|
0b200dc9e2 | ||
|
|
377f4302c0 | ||
|
|
740e9a5082 | ||
|
|
e7652ea4eb | ||
|
|
863f935ef4 | ||
|
|
7388c907a5 | ||
|
|
3d07306e7d | ||
|
|
f37fc0edfe | ||
|
|
c59cdeda47 | ||
|
|
ad444e8fa7 | ||
|
|
fa52b55828 | ||
|
|
1ce1113a8c | ||
|
|
e7a9797484 | ||
|
|
d104aea8b8 | ||
|
|
ab3ee3bac1 | ||
|
|
838066ac75 | ||
|
|
b1a559d801 | ||
|
|
eecf50d461 | ||
|
|
d862ae03e3 | ||
|
|
f6da091577 | ||
|
|
22f0cd885e | ||
|
|
08c6ce9071 | ||
|
|
c3e1a53d09 | ||
|
|
9e86de6e17 | ||
|
|
98c4cd1e39 | ||
|
|
f67702e1d3 | ||
|
|
a9e68e13d5 | ||
|
|
1b3770aafb |
No files matched your search
+10
-2
@@ -12,6 +12,10 @@ AUTH_SECRET="supersecret"
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# Cron job API key is generated every time the container starts as it is required for communication between nextjs-api and tasks-api
|
||||
# export CRON_JOB_API_KEY=$(openssl rand -base64 32)
|
||||
# CRON_JOB_API_KEY="your-generated-api-key"
|
||||
|
||||
LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
@@ -30,11 +34,15 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
# The below path can be used to store trusted certificates during development, it is not required and can be left empty.
|
||||
# The below path can be used to store trusted certificates, it is not required and can be left empty.
|
||||
# If it is empty, it will default to `/appdata/trusted-certificates` in production.
|
||||
# If it is used, please use the full path to the directory where the certificates are stored.
|
||||
# LOCAL_CERTIFICATE_PATH='FULL_PATH_TO_CERTIFICATES'
|
||||
|
||||
TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
# Enable kubernetes tool
|
||||
# ENABLE_KUBERNETES=true
|
||||
# ENABLE_KUBERNETES=true
|
||||
|
||||
# Enable mock integration
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
@@ -31,6 +31,13 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.29.0
|
||||
- 1.28.1
|
||||
- 1.28.0
|
||||
- 1.27.0
|
||||
- 1.26.0
|
||||
- 1.25.0
|
||||
- 1.24.0
|
||||
- 1.23.0
|
||||
- 1.22.0
|
||||
- 1.21.0
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
matchPackagePatterns: ["^@homarr/"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchPackagePatterns: ["^zod$", "^drizzle-zod$", "^zod-form-data$"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||
automerge: true,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# https://github.com/webiny/action-conventional-commits?tab=readme-ov-file
|
||||
name: "[Conventions] Semantic Commits"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Conventional Commits
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: webiny/action-conventional-commits@v1.3.0
|
||||
+225
@@ -1,3 +1,228 @@
|
||||
## [1.30.0](https://github.com/homarr-labs/homarr/compare/v1.29.0...v1.30.0) (2025-07-21)
|
||||
|
||||
### Features
|
||||
|
||||
* **infra:** add external redis ([#3639](https://github.com/homarr-labs/homarr/issues/3639)) ([8e96032](https://github.com/homarr-labs/homarr/commit/8e960324bcc43a64100fd9c6c1fd6822c7759160))
|
||||
* **widgets:** add media release widget ([#3219](https://github.com/homarr-labs/homarr/issues/3219)) ([66ebb50](https://github.com/homarr-labs/homarr/commit/66ebb5061fd219f9c804ef3a6bbe3372d296f740))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @gitbeaker/rest to v43 ([#3623](https://github.com/homarr-labs/homarr/issues/3623)) ([732bce7](https://github.com/homarr-labs/homarr/commit/732bce72aee6c99d0ee94d04e173d378c2552a16))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.34.1 ([#3661](https://github.com/homarr-labs/homarr/issues/3661)) ([bf88f0d](https://github.com/homarr-labs/homarr/commit/bf88f0d0918b4073a5770ab3589e00e5f89cacf1))
|
||||
* **deps:** update nextjs monorepo to v15.4.2 ([#3654](https://github.com/homarr-labs/homarr/issues/3654)) ([42301b4](https://github.com/homarr-labs/homarr/commit/42301b4a1ed9e9f638b449a460862db9cd7ed50b))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.3.0 ([#3652](https://github.com/homarr-labs/homarr/issues/3652)) ([2394159](https://github.com/homarr-labs/homarr/commit/2394159f8b546e8b6e2d3cdc7c6bd0f75483209c))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.3.1 ([#3659](https://github.com/homarr-labs/homarr/issues/3659)) ([98f8279](https://github.com/homarr-labs/homarr/commit/98f8279b61013c312d492522e317bcf93f10e2a6))
|
||||
* improved import from docker parsing ([#3642](https://github.com/homarr-labs/homarr/issues/3642)) ([36b0f57](https://github.com/homarr-labs/homarr/commit/36b0f576e5a00726d76695fd3300710be6f925f8))
|
||||
* **security:** upgrade eslint-config-prettier to latest version without malicious code ([ecda928](https://github.com/homarr-labs/homarr/commit/ecda928bef407c67f01ce798c5f7a086cbd0dd9c))
|
||||
|
||||
## [1.29.0](https://github.com/homarr-labs/homarr/compare/v1.28.1...v1.29.0) (2025-07-18)
|
||||
|
||||
### Features
|
||||
|
||||
* **logs:** add log level selection to tools ui ([#3565](https://github.com/homarr-labs/homarr/issues/3565)) ([c4e5c30](https://github.com/homarr-labs/homarr/commit/c4e5c3002bd1c746cee496b5ba6d19159104b7f8))
|
||||
* **releases-widget:** show project description when release description is missing ([#3635](https://github.com/homarr-labs/homarr/issues/3635)) ([47806c1](https://github.com/homarr-labs/homarr/commit/47806c180ebc960eb1731c02ec6af42156831bd0))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.3 ([#3629](https://github.com/homarr-labs/homarr/issues/3629)) ([e7012b6](https://github.com/homarr-labs/homarr/commit/e7012b6da496a0f4b7aad527d1d05cd03d0bfcda))
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.1.7 ([#3651](https://github.com/homarr-labs/homarr/issues/3651)) ([1cc6ecb](https://github.com/homarr-labs/homarr/commit/1cc6ecbce29348cf891dd8cfc29d4238ff5f7f78))
|
||||
* **deps:** update dependency ldapts to v8.0.6 ([#3628](https://github.com/homarr-labs/homarr/issues/3628)) ([71dee5c](https://github.com/homarr-labs/homarr/commit/71dee5ca4bca04acc9c295c373d1209641ef3d99))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.27.0 ([#3638](https://github.com/homarr-labs/homarr/issues/3638)) ([ee983e8](https://github.com/homarr-labs/homarr/commit/ee983e8241d2f32c1dcbbea4930b18442f32a5bf))
|
||||
* **deps:** update dependency typescript-eslint to ^8.37.0 ([#3631](https://github.com/homarr-labs/homarr/issues/3631)) ([d6ea8e8](https://github.com/homarr-labs/homarr/commit/d6ea8e8993d130a03592473744e74be7b4c54a79))
|
||||
* **deps:** update dependency undici to v7.12.0 ([#3650](https://github.com/homarr-labs/homarr/issues/3650)) ([cf0d2d3](https://github.com/homarr-labs/homarr/commit/cf0d2d3cdaf495d7786c45ba4c79d49f356c0e37))
|
||||
* **deps:** update nextjs monorepo to v15.4.1 ([#3632](https://github.com/homarr-labs/homarr/issues/3632)) ([39b025f](https://github.com/homarr-labs/homarr/commit/39b025f7633391c74aedeb4c4dca10d42fb3b4d8))
|
||||
* **deps:** update turbo monorepo to ^2.5.5 ([#3641](https://github.com/homarr-labs/homarr/issues/3641)) ([d4acb01](https://github.com/homarr-labs/homarr/commit/d4acb01efd6a9341fb5550a08def42d82c1f0b92))
|
||||
|
||||
## [1.28.1](https://github.com/homarr-labs/homarr/compare/v1.28.0...v1.28.1) (2025-07-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update tiptap monorepo to v2.26.1 ([#3603](https://github.com/homarr-labs/homarr/issues/3603)) ([473a9f4](https://github.com/homarr-labs/homarr/commit/473a9f47f352a532128c1bf2c02490e8e4c6ca7c))
|
||||
* **releases-widget:** fixed bug with changing a missing provider ([#3605](https://github.com/homarr-labs/homarr/issues/3605)) ([5ecc4db](https://github.com/homarr-labs/homarr/commit/5ecc4db5dabcfd4f953f3b436527d34ea9191eef))
|
||||
|
||||
## [1.28.0](https://github.com/homarr-labs/homarr/compare/v1.27.0...v1.28.0) (2025-07-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **icon-color:** add icon color clear button ([#3593](https://github.com/homarr-labs/homarr/issues/3593)) ([5a8653d](https://github.com/homarr-labs/homarr/commit/5a8653d8500388d9392b150d62a56474c05843d7))
|
||||
* **notebook:** add placeholder, redo and undo button ([#3564](https://github.com/homarr-labs/homarr/issues/3564)) ([3411354](https://github.com/homarr-labs/homarr/commit/3411354677fedec6ff5b5b85eb25eacc94308425))
|
||||
* **ping:** ignore certificate error and show request duration ([#3546](https://github.com/homarr-labs/homarr/issues/3546)) ([1eb4731](https://github.com/homarr-labs/homarr/commit/1eb47311fa81184bd67830d0669d9f87d9922e70))
|
||||
* **releases-widget:** define providers as integrations ([#3253](https://github.com/homarr-labs/homarr/issues/3253)) ([5d8126d](https://github.com/homarr-labs/homarr/commit/5d8126d71eea61af9c023895cb911d24544f8066))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bookmarks:** wrong widget icon ([#3572](https://github.com/homarr-labs/homarr/issues/3572)) ([fb57d46](https://github.com/homarr-labs/homarr/commit/fb57d46b573cc9798469540f4823cbf73afd21a5))
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.5.0 ([#3585](https://github.com/homarr-labs/homarr/issues/3585)) ([ee286c9](https://github.com/homarr-labs/homarr/commit/ee286c95563c99297b5419c005550936ca96757b))
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.5.1 ([#3586](https://github.com/homarr-labs/homarr/issues/3586)) ([7e92f1b](https://github.com/homarr-labs/homarr/commit/7e92f1bafadf1188ee3ddc9d2dc9904f103aab4e))
|
||||
* **deps:** update dependency dotenv to ^17.1.0 ([#3567](https://github.com/homarr-labs/homarr/issues/3567)) ([a369beb](https://github.com/homarr-labs/homarr/commit/a369beb248e2cac1f1dcf32421efb48f241700a6))
|
||||
* **deps:** update dependency dotenv to ^17.2.0 ([#3578](https://github.com/homarr-labs/homarr/issues/3578)) ([1724f9d](https://github.com/homarr-labs/homarr/commit/1724f9d26c5761e3195d336eb451dd47855266a4))
|
||||
* **deps:** update dependency ldapts to v8.0.5 ([#3559](https://github.com/homarr-labs/homarr/issues/3559)) ([8b9832b](https://github.com/homarr-labs/homarr/commit/8b9832b2b81dcc1a4f9081755defe44fc0f38a1d))
|
||||
* **deps:** update dependency mysql2 to v3.14.2 ([#3592](https://github.com/homarr-labs/homarr/issues/3592)) ([d68b80e](https://github.com/homarr-labs/homarr/commit/d68b80e6be38366860de84778a45e5a43aae06eb))
|
||||
* **deps:** update dependency node-cron to ^4.2.1 ([#3590](https://github.com/homarr-labs/homarr/issues/3590)) ([e21b236](https://github.com/homarr-labs/homarr/commit/e21b236d37f076654768e88bc7ef139a3e51abbc))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.26.1 ([#3554](https://github.com/homarr-labs/homarr/issues/3554)) ([12f4e90](https://github.com/homarr-labs/homarr/commit/12f4e90e440a8ea1708a2a9ef64a851ef977bd6c))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.26.2 ([#3558](https://github.com/homarr-labs/homarr/issues/3558)) ([57be43f](https://github.com/homarr-labs/homarr/commit/57be43f51b7ef306b962299789194de68ec9e8ea))
|
||||
* **deps:** update dependency typescript-eslint to ^8.36.0 ([#3563](https://github.com/homarr-labs/homarr/issues/3563)) ([7c0a742](https://github.com/homarr-labs/homarr/commit/7c0a7428d85d11f3d2226d809ba04671ca713b72))
|
||||
* **deps:** update dependency zod to ^3.25.75 ([#3555](https://github.com/homarr-labs/homarr/issues/3555)) ([e9a35ba](https://github.com/homarr-labs/homarr/commit/e9a35ba811bd1807d63c5f0c760d470b20f4410f))
|
||||
* **deps:** update dependency zod to ^3.25.76 ([#3569](https://github.com/homarr-labs/homarr/issues/3569)) ([c793bf1](https://github.com/homarr-labs/homarr/commit/c793bf1982c3a484176860cebad80c721acd597d))
|
||||
* **deps:** update dependency zod-validation-error to ^3.5.3 ([#3600](https://github.com/homarr-labs/homarr/issues/3600)) ([9020440](https://github.com/homarr-labs/homarr/commit/9020440193595a5b437464957d9a3745fb200c93))
|
||||
* **deps:** update mantine monorepo to ^8.1.3 ([#3560](https://github.com/homarr-labs/homarr/issues/3560)) ([1fe3450](https://github.com/homarr-labs/homarr/commit/1fe34505550f401be97c9d84c383ea70bf39b937))
|
||||
* **deps:** update tanstack-query monorepo to ^5.82.0 ([#3577](https://github.com/homarr-labs/homarr/issues/3577)) ([90f6a2d](https://github.com/homarr-labs/homarr/commit/90f6a2dd014a73cd0131781b6db56a1056e563b6))
|
||||
* **deps:** update tanstack-query monorepo to ^5.83.0 ([#3599](https://github.com/homarr-labs/homarr/issues/3599)) ([bd8ac62](https://github.com/homarr-labs/homarr/commit/bd8ac62c7af034525803410751bfd5ab72094f45))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.1.0 ([#3550](https://github.com/homarr-labs/homarr/issues/3550)) ([b0bdf28](https://github.com/homarr-labs/homarr/commit/b0bdf28016bae2ccfab24cab6df5b4846d1a8f48))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.2.0 ([#3556](https://github.com/homarr-labs/homarr/issues/3556)) ([824f443](https://github.com/homarr-labs/homarr/commit/824f443949e75ec3f3edacb9093faefbdec47ef4))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.2.1 ([#3562](https://github.com/homarr-labs/homarr/issues/3562)) ([5d394c8](https://github.com/homarr-labs/homarr/commit/5d394c849f5f47b47c595f73763e89d0a5d6247a))
|
||||
* **deps:** update tiptap monorepo to v2.25.0 ([#3552](https://github.com/homarr-labs/homarr/issues/3552)) ([430dc58](https://github.com/homarr-labs/homarr/commit/430dc580682cf0885fcd0ef2b7b81a0dfdf93425))
|
||||
* **deps:** update tiptap monorepo to v2.25.1 ([#3591](https://github.com/homarr-labs/homarr/issues/3591)) ([661afa7](https://github.com/homarr-labs/homarr/commit/661afa72be96c629cad4ab7f6007214b54d8ef6a))
|
||||
* **deps:** update tiptap monorepo to v2.26.0 ([#3596](https://github.com/homarr-labs/homarr/issues/3596)) ([df5b6fc](https://github.com/homarr-labs/homarr/commit/df5b6fcb6a6215f5c1cf92e034e27f6e32405b3e))
|
||||
* unable to change certificate path for running from source ([#3582](https://github.com/homarr-labs/homarr/issues/3582)) ([f1143e5](https://github.com/homarr-labs/homarr/commit/f1143e54a06e00a348e37b5633fad4cb6390cff7))
|
||||
|
||||
## [1.27.0](https://github.com/homarr-labs/homarr/compare/v1.26.0...v1.27.0) (2025-07-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **integrations:** add mock integration ([#3505](https://github.com/homarr-labs/homarr/issues/3505)) ([58d5b14](https://github.com/homarr-labs/homarr/commit/58d5b14c516583e79f9079899dfe6544acdc58f7))
|
||||
* **tasks:** allow management of job intervals and disabling them ([#3408](https://github.com/homarr-labs/homarr/issues/3408)) ([9398dd9](https://github.com/homarr-labs/homarr/commit/9398dd983cce00959af2a4885e92da6acfe2f57e))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* broken lockfile ([308d235](https://github.com/homarr-labs/homarr/commit/308d235fa9db658fb7c3f5a183f01c4f48f0546f))
|
||||
* **deps:** update dependency better-sqlite3 to ^12.2.0 ([#3503](https://github.com/homarr-labs/homarr/issues/3503)) ([c22a963](https://github.com/homarr-labs/homarr/commit/c22a963342c5613089b0a1a4034379cc80425602))
|
||||
* **deps:** update dependency dotenv to ^16.6.1 ([#3494](https://github.com/homarr-labs/homarr/issues/3494)) ([b880de8](https://github.com/homarr-labs/homarr/commit/b880de8a794a879a0c2b38cc46cb5412eedbb44e))
|
||||
* **deps:** update dependency dotenv to v17 ([#3499](https://github.com/homarr-labs/homarr/issues/3499)) ([fe87d97](https://github.com/homarr-labs/homarr/commit/fe87d978c4335a700e2859c648bf997a7c323a31))
|
||||
* **deps:** update dependency ldapts to v8.0.4 ([#3509](https://github.com/homarr-labs/homarr/issues/3509)) ([8d53801](https://github.com/homarr-labs/homarr/commit/8d53801f67b5458b5eb60b4ebcd6a923a60b7e5f))
|
||||
* **deps:** update dependency next-intl to v4.3.2 ([#3520](https://github.com/homarr-labs/homarr/issues/3520)) ([6fda939](https://github.com/homarr-labs/homarr/commit/6fda939a2d94120998eb1365134652eb00602937))
|
||||
* **deps:** update dependency next-intl to v4.3.3 ([#3521](https://github.com/homarr-labs/homarr/issues/3521)) ([ab9165f](https://github.com/homarr-labs/homarr/commit/ab9165f2cd885069c7389d62db187c3be240fd72))
|
||||
* **deps:** update dependency next-intl to v4.3.4 ([#3528](https://github.com/homarr-labs/homarr/issues/3528)) ([6005784](https://github.com/homarr-labs/homarr/commit/60057842842d393b8d9c5404d6c2ae85bc82b27c))
|
||||
* **deps:** update dependency node-cron to ^4.2.0 ([#3518](https://github.com/homarr-labs/homarr/issues/3518)) ([0f94ac4](https://github.com/homarr-labs/homarr/commit/0f94ac456eeacbc03183d635ea4c57b224cf8537))
|
||||
* **deps:** update dependency postcss-preset-mantine to ^1.18.0 ([#3510](https://github.com/homarr-labs/homarr/issues/3510)) ([01d0ffb](https://github.com/homarr-labs/homarr/commit/01d0ffb0d143a77c94c977873ad04f661c280eb7))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.25.4 ([#3523](https://github.com/homarr-labs/homarr/issues/3523)) ([6c94d1a](https://github.com/homarr-labs/homarr/commit/6c94d1a10c495850958b6399a302eab582e9d40b))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.26.0 ([#3527](https://github.com/homarr-labs/homarr/issues/3527)) ([9da66f5](https://github.com/homarr-labs/homarr/commit/9da66f5b1db88c196dbb6866f920f1a176305667))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.3.2 ([#3502](https://github.com/homarr-labs/homarr/issues/3502)) ([2ebc9f9](https://github.com/homarr-labs/homarr/commit/2ebc9f969b5a0c753622421cb5f0a81873ad4ae9))
|
||||
* **deps:** update dependency typescript-eslint to ^8.35.1 ([#3513](https://github.com/homarr-labs/homarr/issues/3513)) ([17fe293](https://github.com/homarr-labs/homarr/commit/17fe293a7c9e6dbc83bc8f585a54a8fdd8640973))
|
||||
* **deps:** update dependency ws to ^8.18.3 ([#3501](https://github.com/homarr-labs/homarr/issues/3501)) ([ed077d6](https://github.com/homarr-labs/homarr/commit/ed077d63db9a197510e8e139d40813a3854f39e9))
|
||||
* **deps:** update dependency zod to ^3.25.68 ([#3529](https://github.com/homarr-labs/homarr/issues/3529)) ([b375ec9](https://github.com/homarr-labs/homarr/commit/b375ec977e757bed14b6fb6a04a5b30fe481eaf9))
|
||||
* **deps:** update dependency zod to ^3.25.69 ([#3531](https://github.com/homarr-labs/homarr/issues/3531)) ([915eec1](https://github.com/homarr-labs/homarr/commit/915eec1b5e6d7383d36f2e67d86472c6a2932a9b))
|
||||
* **deps:** update dependency zod to ^3.25.71 ([#3534](https://github.com/homarr-labs/homarr/issues/3534)) ([95c8aad](https://github.com/homarr-labs/homarr/commit/95c8aadb0c98f116ff3e256c770636267da82fdc))
|
||||
* **deps:** update dependency zod to ^3.25.72 ([#3535](https://github.com/homarr-labs/homarr/issues/3535)) ([0858a42](https://github.com/homarr-labs/homarr/commit/0858a42b8137f003fb7500f0dbccbb9413a6c470))
|
||||
* **deps:** update dependency zod to ^3.25.73 ([#3543](https://github.com/homarr-labs/homarr/issues/3543)) ([7061a03](https://github.com/homarr-labs/homarr/commit/7061a03b2ef90c51754a4e39b22742c34e5ebc80))
|
||||
* **deps:** update dependency zod to ^3.25.74 ([#3547](https://github.com/homarr-labs/homarr/issues/3547)) ([b057059](https://github.com/homarr-labs/homarr/commit/b057059ff1fb8fcd29893000d30ddcba09f2b775))
|
||||
* **deps:** update nextjs monorepo to v15.3.5 ([#3541](https://github.com/homarr-labs/homarr/issues/3541)) ([d5181d0](https://github.com/homarr-labs/homarr/commit/d5181d0c0e1f25a119a9e91a9544a8f76e0b0901))
|
||||
* **deps:** update tanstack-query monorepo to ^5.81.5 ([#3495](https://github.com/homarr-labs/homarr/issues/3495)) ([5346b96](https://github.com/homarr-labs/homarr/commit/5346b9698444a503e432ee4b0679e408e2c44d96))
|
||||
* **deps:** update tiptap monorepo to v2.23.1 ([#3516](https://github.com/homarr-labs/homarr/issues/3516)) ([0a0e7f9](https://github.com/homarr-labs/homarr/commit/0a0e7f9602429003d45b99fade5f1ad121aa67f3))
|
||||
* **deps:** update tiptap monorepo to v2.24.0 ([#3532](https://github.com/homarr-labs/homarr/issues/3532)) ([0e1f693](https://github.com/homarr-labs/homarr/commit/0e1f6934f9755503ce289b71549b6737171e1c57))
|
||||
* **deps:** update tiptap monorepo to v2.24.1 ([#3536](https://github.com/homarr-labs/homarr/issues/3536)) ([63e2ace](https://github.com/homarr-labs/homarr/commit/63e2acefbdf09d4aa7e5a9522cfb4b0c89fb66e9))
|
||||
* **deps:** update tiptap monorepo to v2.24.2 ([#3544](https://github.com/homarr-labs/homarr/issues/3544)) ([66f663a](https://github.com/homarr-labs/homarr/commit/66f663aad4b35d0cbfb1ffd60545fd0272b70990))
|
||||
|
||||
## [1.26.0](https://github.com/homarr-labs/homarr/compare/v1.25.0...v1.26.0) (2025-06-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add ntfy integration ([#2900](https://github.com/homarr-labs/homarr/issues/2900)) ([e110a84](https://github.com/homarr-labs/homarr/commit/e110a84fdd5fa2468786e1505043d8a7dc5cfa1b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @auth/core to ^0.40.0 ([#3448](https://github.com/homarr-labs/homarr/issues/3448)) ([2c7f5de](https://github.com/homarr-labs/homarr/commit/2c7f5de19271f7c65a9979b37d9602bb84c68564))
|
||||
* **deps:** update dependency @auth/drizzle-adapter to ^1.10.0 ([#3449](https://github.com/homarr-labs/homarr/issues/3449)) ([7b8dc16](https://github.com/homarr-labs/homarr/commit/7b8dc1670d0c8473524f71e1ee872e6ea7d2e0ce))
|
||||
* **deps:** update dependency @tiptap/extension-highlight to v2.22.1 ([#3433](https://github.com/homarr-labs/homarr/issues/3433)) ([f6039a5](https://github.com/homarr-labs/homarr/commit/f6039a5976da70388cb32eecfbf57ac02e6fc050))
|
||||
* **deps:** update dependency better-sqlite3 to ^12.1.0 ([#3462](https://github.com/homarr-labs/homarr/issues/3462)) ([fa421b3](https://github.com/homarr-labs/homarr/commit/fa421b3778c00b30b68ceadfb370803eba1a72f2))
|
||||
* **deps:** update dependency better-sqlite3 to ^12.1.1 ([#3470](https://github.com/homarr-labs/homarr/issues/3470)) ([eadec21](https://github.com/homarr-labs/homarr/commit/eadec212369b8ee81eec00b3ecbd40d1c90972d7))
|
||||
* **deps:** update dependency better-sqlite3 to v12 ([#3441](https://github.com/homarr-labs/homarr/issues/3441)) ([95be039](https://github.com/homarr-labs/homarr/commit/95be0391a651b59a2611ca5ffa7825c6b337a996))
|
||||
* **deps:** update dependency dotenv to ^16.6.0 ([#3484](https://github.com/homarr-labs/homarr/issues/3484)) ([7477d02](https://github.com/homarr-labs/homarr/commit/7477d02cadd88608f33edf0836f7d7e164800dde))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.2 ([#3455](https://github.com/homarr-labs/homarr/issues/3455)) ([8ffbbd6](https://github.com/homarr-labs/homarr/commit/8ffbbd6bf9bb1c329943ae5033029eed115b43dd))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.3 ([#3482](https://github.com/homarr-labs/homarr/issues/3482)) ([7d0ac46](https://github.com/homarr-labs/homarr/commit/7d0ac46753b1cf6bee1f53297b0011e4e86fdffd))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.4 ([#3490](https://github.com/homarr-labs/homarr/issues/3490)) ([670de0f](https://github.com/homarr-labs/homarr/commit/670de0f5fbb9faf950fdb38e4f4cfb57f21257c4))
|
||||
* **deps:** update dependency eslint-plugin-import to ^2.32.0 ([#3430](https://github.com/homarr-labs/homarr/issues/3430)) ([a7052bb](https://github.com/homarr-labs/homarr/commit/a7052bb16f52ccb47d2c8ac205fdf01cf8e8c01e))
|
||||
* **deps:** update dependency ldapts to v8.0.2 ([#3481](https://github.com/homarr-labs/homarr/issues/3481)) ([114de40](https://github.com/homarr-labs/homarr/commit/114de40634b501ecea7be8d9ae1d2a19686845c1))
|
||||
* **deps:** update dependency maria2 to ^0.4.1 ([#3456](https://github.com/homarr-labs/homarr/issues/3456)) ([4e892ef](https://github.com/homarr-labs/homarr/commit/4e892efab7f456b93e993c2c3cb6068f06ea1e4f))
|
||||
* **deps:** update dependency next-auth to v5.0.0-beta.29 ([#3447](https://github.com/homarr-labs/homarr/issues/3447)) ([da2d22b](https://github.com/homarr-labs/homarr/commit/da2d22b1b9b87fe7aa1cbac76909af2d2ef57d3f))
|
||||
* **deps:** update dependency next-intl to v4.2.0 ([#3457](https://github.com/homarr-labs/homarr/issues/3457)) ([86d4585](https://github.com/homarr-labs/homarr/commit/86d4585035c5604a219fd0be404104f62a0be0b2))
|
||||
* **deps:** update dependency next-intl to v4.3.0 ([#3458](https://github.com/homarr-labs/homarr/issues/3458)) ([ded3993](https://github.com/homarr-labs/homarr/commit/ded39930ca1d8c4868f9541c7fad18a234ceeb3f))
|
||||
* **deps:** update dependency next-intl to v4.3.1 ([#3465](https://github.com/homarr-labs/homarr/issues/3465)) ([5d96744](https://github.com/homarr-labs/homarr/commit/5d9674412e6e99af1fb1fce7143ce4b6717ff29b))
|
||||
* **deps:** update dependency prettier to ^3.6.0 ([#3452](https://github.com/homarr-labs/homarr/issues/3452)) ([07ce1c1](https://github.com/homarr-labs/homarr/commit/07ce1c1a2d197f981818a9b1e567102f0772271d))
|
||||
* **deps:** update dependency prettier to ^3.6.1 ([#3469](https://github.com/homarr-labs/homarr/issues/3469)) ([34faa9e](https://github.com/homarr-labs/homarr/commit/34faa9e58ce061c4c99d207cfc6c56cbb7045c03))
|
||||
* **deps:** update dependency prettier to ^3.6.2 ([#3489](https://github.com/homarr-labs/homarr/issues/3489)) ([60786bf](https://github.com/homarr-labs/homarr/commit/60786bf9ac81ce13b6304f5284fb6049e2d7d7dc))
|
||||
* **deps:** update dependency recharts to ^2.15.4 ([#3429](https://github.com/homarr-labs/homarr/issues/3429)) ([caedac4](https://github.com/homarr-labs/homarr/commit/caedac490ed3aaf8f6a815d799ba9fafe9de18a2))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.25.3 ([#3479](https://github.com/homarr-labs/homarr/issues/3479)) ([1cd9fa8](https://github.com/homarr-labs/homarr/commit/1cd9fa8e82eef1f69be06855d076a364883914df))
|
||||
* **deps:** update dependency typescript-eslint to ^8.35.0 ([#3460](https://github.com/homarr-labs/homarr/issues/3460)) ([1813d44](https://github.com/homarr-labs/homarr/commit/1813d4435e08f6a800ee76561e66e6371b30ec80))
|
||||
* **deps:** update dependency undici to v7.11.0 ([#3486](https://github.com/homarr-labs/homarr/issues/3486)) ([01fdd79](https://github.com/homarr-labs/homarr/commit/01fdd7955906c661a620b8fac148693dc4632ebd))
|
||||
* **deps:** update mantine monorepo to ^8.1.2 ([#3471](https://github.com/homarr-labs/homarr/issues/3471)) ([5e6dfb3](https://github.com/homarr-labs/homarr/commit/5e6dfb3a5c2d456fa20fbcd07f98c01d0ab7c4b7))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.12 ([#3436](https://github.com/homarr-labs/homarr/issues/3436)) ([86608f5](https://github.com/homarr-labs/homarr/commit/86608f5a9ffe9ea2c57a9191175994a24eea5dc7))
|
||||
* **deps:** update tanstack-query monorepo to ^5.81.0 ([#3437](https://github.com/homarr-labs/homarr/issues/3437)) ([e6f05f9](https://github.com/homarr-labs/homarr/commit/e6f05f9d94912ef5f38ad4ab222e20d433c627eb))
|
||||
* **deps:** update tanstack-query monorepo to ^5.81.1 ([#3440](https://github.com/homarr-labs/homarr/issues/3440)) ([b8b71da](https://github.com/homarr-labs/homarr/commit/b8b71da291541b714e053ea1bda4e49053261936))
|
||||
* **deps:** update tanstack-query monorepo to ^5.81.2 ([#3442](https://github.com/homarr-labs/homarr/issues/3442)) ([3f0e9c3](https://github.com/homarr-labs/homarr/commit/3f0e9c37385ae23370618f1a11870642a9b3d030))
|
||||
* **deps:** update tanstack-query monorepo to ^5.81.4 ([#3493](https://github.com/homarr-labs/homarr/issues/3493)) ([a737331](https://github.com/homarr-labs/homarr/commit/a737331d207abfd4ab8a8869c70257b5f5d50d5a))
|
||||
* **deps:** update tiptap monorepo to v2.22.1 ([#3432](https://github.com/homarr-labs/homarr/issues/3432)) ([8ccbe7f](https://github.com/homarr-labs/homarr/commit/8ccbe7ff52eff742031c528bcc1e15916ba1fe81))
|
||||
* **deps:** update tiptap monorepo to v2.22.2 ([#3438](https://github.com/homarr-labs/homarr/issues/3438)) ([f677bd6](https://github.com/homarr-labs/homarr/commit/f677bd6b12630130d8cf95a4f2462e4bab1226db))
|
||||
* **deps:** update tiptap monorepo to v2.22.3 ([#3443](https://github.com/homarr-labs/homarr/issues/3443)) ([add73a1](https://github.com/homarr-labs/homarr/commit/add73a1a3c19e0efd2d417463e4f4d72a9fd1c02))
|
||||
* **deps:** update tiptap monorepo to v2.23.0 ([#3487](https://github.com/homarr-labs/homarr/issues/3487)) ([b027f3a](https://github.com/homarr-labs/homarr/commit/b027f3a18084743b452dd35ff8e0fa30f59acd85))
|
||||
* **deps:** update trpc monorepo to ^11.4.3 ([#3491](https://github.com/homarr-labs/homarr/issues/3491)) ([d326f3b](https://github.com/homarr-labs/homarr/commit/d326f3b4b3f5fc342404a12b25380d9dd6cd924a))
|
||||
* **jellyseerr:** add missing deleted media status ([#3439](https://github.com/homarr-labs/homarr/issues/3439)) ([6b474c3](https://github.com/homarr-labs/homarr/commit/6b474c3df470450598b6b9472bf9f52740b8ed21))
|
||||
|
||||
## [1.25.0](https://github.com/homarr-labs/homarr/compare/v1.24.0...v1.25.0) (2025-06-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **integrations:** add support for self-signed-cert-in-chain request error ([#3399](https://github.com/homarr-labs/homarr/issues/3399)) ([e6cb35b](https://github.com/homarr-labs/homarr/commit/e6cb35b7c2dc45349a9cf0819aa21228aea03e26))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency node-cron to ^4.1.1 ([#3427](https://github.com/homarr-labs/homarr/issues/3427)) ([f6a99ff](https://github.com/homarr-labs/homarr/commit/f6a99ffedd690c01e18f445ade6343f1ca476b27))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.24.2 ([#3405](https://github.com/homarr-labs/homarr/issues/3405)) ([bdca920](https://github.com/homarr-labs/homarr/commit/bdca92036a3dde7f655735c305e55c5c15b63440))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.25.2 ([#3420](https://github.com/homarr-labs/homarr/issues/3420)) ([02cd7a5](https://github.com/homarr-labs/homarr/commit/02cd7a50dad9e954cfd569b0f1efcc4fe4fd5da4))
|
||||
* **deps:** update dependency tsdav to ^2.1.5 ([#3414](https://github.com/homarr-labs/homarr/issues/3414)) ([6bcfe78](https://github.com/homarr-labs/homarr/commit/6bcfe789561c518e7ce6c78a9be95412a50f04dd))
|
||||
* **deps:** update dependency typescript-eslint to ^8.34.1 ([#3409](https://github.com/homarr-labs/homarr/issues/3409)) ([637bfb8](https://github.com/homarr-labs/homarr/commit/637bfb8b47b7dc6c70163bd481c47bca9bd93d55))
|
||||
* **deps:** update dependency zod to ^3.25.65 ([#3410](https://github.com/homarr-labs/homarr/issues/3410)) ([a8c9c07](https://github.com/homarr-labs/homarr/commit/a8c9c075f08bca4b86307e98156cae7167ef6ab1))
|
||||
* **deps:** update dependency zod to ^3.25.67 ([#3413](https://github.com/homarr-labs/homarr/issues/3413)) ([56ca8b7](https://github.com/homarr-labs/homarr/commit/56ca8b7b0b1c0187457c085af1c7f4504d72ed9a))
|
||||
* **deps:** update dependency zod-validation-error to ^3.5.1 ([#3406](https://github.com/homarr-labs/homarr/issues/3406)) ([adbef25](https://github.com/homarr-labs/homarr/commit/adbef252c61c29fbbc8c7b0e325a0e9d69bb3191))
|
||||
* **deps:** update dependency zod-validation-error to ^3.5.2 ([#3417](https://github.com/homarr-labs/homarr/issues/3417)) ([430f671](https://github.com/homarr-labs/homarr/commit/430f6714345b5b90859fd3302f76fd3d14956148))
|
||||
* **deps:** update mantine monorepo to ^8.1.1 ([#3404](https://github.com/homarr-labs/homarr/issues/3404)) ([f97a261](https://github.com/homarr-labs/homarr/commit/f97a2619734dcf3b326c69587fa2f4e170cb9c88))
|
||||
* **deps:** update nextjs monorepo to v15.3.4 ([#3421](https://github.com/homarr-labs/homarr/issues/3421)) ([3759a98](https://github.com/homarr-labs/homarr/commit/3759a9831c6fe54b0b0eb0e101c8eb912b752b41))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.10 ([#3422](https://github.com/homarr-labs/homarr/issues/3422)) ([7c58e8a](https://github.com/homarr-labs/homarr/commit/7c58e8ac9bdb2868637648b43b7965189421b295))
|
||||
* **deps:** update tiptap monorepo to v2.14.1 ([#3423](https://github.com/homarr-labs/homarr/issues/3423)) ([9e4b463](https://github.com/homarr-labs/homarr/commit/9e4b4635bc582e4d92821844d58fdb3d057f7fec))
|
||||
* **deps:** update tiptap monorepo to v2.22.0 ([#3425](https://github.com/homarr-labs/homarr/issues/3425)) ([ec4dde6](https://github.com/homarr-labs/homarr/commit/ec4dde64fdd4302b091a0255ec1bcdc6b2caca89))
|
||||
* **deps:** update trpc monorepo to ^11.4.2 ([#3419](https://github.com/homarr-labs/homarr/issues/3419)) ([8e428e2](https://github.com/homarr-labs/homarr/commit/8e428e20a764c54c15fc44b57f85fa684cfbc00e))
|
||||
* **downloads:** not updating automatically ([#3412](https://github.com/homarr-labs/homarr/issues/3412)) ([b5ef7e5](https://github.com/homarr-labs/homarr/commit/b5ef7e5194a828abfddcac5eb492910de5f1f7d8))
|
||||
* **widget-integration-select:** fixed error about `<p>` being descendant of `<p>` ([#3418](https://github.com/homarr-labs/homarr/issues/3418)) ([73fff02](https://github.com/homarr-labs/homarr/commit/73fff02ad0a3a710aa39cda7dccf9206569c2879))
|
||||
|
||||
## [1.24.0](https://github.com/homarr-labs/homarr/compare/v1.23.0...v1.24.0) (2025-06-13)
|
||||
|
||||
### Features
|
||||
|
||||
* reduce cron job intervals ([#3235](https://github.com/homarr-labs/homarr/issues/3235)) ([f67702e](https://github.com/homarr-labs/homarr/commit/f67702e1d32954b4f6db28c02b1a25e56122d779))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* csp prevents external videos ([#3372](https://github.com/homarr-labs/homarr/issues/3372)) ([c59cded](https://github.com/homarr-labs/homarr/commit/c59cdeda47a504069c09634dafe36a9bc80c922f))
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.7 ([#3357](https://github.com/homarr-labs/homarr/issues/3357)) ([22f0cd8](https://github.com/homarr-labs/homarr/commit/22f0cd885e6b3f0cd15daa60840554a545eb2532))
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.8 ([#3364](https://github.com/homarr-labs/homarr/issues/3364)) ([ab3ee3b](https://github.com/homarr-labs/homarr/commit/ab3ee3bac186913cbc76c1381c90b3c90a447185))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.2.5 ([#3353](https://github.com/homarr-labs/homarr/issues/3353)) ([9e86de6](https://github.com/homarr-labs/homarr/commit/9e86de6e1781ef2c088f83af1ba341baecb496da))
|
||||
* **deps:** update dependency glob to ^11.0.3 ([#3391](https://github.com/homarr-labs/homarr/issues/3391)) ([f16efac](https://github.com/homarr-labs/homarr/commit/f16efac05a17b77303f4c41b8910d491d237add2))
|
||||
* **deps:** update dependency sass to ^1.89.2 ([#3362](https://github.com/homarr-labs/homarr/issues/3362)) ([b1a559d](https://github.com/homarr-labs/homarr/commit/b1a559d80161f59114ba3098005c3334662987eb))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.24.1 ([#3368](https://github.com/homarr-labs/homarr/issues/3368)) ([fa52b55](https://github.com/homarr-labs/homarr/commit/fa52b5582880075cd76826641547ccbf42b56f7a))
|
||||
* **deps:** update dependency tsx to v4.20.0 ([#3375](https://github.com/homarr-labs/homarr/issues/3375)) ([3d07306](https://github.com/homarr-labs/homarr/commit/3d07306e7d3bf90db7f7c02d68550a137c5554bc))
|
||||
* **deps:** update dependency tsx to v4.20.1 ([#3380](https://github.com/homarr-labs/homarr/issues/3380)) ([e7652ea](https://github.com/homarr-labs/homarr/commit/e7652ea4eb1ddaf8373c02abfcdb5440bf55c236))
|
||||
* **deps:** update dependency tsx to v4.20.2 ([#3387](https://github.com/homarr-labs/homarr/issues/3387)) ([79519ec](https://github.com/homarr-labs/homarr/commit/79519ec0ebf4ff101fa1cd395f974e023d1af3c3))
|
||||
* **deps:** update dependency tsx to v4.20.3 ([#3393](https://github.com/homarr-labs/homarr/issues/3393)) ([c9097a9](https://github.com/homarr-labs/homarr/commit/c9097a95e2072ed619e93301f8d4644aaadce2dd))
|
||||
* **deps:** update dependency typescript-eslint to ^8.34.0 ([#3359](https://github.com/homarr-labs/homarr/issues/3359)) ([f6da091](https://github.com/homarr-labs/homarr/commit/f6da0915773dd60b40475f258d90e5f3dbdb6729))
|
||||
* **deps:** update dependency zod to ^3.25.56 ([#3340](https://github.com/homarr-labs/homarr/issues/3340)) ([aafd883](https://github.com/homarr-labs/homarr/commit/aafd883c3250ddf3438877d82b4b63a6a15eadda))
|
||||
* **deps:** update dependency zod to ^3.25.57 ([#3367](https://github.com/homarr-labs/homarr/issues/3367)) ([e7a9797](https://github.com/homarr-labs/homarr/commit/e7a9797484e9e2cd595d31e6db4cdf63e25e3b82))
|
||||
* **deps:** update dependency zod to ^3.25.58 ([#3373](https://github.com/homarr-labs/homarr/issues/3373)) ([f37fc0e](https://github.com/homarr-labs/homarr/commit/f37fc0edfee45386b3469bac11c91e4f17c83673))
|
||||
* **deps:** update dependency zod to ^3.25.60 ([#3378](https://github.com/homarr-labs/homarr/issues/3378)) ([7388c90](https://github.com/homarr-labs/homarr/commit/7388c907a57c7999a48118d96a3f34d2c3656f79))
|
||||
* **deps:** update dependency zod to ^3.25.61 ([#3379](https://github.com/homarr-labs/homarr/issues/3379)) ([863f935](https://github.com/homarr-labs/homarr/commit/863f935ef4c27f87a632a5e0b376fe088f4e8a04))
|
||||
* **deps:** update dependency zod to ^3.25.62 ([#3382](https://github.com/homarr-labs/homarr/issues/3382)) ([0b200dc](https://github.com/homarr-labs/homarr/commit/0b200dc9e2239060ddf2670f8508f68e624e821e))
|
||||
* **deps:** update dependency zod to ^3.25.63 ([#3386](https://github.com/homarr-labs/homarr/issues/3386)) ([153c2ef](https://github.com/homarr-labs/homarr/commit/153c2ef8a0098cbe2d998c98a25a0c5880f0da5d))
|
||||
* **deps:** update dependency zod to ^3.25.64 ([#3392](https://github.com/homarr-labs/homarr/issues/3392)) ([5dc0ee8](https://github.com/homarr-labs/homarr/commit/5dc0ee8842dbd4bde468c5762cf5dc0d73958242))
|
||||
* **deps:** update dependency zod-validation-error to ^3.5.0 ([#3388](https://github.com/homarr-labs/homarr/issues/3388)) ([747d034](https://github.com/homarr-labs/homarr/commit/747d034114bd2a3b381aa7318c59015b21811353))
|
||||
* **deps:** update mantine monorepo to ^8.1.0 ([#3360](https://github.com/homarr-labs/homarr/issues/3360)) ([d862ae0](https://github.com/homarr-labs/homarr/commit/d862ae03e319d173468cd7797bd74be022cbc966))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.7 ([#3385](https://github.com/homarr-labs/homarr/issues/3385)) ([c5cc8f1](https://github.com/homarr-labs/homarr/commit/c5cc8f1d375d5a582b169da706a5b433a0181c20))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.0.1 ([#3343](https://github.com/homarr-labs/homarr/issues/3343)) ([6ec66b1](https://github.com/homarr-labs/homarr/commit/6ec66b1fe2d5779b2267532d75d6933b3460ffd5))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.0.2 ([#3352](https://github.com/homarr-labs/homarr/issues/3352)) ([98c4cd1](https://github.com/homarr-labs/homarr/commit/98c4cd1e39286164c09f7d8e961488e26fa2c562))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.0.3 ([#3369](https://github.com/homarr-labs/homarr/issues/3369)) ([ad444e8](https://github.com/homarr-labs/homarr/commit/ad444e8fa7a186710deafd98f2aa48717de1ec80))
|
||||
* **deps:** update trpc monorepo to ^11.4.0 ([#3389](https://github.com/homarr-labs/homarr/issues/3389)) ([3bf4f9e](https://github.com/homarr-labs/homarr/commit/3bf4f9e0588440d74dfaf7016acc66c1ed40843a))
|
||||
* **deps:** update trpc monorepo to ^11.4.1 ([#3395](https://github.com/homarr-labs/homarr/issues/3395)) ([6d9992d](https://github.com/homarr-labs/homarr/commit/6d9992d7114f97c24e02c40dcaf11fad90608a1c))
|
||||
* module not found sshcrypto.node during development ([#3349](https://github.com/homarr-labs/homarr/issues/3349)) ([1b3770a](https://github.com/homarr-labs/homarr/commit/1b3770aafbe4e903e6235dc681edac556a36e4e0))
|
||||
* **pi-hole:** cached session not invalidated when changing password ([#3351](https://github.com/homarr-labs/homarr/issues/3351)) ([a9e68e1](https://github.com/homarr-labs/homarr/commit/a9e68e13d53c8e06d150ecba085680cd248d4f64))
|
||||
|
||||
## [1.23.0](https://github.com/homarr-labs/homarr/compare/v1.22.0...v1.23.0) (2025-06-06)
|
||||
|
||||
### Features
|
||||
|
||||
+3
-5
@@ -1,4 +1,4 @@
|
||||
FROM node:22.16.0-alpine AS base
|
||||
FROM node:22.17.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
@@ -10,8 +10,6 @@ RUN apk add --no-cache libc6-compat curl bash
|
||||
RUN apk update
|
||||
COPY . .
|
||||
|
||||
# Install working version of corepack (See https://github.com/nodejs/corepack/issues/612)
|
||||
RUN npm install -g corepack@0.31.0 && corepack --version
|
||||
RUN corepack enable pnpm && pnpm install --recursive --frozen-lockfile
|
||||
|
||||
# Copy static data as it is not part of the build
|
||||
@@ -19,8 +17,7 @@ COPY static-data ./static-data
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
ARG DISABLE_REDIS_LOGS='true'
|
||||
# Install working version of corepack (See https://github.com/nodejs/corepack/issues/612)
|
||||
RUN npm install -g corepack@0.31.0 && corepack --version
|
||||
|
||||
RUN corepack enable pnpm && pnpm build
|
||||
|
||||
FROM base AS runner
|
||||
@@ -67,6 +64,7 @@ ENV DB_URL='/appdata/db/db.sqlite'
|
||||
ENV DB_DIALECT='sqlite'
|
||||
ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
|
||||
@@ -17,31 +17,16 @@ const withNextIntl = createNextIntlPlugin({
|
||||
requestConfig: "../../packages/translation/src/request.ts",
|
||||
});
|
||||
|
||||
interface WebpackConfig {
|
||||
module: {
|
||||
rules: {
|
||||
test: RegExp;
|
||||
loader: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
/** We already do linting and typechecking as separate tasks in CI */
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
webpack: (config: WebpackConfig, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.module.rules.push({
|
||||
test: /\.node$/,
|
||||
loader: "node-loader",
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
/**
|
||||
* dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612
|
||||
*/
|
||||
serverExternalPackages: ["dockerode"],
|
||||
experimental: {
|
||||
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
||||
},
|
||||
@@ -68,6 +53,7 @@ const nextConfig: NextConfig = {
|
||||
form-action 'self';
|
||||
img-src * data:;
|
||||
font-src * data:;
|
||||
media-src * data:;
|
||||
`
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim(),
|
||||
|
||||
+28
-26
@@ -25,6 +25,7 @@
|
||||
"@homarr/boards": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
@@ -33,6 +34,7 @@
|
||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||
"@homarr/gridstack": "^1.12.0",
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/image-proxy": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/modals": "workspace:^0.1.0",
|
||||
@@ -48,59 +50,59 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.0.2",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/dropzone": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/modals": "^8.0.2",
|
||||
"@mantine/tiptap": "^8.0.2",
|
||||
"@mantine/colors-generator": "^8.1.3",
|
||||
"@mantine/core": "^8.1.3",
|
||||
"@mantine/dropzone": "^8.1.3",
|
||||
"@mantine/hooks": "^8.1.3",
|
||||
"@mantine/modals": "^8.1.3",
|
||||
"@mantine/tiptap": "^8.1.3",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@tanstack/react-query-devtools": "^5.80.6",
|
||||
"@tanstack/react-query-next-experimental": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/next": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query-devtools": "^5.83.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.83.0",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/next": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@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": "^16.5.0",
|
||||
"dotenv": "^17.2.0",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^11.0.2",
|
||||
"glob": "^11.0.3",
|
||||
"jotai": "^2.12.5",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.3",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"next": "15.4.2",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.89.1",
|
||||
"sass": "^1.89.2",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.24.0",
|
||||
"swagger-ui-react": "^5.27.0",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.25.56"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"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.15.30",
|
||||
"@types/node": "^22.16.4",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.6",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.28.0",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 595 KiB |
@@ -17,6 +17,7 @@ import {
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
@@ -102,12 +103,25 @@ export const ColorSettingsContent = ({ board }: Props) => {
|
||||
</InputWrapper>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<ColorInput
|
||||
label={t("board.field.iconColor.label")}
|
||||
format="hex"
|
||||
swatches={Object.values(theme.colors).map((color) => color[6])}
|
||||
{...form.getInputProps("iconColor")}
|
||||
/>
|
||||
<Group align="end">
|
||||
<ColorInput
|
||||
label={t("board.field.iconColor.label")}
|
||||
format="hex"
|
||||
swatches={Object.values(theme.colors).map((color) => color[6])}
|
||||
flex={1}
|
||||
{...form.getInputProps("iconColor")}
|
||||
/>
|
||||
<Button
|
||||
variant="subtle"
|
||||
leftSection={<IconX />}
|
||||
onClick={() => form.setFieldValue("iconColor", "")}
|
||||
disabled={!form.values.iconColor}
|
||||
>
|
||||
{t("board.field.clearColor.label")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<Select
|
||||
label={t("board.field.itemRadius.label")}
|
||||
description={t("board.field.itemRadius.description")}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Button, Fieldset, Grid, Group, Input, NumberInput, Slider, Stack, Text, TextInput } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { boardSaveLayoutsSchema } from "@homarr/validation/board";
|
||||
|
||||
+11
-1
@@ -1,4 +1,12 @@
|
||||
import { IconGrid3x3, IconKey, IconPassword, IconServer, IconUser } from "@tabler/icons-react";
|
||||
import {
|
||||
IconGrid3x3,
|
||||
IconKey,
|
||||
IconMessage,
|
||||
IconPassword,
|
||||
IconPasswordUser,
|
||||
IconServer,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||
import type { TablerIcon } from "@homarr/ui";
|
||||
@@ -9,4 +17,6 @@ export const integrationSecretIcons = {
|
||||
password: IconPassword,
|
||||
realm: IconServer,
|
||||
tokenId: IconGrid3x3,
|
||||
personalAccessToken: IconPasswordUser,
|
||||
topic: IconMessage,
|
||||
} satisfies Record<IntegrationSecretKind, TablerIcon>;
|
||||
+13
-7
@@ -4,8 +4,7 @@ import { IconAlertTriangle, IconCheck, IconCopy, IconExclamationCircle, IconRepe
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { getMantineColor } from "@homarr/common";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId, getMantineColor } from "@homarr/common";
|
||||
import { createDocumentationLink } from "@homarr/definitions";
|
||||
import { createModal, useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { AddCertificateModal } from "@homarr/modals-collection";
|
||||
@@ -30,6 +29,8 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
const { mutateAsync: trustHostnameAsync } = clientApi.certificates.trustHostnameMismatch.useMutation();
|
||||
const { mutateAsync: addCertificateAsync } = clientApi.certificates.addCertificate.useMutation();
|
||||
|
||||
const rootCertificate = getHeighestCertificate(error.data.certificate);
|
||||
|
||||
const handleTrustHostname = () => {
|
||||
const { hostname } = new URL(url);
|
||||
openConfirmModal({
|
||||
@@ -72,7 +73,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
"file",
|
||||
new File([error.data.certificate.pem], `${hostname}-${createId()}.crt`, {
|
||||
new File([rootCertificate.pem], `${hostname}-${createId()}.crt`, {
|
||||
type: "application/x-x509-ca-cert",
|
||||
}),
|
||||
);
|
||||
@@ -110,11 +111,11 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
<>
|
||||
{description}
|
||||
|
||||
<CertificateDetailsCard certificate={error.data.certificate} />
|
||||
<CertificateDetailsCard certificate={rootCertificate} />
|
||||
|
||||
{error.data.reason === "hostnameMismatch" && <HostnameMismatchAlert />}
|
||||
|
||||
{!error.data.certificate.isSelfSigned && error.data.reason === "untrusted" && <CertificateExtractAlert />}
|
||||
{!rootCertificate.isSelfSigned && error.data.reason === "untrusted" && <CertificateExtractAlert />}
|
||||
|
||||
{showRetryButton && (
|
||||
<Button
|
||||
@@ -127,7 +128,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{(error.data.reason === "untrusted" && error.data.certificate.isSelfSigned) ||
|
||||
{(error.data.reason === "untrusted" && rootCertificate.isSelfSigned) ||
|
||||
error.data.reason === "hostnameMismatch" ? (
|
||||
<Button
|
||||
variant="default"
|
||||
@@ -137,7 +138,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
{tError("certificate.action.trust.label")}
|
||||
</Button>
|
||||
) : null}
|
||||
{error.data.reason === "untrusted" && !error.data.certificate.isSelfSigned ? (
|
||||
{error.data.reason === "untrusted" && !rootCertificate.isSelfSigned ? (
|
||||
<Button
|
||||
variant="default"
|
||||
fullWidth
|
||||
@@ -325,3 +326,8 @@ const PemContentModal = createModal<{ content: string }>(({ actions, innerProps
|
||||
},
|
||||
size: "lg",
|
||||
});
|
||||
|
||||
const getHeighestCertificate = (certificate: MappedCertificate): MappedCertificate => {
|
||||
if (certificate.issuerCertificate) return getHeighestCertificate(certificate.issuerCertificate);
|
||||
return certificate;
|
||||
};
|
||||
@@ -10,15 +10,20 @@ import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
export const IntegrationCreateDropdownContent = () => {
|
||||
interface IntegrationCreateDropdownContentProps {
|
||||
enableMockIntegration: boolean;
|
||||
}
|
||||
|
||||
export const IntegrationCreateDropdownContent = ({ enableMockIntegration }: IntegrationCreateDropdownContentProps) => {
|
||||
const t = useI18n();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filteredKinds = useMemo(() => {
|
||||
return integrationKinds.filter((kind) =>
|
||||
getIntegrationName(kind).toLowerCase().includes(search.toLowerCase().trim()),
|
||||
);
|
||||
}, [search]);
|
||||
return integrationKinds
|
||||
.filter((kind) => enableMockIntegration || kind !== "mock")
|
||||
.filter((kind) => getIntegrationName(kind).toLowerCase().includes(search.toLowerCase().trim()))
|
||||
.sort((kindA, kindB) => getIntegrationName(kindA).localeCompare(getIntegrationName(kindB)));
|
||||
}, [search, enableMockIntegration]);
|
||||
|
||||
const handleSearch = React.useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => setSearch(event.target.value),
|
||||
|
||||
@@ -21,7 +21,13 @@ import { z } from "zod";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||
import { getAllSecretKindOptions, getIconUrl, getIntegrationName, integrationDefs } from "@homarr/definitions";
|
||||
import {
|
||||
getAllSecretKindOptions,
|
||||
getIconUrl,
|
||||
getIntegrationDefaultUrl,
|
||||
getIntegrationName,
|
||||
integrationDefs,
|
||||
} from "@homarr/definitions";
|
||||
import type { UseFormReturnType } from "@homarr/form";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
@@ -54,7 +60,7 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
const form = useZodForm(formSchema, {
|
||||
initialValues: {
|
||||
name: searchParams.name ?? getIntegrationName(searchParams.kind),
|
||||
url: searchParams.url ?? "",
|
||||
url: searchParams.url ?? getIntegrationDefaultUrl(searchParams.kind) ?? "",
|
||||
secrets: secretKinds[0].map((kind) => ({
|
||||
kind,
|
||||
value: "",
|
||||
|
||||
@@ -41,6 +41,7 @@ import { CountBadge, IntegrationAvatar } from "@homarr/ui";
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { NoResults } from "~/components/no-results";
|
||||
import { env } from "~/env";
|
||||
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
|
||||
import { DeleteIntegrationActionButton } from "./_integration-buttons";
|
||||
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
|
||||
@@ -114,7 +115,7 @@ const IntegrationSelectMenu = ({ children }: PropsWithChildren) => {
|
||||
>
|
||||
{children}
|
||||
<MenuDropdown>
|
||||
<IntegrationCreateDropdownContent />
|
||||
<IntegrationCreateDropdownContent enableMockIntegration={env.UNSAFE_ENABLE_MOCK_INTEGRATION} />
|
||||
</MenuDropdown>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import { SimpleGrid, Skeleton, Stack, Title } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { KubernetesLabelResourceType } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { MantineReactTable } from "mantine-react-table";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { KubernetesIngress } from "@homarr/definitions";
|
||||
import type { ScopedTranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { MantineReactTable } from "mantine-react-table";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { KubernetesService } from "@homarr/definitions";
|
||||
import type { ScopedTranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { Select } from "@mantine/core";
|
||||
|
||||
import type { LogLevel } from "@homarr/log/constants";
|
||||
import { logLevelConfiguration, logLevels } from "@homarr/log/constants";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { useLogContext } from "./log-context";
|
||||
|
||||
export const LogLevelSelection = () => {
|
||||
const { level, setLevel } = useLogContext();
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Select
|
||||
data={logLevels.map((level) => ({
|
||||
value: level,
|
||||
label: `${logLevelConfiguration[level].prefix} ${t(`log.level.option.${level}`)}`,
|
||||
}))}
|
||||
value={level}
|
||||
onChange={(value) => setLevel(value as LogLevel)}
|
||||
checkIconPosition="right"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { createContext, useContext, useMemo, useState } from "react";
|
||||
|
||||
import type { LogLevel } from "@homarr/log/constants";
|
||||
import { logLevels } from "@homarr/log/constants";
|
||||
|
||||
const LogContext = createContext<{
|
||||
level: LogLevel;
|
||||
setLevel: (level: LogLevel) => void;
|
||||
activeLevels: LogLevel[];
|
||||
} | null>(null);
|
||||
|
||||
interface LogContextProviderProps extends PropsWithChildren {
|
||||
defaultLevel: LogLevel;
|
||||
}
|
||||
|
||||
export const LogContextProvider = ({ defaultLevel, children }: LogContextProviderProps) => {
|
||||
const [level, setLevel] = useState(defaultLevel);
|
||||
const activeLevels = useMemo(() => logLevels.slice(0, logLevels.indexOf(level) + 1), [level]);
|
||||
|
||||
return <LogContext.Provider value={{ level, setLevel, activeLevels }}>{children}</LogContext.Provider>;
|
||||
};
|
||||
|
||||
export const useLogContext = () => {
|
||||
const context = useContext(LogContext);
|
||||
if (!context) {
|
||||
throw new Error("useLogContext must be used within a LogContextProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box } from "@mantine/core";
|
||||
import { Box, Group } from "@mantine/core";
|
||||
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
@@ -7,11 +7,14 @@ import "@xterm/xterm/css/xterm.css";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { env } from "@homarr/log/env";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { fullHeightWithoutHeaderAndFooter } from "~/constants";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { ClientSideTerminalComponent } from "./client";
|
||||
import { LogLevelSelection } from "./level-selection";
|
||||
import { LogContextProvider } from "./log-context";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const session = await auth();
|
||||
@@ -32,11 +35,14 @@ export default async function LogsManagementPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DynamicBreadcrumb />
|
||||
<LogContextProvider defaultLevel={env.LOG_LEVEL}>
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<DynamicBreadcrumb />
|
||||
<LogLevelSelection />
|
||||
</Group>
|
||||
<Box style={{ borderRadius: 6 }} h={fullHeightWithoutHeaderAndFooter} p="md" bg="black">
|
||||
<ClientSideTerminalComponent />
|
||||
</Box>
|
||||
</>
|
||||
</LogContextProvider>
|
||||
);
|
||||
}
|
||||
@@ -8,22 +8,29 @@ import { Terminal } from "@xterm/xterm";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
import { useLogContext } from "./log-context";
|
||||
import classes from "./terminal.module.css";
|
||||
|
||||
export const TerminalComponent = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { activeLevels } = useLogContext();
|
||||
|
||||
const terminalRef = useRef<Terminal>(null);
|
||||
clientApi.log.subscribe.useSubscription(undefined, {
|
||||
onData(data) {
|
||||
terminalRef.current?.writeln(`${data.timestamp} ${data.level} ${data.message}`);
|
||||
terminalRef.current?.refresh(0, terminalRef.current.rows - 1);
|
||||
clientApi.log.subscribe.useSubscription(
|
||||
{
|
||||
levels: activeLevels,
|
||||
},
|
||||
onError(err) {
|
||||
// This makes sense as logging might cause an infinite loop
|
||||
alert(err);
|
||||
{
|
||||
onData(data) {
|
||||
terminalRef.current?.writeln(data.message);
|
||||
terminalRef.current?.refresh(0, terminalRef.current.rows - 1);
|
||||
},
|
||||
onError(err) {
|
||||
// This makes sense as logging might cause an infinite loop
|
||||
alert(err);
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
|
||||
@@ -1,89 +1,216 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { ActionIcon, Badge, Card, Group, Stack, Text } from "@mantine/core";
|
||||
import { useListState } from "@mantine/hooks";
|
||||
import { IconPlayerPlay } from "@tabler/icons-react";
|
||||
import React, { useState, useTransition } from "react";
|
||||
import { ActionIcon, Badge, Button, Card, Group, Select, Stack, Text } from "@mantine/core";
|
||||
import { useMap } from "@mantine/hooks";
|
||||
import { IconPlayerPlay, IconPower, IconSettings } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import { getMantineColor, useTimeAgo } from "@homarr/common";
|
||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||
import type { TranslationKeys } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useForm } from "@homarr/form";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import { TranslationFunction } from "@homarr/translation";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import { IconPowerOff } from "@homarr/ui/icons";
|
||||
|
||||
interface JobsListProps {
|
||||
initialJobs: RouterOutputs["cronJobs"]["getJobs"];
|
||||
}
|
||||
|
||||
interface JobState {
|
||||
job: JobsListProps["initialJobs"][number];
|
||||
type JobName = RouterOutputs["cronJobs"]["getJobs"][number]["name"];
|
||||
|
||||
export const JobsList = ({ initialJobs }: JobsListProps) => {
|
||||
const [jobs] = clientApi.cronJobs.getJobs.useSuspenseQuery(undefined, {
|
||||
initialData: initialJobs,
|
||||
refetchOnMount: false,
|
||||
});
|
||||
|
||||
const jobStatusMap = useMap<string, TaskStatus | null>(initialJobs.map(({ name }) => [name, null] as const));
|
||||
|
||||
clientApi.cronJobs.subscribeToStatusUpdates.useSubscription(undefined, {
|
||||
onData: (data) => {
|
||||
jobStatusMap.set(data.name, data);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{jobs.map((job) => {
|
||||
const status = jobStatusMap.get(job.name);
|
||||
|
||||
return <JobCard key={job.name} job={job} status={status ?? null} />;
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const cronExpressions = [
|
||||
{
|
||||
value: "*/5 * * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
|
||||
},
|
||||
{
|
||||
value: "*/10 * * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 10 }),
|
||||
},
|
||||
{
|
||||
value: "*/20 * * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 20 }),
|
||||
},
|
||||
{
|
||||
value: "* * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 1 }),
|
||||
},
|
||||
{
|
||||
value: "*/5 * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 5 }),
|
||||
},
|
||||
{
|
||||
value: "*/10 * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 10 }),
|
||||
},
|
||||
{
|
||||
value: "*/15 * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 15 }),
|
||||
},
|
||||
{
|
||||
value: "0 * * * *",
|
||||
label: (t) => t("management.page.tool.tasks.interval.hours", { interval: 1 }),
|
||||
},
|
||||
{
|
||||
value: "0 0 * * */1",
|
||||
label: (t) => t("management.page.tool.tasks.interval.midnight"),
|
||||
},
|
||||
{
|
||||
value: "0 0 * * 1",
|
||||
label: (t) => t("management.page.tool.tasks.interval.weeklyMonday"),
|
||||
},
|
||||
] satisfies { value: string; label: (t: TranslationFunction) => string }[];
|
||||
|
||||
interface JobCardProps {
|
||||
job: RouterOutputs["cronJobs"]["getJobs"][number];
|
||||
status: TaskStatus | null;
|
||||
}
|
||||
|
||||
export const JobsList = ({ initialJobs }: JobsListProps) => {
|
||||
const t = useScopedI18n("management.page.tool.tasks");
|
||||
const [jobs, handlers] = useListState<JobState>(
|
||||
initialJobs.map((job) => ({
|
||||
job,
|
||||
status: null,
|
||||
})),
|
||||
);
|
||||
clientApi.cronJobs.subscribeToStatusUpdates.useSubscription(undefined, {
|
||||
onData: (data) => {
|
||||
const jobByName = jobs.find((job) => job.job.name === data.name);
|
||||
if (!jobByName) {
|
||||
return;
|
||||
}
|
||||
handlers.applyWhere(
|
||||
(job) => job.job.name === data.name,
|
||||
(job) => ({ ...job, status: data }),
|
||||
);
|
||||
},
|
||||
});
|
||||
const { mutateAsync } = clientApi.cronJobs.triggerJob.useMutation();
|
||||
const JobCard = ({ job, status }: JobCardProps) => {
|
||||
const t = useI18n();
|
||||
const tTasks = useScopedI18n("management.page.tool.tasks");
|
||||
const triggerMutation = clientApi.cronJobs.triggerJob.useMutation();
|
||||
const handleJobTrigger = React.useCallback(
|
||||
async (job: JobState) => {
|
||||
if (job.status?.status === "running") {
|
||||
return;
|
||||
}
|
||||
await mutateAsync(job.job.name);
|
||||
async (name: JobName) => {
|
||||
if (status?.status === "running") return;
|
||||
await triggerMutation.mutateAsync(name);
|
||||
},
|
||||
[mutateAsync],
|
||||
[triggerMutation, status],
|
||||
);
|
||||
return (
|
||||
<Stack>
|
||||
{jobs.map((job) => (
|
||||
<Card key={job.job.name} withBorder>
|
||||
<Group justify={"space-between"} gap={"md"}>
|
||||
<Stack gap={0}>
|
||||
<Group>
|
||||
<Text>{t(`job.${job.job.name}.label` as TranslationKeys)}</Text>
|
||||
{job.status?.status === "idle" && <Badge variant="default">{t("status.idle")}</Badge>}
|
||||
{job.status?.status === "running" && <Badge color="green">{t("status.running")}</Badge>}
|
||||
{job.status?.lastExecutionStatus === "error" && <Badge color="red">{t("status.error")}</Badge>}
|
||||
</Group>
|
||||
{job.status && <TimeAgo timestamp={job.status.lastExecutionTimestamp} />}
|
||||
</Stack>
|
||||
|
||||
{!job.job.preventManualExecution && (
|
||||
<ActionIcon
|
||||
onClick={() => handleJobTrigger(job)}
|
||||
disabled={job.status?.status === "running"}
|
||||
variant={"default"}
|
||||
size={"xl"}
|
||||
radius={"xl"}
|
||||
>
|
||||
<IconPlayerPlay stroke={1.5} />
|
||||
</ActionIcon>
|
||||
const { openModal } = useModalAction(TaskConfigurationModal);
|
||||
const [isEnabled, setEnabled] = useState(job.isEnabled);
|
||||
const disableMutation = clientApi.cronJobs.disableJob.useMutation();
|
||||
const enableMutation = clientApi.cronJobs.enableJob.useMutation();
|
||||
|
||||
const [activeStatePending, startActiveTransition] = useTransition();
|
||||
const handleActiveChange = () =>
|
||||
startActiveTransition(async () => {
|
||||
if (isEnabled) {
|
||||
await disableMutation.mutateAsync(job.name, {
|
||||
onSuccess() {
|
||||
setEnabled(false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await enableMutation.mutateAsync(job.name, {
|
||||
onSuccess() {
|
||||
setEnabled(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card key={job.name} withBorder>
|
||||
<Group justify={"space-between"} gap={"md"}>
|
||||
<Stack gap={0}>
|
||||
<Group>
|
||||
<Text>{tTasks(`job.${job.name}.label`)}</Text>
|
||||
<StatusBadge isEnabled={isEnabled} status={status} />
|
||||
{status?.lastExecutionStatus === "error" && <Badge color="red">{tTasks("status.error")}</Badge>}
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
{status && (
|
||||
<>
|
||||
<TimeAgo timestamp={status.lastExecutionTimestamp} />
|
||||
<Text size="sm" c="dimmed">
|
||||
•
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{cronExpressions.find((expression) => expression.value === job.cron)?.label(t) ?? job.cron}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Group>
|
||||
{!job.preventManualExecution && (
|
||||
<ActionIcon
|
||||
onClick={() => handleJobTrigger(job.name)}
|
||||
disabled={status?.status === "running"}
|
||||
loading={triggerMutation.isPending}
|
||||
variant="default"
|
||||
size="xl"
|
||||
radius="xl"
|
||||
>
|
||||
<IconPlayerPlay color={getMantineColor("green", 6)} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon onClick={handleActiveChange} loading={activeStatePending} variant="default" size="xl" radius="xl">
|
||||
{isEnabled ? (
|
||||
<IconPower color={getMantineColor("green", 6)} stroke={1.5} />
|
||||
) : (
|
||||
<IconPowerOff color={getMantineColor("gray", 6)} stroke={1.5} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
onClick={() =>
|
||||
openModal(
|
||||
{ job },
|
||||
{
|
||||
title: tTasks("settings.title", {
|
||||
jobName: tTasks(`job.${job.name}.label`),
|
||||
}),
|
||||
},
|
||||
)
|
||||
}
|
||||
variant={"default"}
|
||||
size={"xl"}
|
||||
radius={"xl"}
|
||||
>
|
||||
<IconSettings stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface StatusBadgeProps {
|
||||
isEnabled: boolean;
|
||||
status: TaskStatus | null;
|
||||
}
|
||||
|
||||
const StatusBadge = ({ isEnabled, status }: StatusBadgeProps) => {
|
||||
const t = useScopedI18n("management.page.tool.tasks");
|
||||
if (!isEnabled) return <Badge color="yellow">{t("status.disabled")}</Badge>;
|
||||
|
||||
if (!status) return null;
|
||||
|
||||
if (status.status === "running") return <Badge color="green">{t("status.running")}</Badge>;
|
||||
return <Badge variant="default">{t("status.idle")}</Badge>;
|
||||
};
|
||||
|
||||
const TimeAgo = ({ timestamp }: { timestamp: string }) => {
|
||||
const timeAgo = useTimeAgo(new Date(timestamp));
|
||||
|
||||
@@ -93,3 +220,65 @@ const TimeAgo = ({ timestamp }: { timestamp: string }) => {
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const TaskConfigurationModal = createModal<{
|
||||
job: RouterOutputs["cronJobs"]["getJobs"][number];
|
||||
}>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
cron: innerProps.job.cron,
|
||||
},
|
||||
});
|
||||
const { mutateAsync, isPending } = clientApi.cronJobs.updateJobInterval.useMutation();
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
utils.cronJobs.getJobs.setData(undefined, (data) =>
|
||||
data?.map((job) =>
|
||||
job.name === innerProps.job.name
|
||||
? {
|
||||
...job,
|
||||
cron: values.cron,
|
||||
}
|
||||
: job,
|
||||
),
|
||||
);
|
||||
await mutateAsync(
|
||||
{
|
||||
name: innerProps.job.name,
|
||||
cron: values.cron,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
actions.closeModal();
|
||||
},
|
||||
async onSettled() {
|
||||
await utils.cronJobs.getJobs.invalidate();
|
||||
},
|
||||
},
|
||||
);
|
||||
})}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Select
|
||||
label={t("management.page.tool.tasks.field.interval.label")}
|
||||
{...form.getInputProps("cron")}
|
||||
data={cronExpressions.map(({ value, label }) => ({ value, label: label(t) }))}
|
||||
/>
|
||||
<Group justify="end">
|
||||
<Button variant="subtle" color="gray" disabled={isPending} onClick={actions.closeModal}>
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.save")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: "",
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
export const GET = async (_request: Request, props: { params: Promise<{ id: string }> }) => {
|
||||
const { id } = await props.params;
|
||||
|
||||
const imageProxy = new ImageProxy();
|
||||
const image = await imageProxy.forwardImageAsync(id);
|
||||
if (!image) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return new Response(image, {
|
||||
headers: {
|
||||
"Cache-Control": "public, max-age=3600, immutable", // Cache for 1 hour
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getBoardLayouts } from "@homarr/boards/context";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
|
||||
import type { Board, EmptySection, Item, ItemLayout } from "~/app/[locale]/boards/_types";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { Board, EmptySection, ItemLayout, Section } from "~/app/[locale]/boards/_types";
|
||||
import { getFirstEmptyPosition } from "./empty-position";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { Board, DynamicSection, EmptySection, Item, Section } from "~/app/[locale]/boards/_types";
|
||||
import { DynamicSectionMockBuilder } from "./dynamic-section-mock";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { CategorySection } from "~/app/[locale]/boards/_types";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { DynamicSection } from "~/app/[locale]/boards/_types";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { EmptySection } from "~/app/[locale]/boards/_types";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { Item } from "~/app/[locale]/boards/_types";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { CategorySection, EmptySection, Section } from "~/app/[locale]/boards/_types";
|
||||
import type { MoveCategoryInput } from "./actions/move-category";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback } from "react";
|
||||
|
||||
import { fetchApi } from "@homarr/api/client";
|
||||
import { getCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { useSettings } from "@homarr/settings";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getBoardLayouts } from "@homarr/boards/context";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
import type { Board, DynamicSection, DynamicSectionLayout, EmptySection } from "~/app/[locale]/boards/_types";
|
||||
import { getFirstEmptyPosition } from "~/components/board/items/actions/empty-position";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createBooleanSchema, createEnv } from "@homarr/core/infrastructure/env";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION: createBooleanSchema(false),
|
||||
},
|
||||
experimental__runtimeEnv: process.env,
|
||||
});
|
||||
+10
-10
@@ -10,7 +10,7 @@
|
||||
"main": "./src/main.ts",
|
||||
"types": "./src/main.ts",
|
||||
"scripts": {
|
||||
"build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:*.node --external:@opentelemetry/api --external:deasync --outfile=tasks.cjs",
|
||||
"build": "esbuild src/main.ts --bundle --platform=node --loader:.scss=text --external:*.node --external:@opentelemetry/api --external:deasync --external:bcrypt --outfile=tasks.cjs",
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"dev": "pnpm with-env tsx ./src/main.ts",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-runner": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
@@ -30,26 +30,26 @@
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "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": "^16.5.0",
|
||||
"dotenv": "^17.2.0",
|
||||
"fastify": "^5.4.0",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.10.0"
|
||||
"undici": "7.12.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.15.30",
|
||||
"@types/node": "^22.16.4",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { schedule, validate as validateCron } from "node-cron";
|
||||
|
||||
import type { IJobManager } from "@homarr/cron-job-api";
|
||||
import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs";
|
||||
import type { Database, InferInsertModel } from "@homarr/db";
|
||||
import { eq } from "@homarr/db";
|
||||
import { cronJobConfigurations } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
export class JobManager implements IJobManager {
|
||||
constructor(
|
||||
private db: Database,
|
||||
private jobGroup: typeof cronJobGroup,
|
||||
) {}
|
||||
|
||||
public async startAsync(name: JobGroupKeys): Promise<void> {
|
||||
await this.jobGroup.startAsync(name);
|
||||
}
|
||||
public async triggerAsync(name: JobGroupKeys): Promise<void> {
|
||||
await this.jobGroup.runManuallyAsync(name);
|
||||
}
|
||||
public async stopAsync(name: JobGroupKeys): Promise<void> {
|
||||
await this.jobGroup.stopAsync(name);
|
||||
}
|
||||
public async updateIntervalAsync(name: JobGroupKeys, cron: string): Promise<void> {
|
||||
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}`);
|
||||
}
|
||||
await this.updateConfigurationAsync(name, { cronExpression: cron });
|
||||
await this.jobGroup.getTask(name)?.destroy();
|
||||
|
||||
this.jobGroup.setTask(
|
||||
name,
|
||||
schedule(cron, () => void job.executeAsync(), {
|
||||
name,
|
||||
}),
|
||||
);
|
||||
logger.info(`Cron job interval updated name="${name}" expression="${cron}"`);
|
||||
}
|
||||
public async disableAsync(name: JobGroupKeys): Promise<void> {
|
||||
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);
|
||||
logger.info(`Cron job disabled name="${name}"`);
|
||||
}
|
||||
public async enableAsync(name: JobGroupKeys): Promise<void> {
|
||||
logger.info(`Enabling cron job name="${name}"`);
|
||||
await this.updateConfigurationAsync(name, { isEnabled: true });
|
||||
await this.jobGroup.startAsync(name);
|
||||
logger.info(`Cron job enabled name="${name}"`);
|
||||
}
|
||||
|
||||
private async updateConfigurationAsync(
|
||||
name: JobGroupKeys,
|
||||
configuration: Omit<Partial<InferInsertModel<typeof cronJobConfigurations>>, "name">,
|
||||
) {
|
||||
const existingConfig = await this.db.query.cronJobConfigurations.findFirst({
|
||||
where: (table, { eq }) => eq(table.name, name),
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Updating cron job configuration name="${name}" configuration="${JSON.stringify(configuration)}" exists="${Boolean(existingConfig)}"`,
|
||||
);
|
||||
|
||||
if (existingConfig) {
|
||||
await this.db
|
||||
.update(cronJobConfigurations)
|
||||
// prevent updating the name, as it is the primary key
|
||||
.set({ ...configuration, name: undefined })
|
||||
.where(eq(cronJobConfigurations.name, name));
|
||||
logger.debug(`Cron job configuration updated name="${name}" configuration="${JSON.stringify(configuration)}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const job = this.jobGroup.getJobRegistry().get(name);
|
||||
if (!job) throw new Error(`Job ${name} not found`);
|
||||
|
||||
await this.db.insert(cronJobConfigurations).values({
|
||||
name,
|
||||
cronExpression: configuration.cronExpression ?? job.cronExpression,
|
||||
isEnabled: configuration.isEnabled ?? true,
|
||||
});
|
||||
logger.debug(`Cron job configuration updated name="${name}" configuration="${JSON.stringify(configuration)}"`);
|
||||
}
|
||||
|
||||
public async getAllAsync(): Promise<
|
||||
{ name: JobGroupKeys; cron: string; preventManualExecution: boolean; isEnabled: boolean }[]
|
||||
> {
|
||||
const configurations = await this.db.query.cronJobConfigurations.findMany();
|
||||
|
||||
return [...this.jobGroup.getJobRegistry().entries()].map(([name, job]) => {
|
||||
const config = configurations.find((config) => config.name === name);
|
||||
return {
|
||||
name,
|
||||
cron: config?.cronExpression ?? job.cronExpression,
|
||||
preventManualExecution: job.preventManualExecution,
|
||||
isEnabled: config?.isEnabled ?? true,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
+37
-2
@@ -1,10 +1,45 @@
|
||||
// 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 { registerCronJobRunner } from "@homarr/cron-job-runner/register";
|
||||
import type { FastifyTRPCPluginOptions } from "@trpc/server/adapters/fastify";
|
||||
import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
|
||||
import fastify from "fastify";
|
||||
|
||||
import type { JobRouter } from "@homarr/cron-job-api";
|
||||
import { jobRouter } from "@homarr/cron-job-api";
|
||||
import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "@homarr/cron-job-api/constants";
|
||||
import { jobGroup } from "@homarr/cron-jobs";
|
||||
import { db } from "@homarr/db";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { JobManager } from "./job-manager";
|
||||
|
||||
const server = fastify({
|
||||
maxParamLength: 5000,
|
||||
});
|
||||
server.register(fastifyTRPCPlugin, {
|
||||
prefix: CRON_JOB_API_PATH,
|
||||
trpcOptions: {
|
||||
router: jobRouter,
|
||||
createContext: ({ req }) => ({
|
||||
manager: new JobManager(db, jobGroup),
|
||||
apiKey: req.headers[CRON_JOB_API_KEY_HEADER] as string | undefined,
|
||||
}),
|
||||
onError({ path, error }) {
|
||||
logger.error(new Error(`Error in tasks tRPC handler path="${path}"`, { cause: error }));
|
||||
},
|
||||
} satisfies FastifyTRPCPluginOptions<JobRouter>["trpcOptions"],
|
||||
});
|
||||
|
||||
void (async () => {
|
||||
registerCronJobRunner();
|
||||
await jobGroup.initializeAsync();
|
||||
await jobGroup.startAllAsync();
|
||||
|
||||
try {
|
||||
await server.listen({ port: CRON_JOB_API_PORT });
|
||||
logger.info(`Tasks web server started successfully port="${CRON_JOB_API_PORT}"`);
|
||||
} catch (err) {
|
||||
logger.error(new Error(`Failed to start tasks web server port="${CRON_JOB_API_PORT}"`, { cause: err }));
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -25,18 +25,18 @@
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"tsx": "4.19.4",
|
||||
"ws": "^8.18.2"
|
||||
"dotenv": "^17.2.0",
|
||||
"tsx": "4.20.3",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { createHomarrContainer } from "./shared/create-homarr-container";
|
||||
import { createRedisContainer } from "./shared/redis-container";
|
||||
|
||||
describe("Health checks", () => {
|
||||
test("ready and live should return 200 OK", async () => {
|
||||
test("ready and live should return 200 OK with normal image and no extra configuration", async () => {
|
||||
// Arrange
|
||||
const homarrContainer = await createHomarrContainer().start();
|
||||
|
||||
@@ -15,4 +16,31 @@ describe("Health checks", () => {
|
||||
expect(readyResponse.status).toBe(200);
|
||||
expect(liveResponse.status).toBe(200);
|
||||
}, 20_000);
|
||||
|
||||
test("ready and live should return 200 OK with external redis", async () => {
|
||||
// Arrange
|
||||
const redisContainer = await createRedisContainer().start();
|
||||
const homarrContainer = await createHomarrContainer({
|
||||
environment: {
|
||||
REDIS_IS_EXTERNAL: "true",
|
||||
REDIS_HOST: "host.docker.internal",
|
||||
REDIS_PORT: redisContainer.getMappedPort(6379).toString(),
|
||||
REDIS_PASSWORD: redisContainer.getPassword(),
|
||||
},
|
||||
}).start();
|
||||
|
||||
// Act
|
||||
const readyResponse = await fetch(`http://localhost:${homarrContainer.getMappedPort(7575)}/api/health/ready`);
|
||||
const liveResponse = await fetch(`http://localhost:${homarrContainer.getMappedPort(7575)}/api/health/live`);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
readyResponse.status,
|
||||
`Expected ready to return OK statusCode=${readyResponse.status} content=${await readyResponse.text()}`,
|
||||
).toBe(200);
|
||||
expect(
|
||||
liveResponse.status,
|
||||
`Expected live to return OK statusCode=${liveResponse.status} content=${await liveResponse.text()}`,
|
||||
).toBe(200);
|
||||
}, 20_000);
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { RedisContainer } from "@testcontainers/redis";
|
||||
|
||||
export const createRedisContainer = () => {
|
||||
return new RedisContainer("redis:latest").withPassword("homarr");
|
||||
};
|
||||
+16
-15
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.23.0",
|
||||
"version": "1.30.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -36,26 +36,27 @@
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^11.0.3",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/npm": "^12.0.2",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.5.4",
|
||||
"@vitejs/plugin-react": "^4.5.1",
|
||||
"@vitest/coverage-v8": "^3.2.2",
|
||||
"@vitest/ui": "^3.2.2",
|
||||
"conventional-changelog-conventionalcommits": "^9.0.0",
|
||||
"@testcontainers/redis": "^11.3.1",
|
||||
"@turbo/gen": "^2.5.5",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"semantic-release": "^24.2.5",
|
||||
"testcontainers": "^11.0.1",
|
||||
"turbo": "^2.5.4",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^24.2.7",
|
||||
"testcontainers": "^11.3.1",
|
||||
"turbo": "^2.5.5",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.2"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.1",
|
||||
"packageManager": "pnpm@10.13.1",
|
||||
"engines": {
|
||||
"node": ">=22.16.0"
|
||||
"node": ">=22.17.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -70,7 +71,7 @@
|
||||
"tree-sitter-json"
|
||||
],
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.10.0"
|
||||
"proxmox-api>undici": "7.12.0"
|
||||
},
|
||||
"allowUnusedPatches": true,
|
||||
"ignoredBuiltDependencies": [
|
||||
|
||||
@@ -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.28.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
+11
-12
@@ -24,7 +24,7 @@
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-runner": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
@@ -35,31 +35,30 @@
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/old-import": "workspace:^0.1.0",
|
||||
"@homarr/old-schema": "workspace:^0.1.0",
|
||||
"@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",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@trpc/tanstack-react-query": "^11.3.1",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@trpc/tanstack-react-query": "^11.4.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.3.3",
|
||||
"next": "15.4.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.3.1",
|
||||
"zod": "^3.25.56"
|
||||
"trpc-to-openapi": "^2.3.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"eslint": "^9.31.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import { generateSecureRandomToken } from "@homarr/common/server";
|
||||
import { createId, db, eq } from "@homarr/db";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { apiKeys } from "@homarr/db/schema";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { asc, createId, eq, inArray, like } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq, inArray, like } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { selectAppSchema } from "@homarr/db/validationSchemas";
|
||||
import { getIconForName } from "@homarr/icons";
|
||||
|
||||
@@ -3,9 +3,10 @@ import superjson from "superjson";
|
||||
import { z } from "zod";
|
||||
|
||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { DeviceType } from "@homarr/common/server";
|
||||
import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db";
|
||||
import { and, asc, createId, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db";
|
||||
import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db";
|
||||
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import z from "zod/v4";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import { cronJobNames, cronJobs, jobNameSchema, triggerCronJobAsync } from "@homarr/cron-job-runner";
|
||||
import { cronExpressionSchema, jobGroupKeys, jobNameSchema } from "@homarr/cron-job-api";
|
||||
import { cronJobApi } from "@homarr/cron-job-api/client";
|
||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||
import { createCronJobStatusChannel } from "@homarr/cron-job-status";
|
||||
import { logger } from "@homarr/log";
|
||||
@@ -13,19 +14,51 @@ export const cronJobsRouter = createTRPCRouter({
|
||||
.requiresPermission("admin")
|
||||
.input(jobNameSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
await triggerCronJobAsync(input);
|
||||
await cronJobApi.trigger.mutate(input);
|
||||
}),
|
||||
getJobs: permissionRequiredProcedure.requiresPermission("admin").query(() => {
|
||||
return objectEntries(cronJobs).map(([name, options]) => ({
|
||||
name,
|
||||
preventManualExecution: options.preventManualExecution,
|
||||
}));
|
||||
startJob: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(jobNameSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
await cronJobApi.start.mutate(input);
|
||||
}),
|
||||
stopJob: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(jobNameSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
await cronJobApi.stop.mutate(input);
|
||||
}),
|
||||
updateJobInterval: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(
|
||||
z.object({
|
||||
name: jobNameSchema,
|
||||
cron: cronExpressionSchema,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
await cronJobApi.updateInterval.mutate(input);
|
||||
}),
|
||||
disableJob: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(jobNameSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
await cronJobApi.disable.mutate(input);
|
||||
}),
|
||||
enableJob: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(jobNameSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
await cronJobApi.enable.mutate(input);
|
||||
}),
|
||||
getJobs: permissionRequiredProcedure.requiresPermission("admin").query(async () => {
|
||||
return await cronJobApi.getAll.query();
|
||||
}),
|
||||
subscribeToStatusUpdates: permissionRequiredProcedure.requiresPermission("admin").subscription(() => {
|
||||
return observable<TaskStatus>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
|
||||
for (const name of cronJobNames) {
|
||||
for (const name of jobGroupKeys) {
|
||||
const channel = createCronJobStatusChannel(name);
|
||||
const unsubscribe = channel.subscribe((data) => {
|
||||
emit.next(data);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { and, createId, eq, handleTransactionsAsync, like, not } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db";
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import { createId, objectEntries } from "@homarr/common";
|
||||
import { decryptSecret, encryptSecret } from "@homarr/common/server";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { and, asc, createId, eq, handleTransactionsAsync, inArray, like } from "@homarr/db";
|
||||
import { and, asc, eq, handleTransactionsAsync, inArray, like } from "@homarr/db";
|
||||
import {
|
||||
groupMembers,
|
||||
groupPermissions,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getIconUrl,
|
||||
getIntegrationKindsByCategory,
|
||||
getPermissionsWithParents,
|
||||
integrationCategories,
|
||||
integrationDefs,
|
||||
integrationKinds,
|
||||
integrationSecretKindObject,
|
||||
@@ -129,6 +130,57 @@ export const integrationRouter = createTRPCRouter({
|
||||
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
|
||||
);
|
||||
}),
|
||||
allOfGivenCategory: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
category: z.enum(integrationCategories),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({
|
||||
where: eq(groupMembers.userId, ctx.session?.user.id ?? ""),
|
||||
});
|
||||
|
||||
const intergrationKinds = getIntegrationKindsByCategory(input.category);
|
||||
|
||||
const integrationsFromDb = await ctx.db.query.integrations.findMany({
|
||||
with: {
|
||||
userPermissions: {
|
||||
where: eq(integrationUserPermissions.userId, ctx.session?.user.id ?? ""),
|
||||
},
|
||||
groupPermissions: {
|
||||
where: inArray(
|
||||
integrationGroupPermissions.groupId,
|
||||
groupsOfCurrentUser.map((group) => group.groupId),
|
||||
),
|
||||
},
|
||||
},
|
||||
where: inArray(integrations.kind, intergrationKinds),
|
||||
});
|
||||
return integrationsFromDb
|
||||
.map((integration) => {
|
||||
const permissions = integration.userPermissions
|
||||
.map(({ permission }) => permission)
|
||||
.concat(integration.groupPermissions.map(({ permission }) => permission));
|
||||
|
||||
return {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
url: integration.url,
|
||||
permissions: {
|
||||
hasUseAccess:
|
||||
permissions.includes("use") || permissions.includes("interact") || permissions.includes("full"),
|
||||
hasInteractAccess: permissions.includes("interact") || permissions.includes("full"),
|
||||
hasFullAccess: permissions.includes("full"),
|
||||
},
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(integrationA, integrationB) =>
|
||||
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
|
||||
);
|
||||
}),
|
||||
search: protectedProcedure
|
||||
.input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
|
||||
@@ -36,6 +36,7 @@ const mapError = (error: Error): MappedError => {
|
||||
export interface MappedCertificate {
|
||||
isSelfSigned: boolean;
|
||||
issuer: string;
|
||||
issuerCertificate?: MappedCertificate;
|
||||
subject: string;
|
||||
serialNumber: string;
|
||||
validFrom: Date;
|
||||
@@ -47,6 +48,7 @@ export interface MappedCertificate {
|
||||
const mapCertificate = (certificate: X509Certificate, code: RequestErrorCode): MappedCertificate => ({
|
||||
isSelfSigned: certificate.ca || code === "DEPTH_ZERO_SELF_SIGNED_CERT",
|
||||
issuer: certificate.issuer,
|
||||
issuerCertificate: certificate.issuerCertificate ? mapCertificate(certificate.issuerCertificate, code) : undefined,
|
||||
subject: certificate.subject,
|
||||
serialNumber: certificate.serialNumber,
|
||||
validFrom: certificate.validFromDate,
|
||||
|
||||
@@ -2,7 +2,8 @@ import { randomBytes } from "crypto";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { asc, createId, eq } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq } from "@homarr/db";
|
||||
import { invites } from "@homarr/db/schema";
|
||||
import { selectInviteSchema } from "@homarr/db/validationSchemas";
|
||||
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import z from "zod";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { logLevels } from "@homarr/log/constants";
|
||||
import type { LoggerMessage } from "@homarr/redis";
|
||||
import { loggingChannel } from "@homarr/redis";
|
||||
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
|
||||
|
||||
export const logRouter = createTRPCRouter({
|
||||
subscribe: permissionRequiredProcedure.requiresPermission("other-view-logs").subscription(() => {
|
||||
return observable<LoggerMessage>((emit) => {
|
||||
const unsubscribe = loggingChannel.subscribe((data) => {
|
||||
emit.next(data);
|
||||
});
|
||||
logger.info("A tRPC client has connected to the logging procedure");
|
||||
subscribe: permissionRequiredProcedure
|
||||
.requiresPermission("other-view-logs")
|
||||
.input(
|
||||
z.object({
|
||||
levels: z.array(zodEnumFromArray(logLevels)).default(["info"]),
|
||||
}),
|
||||
)
|
||||
.subscription(({ input }) => {
|
||||
return observable<LoggerMessage>((emit) => {
|
||||
const unsubscribe = loggingChannel.subscribe((data) => {
|
||||
if (!input.levels.includes(data.level)) return;
|
||||
emit.next(data);
|
||||
});
|
||||
logger.info("A tRPC client has connected to the logging procedure");
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
}),
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { and, createId, desc, eq, like } from "@homarr/db";
|
||||
import { and, desc, eq, like } from "@homarr/db";
|
||||
import { iconRepositories, icons, medias } from "@homarr/db/schema";
|
||||
import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local";
|
||||
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { asc, createId, eq, like } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq, like } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { searchEngines, users } from "@homarr/db/schema";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { GroupPermissionKey } from "@homarr/definitions";
|
||||
|
||||
@@ -2,8 +2,9 @@ import SuperJSON from "superjson";
|
||||
import { describe, expect, it, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database, InferInsertModel } from "@homarr/db";
|
||||
import { and, createId, eq, not } from "@homarr/db";
|
||||
import { and, eq, not } from "@homarr/db";
|
||||
import {
|
||||
boardGroupPermissions,
|
||||
boards,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import * as authShared from "@homarr/auth/shared";
|
||||
import { createId, eq } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { eq } from "@homarr/db";
|
||||
import { boards, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import * as env from "@homarr/auth/env";
|
||||
import { createId, eq } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { eq } from "@homarr/db";
|
||||
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { GroupPermissionKey } from "@homarr/definitions";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import * as authShared from "@homarr/auth/shared";
|
||||
import { createId, eq } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { eq } from "@homarr/db";
|
||||
import { integrations, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import { encryptSecret } from "@homarr/common/server";
|
||||
import { createId } from "@homarr/db";
|
||||
import { integrations, integrationSecrets } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { GroupPermissionKey } from "@homarr/definitions";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { invites, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import SuperJSON from "superjson";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { serverSettings } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { describe, expect, it, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { createId, eq } from "@homarr/db";
|
||||
import { eq } from "@homarr/db";
|
||||
import { invites, onboarding, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { GroupPermissionKey, OnboardingStep } from "@homarr/definitions";
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import * as ping from "@homarr/ping";
|
||||
|
||||
import { appRouter } from "../../widgets/app";
|
||||
|
||||
// Mock the auth module to return an empty session
|
||||
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
||||
vi.mock("@homarr/ping", () => ({ sendPingRequestAsync: async () => await Promise.resolve(null) }));
|
||||
|
||||
describe("ping should call sendPingRequestAsync with url and return result", () => {
|
||||
test("ping with error response should return error and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ error: "error" }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("error" in result).toBe(true);
|
||||
});
|
||||
|
||||
test("ping with success response should return statusCode and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ statusCode: 200 }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("statusCode" in result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,9 @@ import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { and, createId, eq, like } from "@homarr/db";
|
||||
import { and, eq, like } from "@homarr/db";
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema";
|
||||
import { selectUserSchema } from "@homarr/db/validationSchemas";
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
import { pingUrlChannel } from "@homarr/redis";
|
||||
import { pingRequestHandler } from "@homarr/request-handler/ping";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
ping: publicProcedure.input(z.object({ url: z.string() })).query(async ({ input }) => {
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
...pingResult,
|
||||
};
|
||||
}),
|
||||
updatedPing: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
@@ -23,21 +15,27 @@ export const appRouter = createTRPCRouter({
|
||||
)
|
||||
.subscription(async ({ input }) => {
|
||||
await pingUrlChannel.addAsync(input.url);
|
||||
const innerHandler = pingRequestHandler.handler({ url: input.url });
|
||||
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
return observable<{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }>(
|
||||
(emit) => {
|
||||
// Run ping request in background
|
||||
void innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then(({ data }) => {
|
||||
emit.next({ url: input.url, ...data });
|
||||
});
|
||||
|
||||
return observable<{ url: string; statusCode: number } | { url: string; error: string }>((emit) => {
|
||||
emit.next({ url: input.url, ...pingResult });
|
||||
const unsubscribe = pingChannel.subscribe((message) => {
|
||||
// Only emit if same url
|
||||
if (message.url !== input.url) return;
|
||||
emit.next(message);
|
||||
});
|
||||
const unsubscribe = innerHandler.subscribe((pingResponse) => {
|
||||
emit.next({
|
||||
url: input.url,
|
||||
...pingResponse,
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
void pingUrlChannel.removeAsync(input.url);
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
unsubscribe();
|
||||
void pingUrlChannel.removeAsync(input.url);
|
||||
};
|
||||
},
|
||||
);
|
||||
}),
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import type { HealthMonitoring } from "@homarr/integrations";
|
||||
import type { SystemHealthMonitoring } from "@homarr/integrations";
|
||||
import type { ProxmoxClusterInfo } from "@homarr/integrations/types";
|
||||
import { clusterInfoRequestHandler, systemInfoRequestHandler } from "@homarr/request-handler/health-monitoring";
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const healthMonitoringRouter = createTRPCRouter({
|
||||
getSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "mock"))
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -26,9 +26,9 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "mock"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{ integrationId: string; healthInfo: HealthMonitoring; timestamp: Date }>((emit) => {
|
||||
return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integration of ctx.integrations) {
|
||||
const innerHandler = systemInfoRequestHandler.handler(integration, {});
|
||||
@@ -49,14 +49,14 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
getClusterHealthStatus: publicProcedure
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox", "mock"))
|
||||
.query(async ({ ctx }) => {
|
||||
const innerHandler = clusterInfoRequestHandler.handler(ctx.integration, {});
|
||||
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return data;
|
||||
}),
|
||||
subscribeClusterHealthStatus: publicProcedure
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox", "mock"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<ProxmoxClusterInfo>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
|
||||
@@ -5,12 +5,14 @@ import { dnsHoleRouter } from "./dns-hole";
|
||||
import { downloadsRouter } from "./downloads";
|
||||
import { healthMonitoringRouter } from "./health-monitoring";
|
||||
import { indexerManagerRouter } from "./indexer-manager";
|
||||
import { mediaReleaseRouter } from "./media-release";
|
||||
import { mediaRequestsRouter } from "./media-requests";
|
||||
import { mediaServerRouter } from "./media-server";
|
||||
import { mediaTranscodingRouter } from "./media-transcoding";
|
||||
import { minecraftRouter } from "./minecraft";
|
||||
import { networkControllerRouter } from "./network-controller";
|
||||
import { notebookRouter } from "./notebook";
|
||||
import { notificationsRouter } from "./notifications";
|
||||
import { optionsRouter } from "./options";
|
||||
import { releasesRouter } from "./releases";
|
||||
import { rssFeedRouter } from "./rssFeed";
|
||||
@@ -26,6 +28,7 @@ export const widgetRouter = createTRPCRouter({
|
||||
smartHome: smartHomeRouter,
|
||||
stockPrice: stockPriceRouter,
|
||||
mediaServer: mediaServerRouter,
|
||||
mediaRelease: mediaReleaseRouter,
|
||||
calendar: calendarRouter,
|
||||
downloads: downloadsRouter,
|
||||
mediaRequests: mediaRequestsRouter,
|
||||
@@ -37,4 +40,5 @@ export const widgetRouter = createTRPCRouter({
|
||||
options: optionsRouter,
|
||||
releases: releasesRouter,
|
||||
networkController: networkControllerRouter,
|
||||
notifications: notificationsRouter,
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
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 { MediaRelease } from "@homarr/integrations/types";
|
||||
import { mediaReleaseRequestHandler } from "@homarr/request-handler/media-release";
|
||||
|
||||
import { createManyIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const mediaReleaseRouter = createTRPCRouter({
|
||||
getMediaReleases: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRelease")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = mediaReleaseRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
releases: data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return results.flatMap((result) =>
|
||||
result.releases.map((release) => ({
|
||||
...release,
|
||||
integration: result.integration,
|
||||
})),
|
||||
);
|
||||
}),
|
||||
|
||||
subscribeToReleases: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRelease")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"mediaRelease"> }>;
|
||||
releases: MediaRelease[];
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = mediaReleaseRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((releases) => {
|
||||
emit.next({
|
||||
integration,
|
||||
releases,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
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 { Notification } from "@homarr/integrations";
|
||||
import { notificationsRequestHandler } from "@homarr/request-handler/notifications";
|
||||
|
||||
import type { IntegrationAction } from "../../middlewares/integration";
|
||||
import { createManyIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const createNotificationsIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
createManyIntegrationMiddleware(action, ...getIntegrationKindsByCategory("notifications"));
|
||||
|
||||
export const notificationsRouter = createTRPCRouter({
|
||||
getNotifications: publicProcedure
|
||||
.unstable_concat(createNotificationsIntegrationMiddleware("query"))
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const innerHandler = notificationsRequestHandler.handler(integration, {});
|
||||
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
|
||||
return {
|
||||
integration: {
|
||||
id: integration.id,
|
||||
name: integration.name,
|
||||
kind: integration.kind,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}),
|
||||
subscribeNotifications: publicProcedure
|
||||
.unstable_concat(createNotificationsIntegrationMiddleware("query"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"notifications"> }>;
|
||||
data: Notification[];
|
||||
}>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
for (const integrationWithSecrets of ctx.integrations) {
|
||||
const { decryptedSecrets: _, ...integration } = integrationWithSecrets;
|
||||
const innerHandler = notificationsRequestHandler.handler(integrationWithSecrets, {});
|
||||
const unsubscribe = innerHandler.subscribe((data) => {
|
||||
emit.next({
|
||||
integration,
|
||||
data,
|
||||
});
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
import { escapeForRegEx } from "@tiptap/react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import { releasesRequestHandler } from "@homarr/request-handler/releases";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const formatVersionFilterRegex = (versionFilter: z.infer<typeof releaseVersionFilterSchema> | undefined) => {
|
||||
@@ -23,31 +25,31 @@ const releaseVersionFilterSchema = z.object({
|
||||
|
||||
export const releasesRouter = createTRPCRouter({
|
||||
getLatest: publicProcedure
|
||||
.concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("releasesProvider")))
|
||||
.input(
|
||||
z.object({
|
||||
repositories: z.array(
|
||||
z.object({
|
||||
providerKey: z.string(),
|
||||
id: z.string(),
|
||||
identifier: z.string(),
|
||||
versionFilter: releaseVersionFilterSchema.optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const result = await Promise.all(
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await Promise.all(
|
||||
input.repositories.map(async (repository) => {
|
||||
const innerHandler = releasesRequestHandler.handler({
|
||||
providerKey: repository.providerKey,
|
||||
const innerHandler = releasesRequestHandler.handler(ctx.integration, {
|
||||
id: repository.id,
|
||||
identifier: repository.identifier,
|
||||
versionRegex: formatVersionFilterRegex(repository.versionFilter),
|
||||
});
|
||||
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
@@ -1,8 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createBooleanSchema, createDurationSchema, createEnv } from "@homarr/core/infrastructure/env";
|
||||
import { supportedAuthProviders } from "@homarr/definitions";
|
||||
import { createEnv } from "@homarr/env";
|
||||
import { createBooleanSchema, createDurationSchema } from "@homarr/env/schemas";
|
||||
|
||||
const authProvidersSchema = z
|
||||
.string()
|
||||
|
||||
@@ -23,23 +23,23 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.39.1",
|
||||
"@auth/drizzle-adapter": "^1.9.1",
|
||||
"@auth/core": "^0.40.0",
|
||||
"@auth/drizzle-adapter": "^1.10.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.1",
|
||||
"next": "15.3.3",
|
||||
"next-auth": "5.0.0-beta.28",
|
||||
"ldapts": "8.0.6",
|
||||
"next": "15.4.2",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.56"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -47,8 +47,8 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookies": "0.9.1",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"eslint": "^9.31.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
|
||||
interface IntegrationContextProps {
|
||||
integrations: {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
kind: string;
|
||||
kind: IntegrationKind;
|
||||
permissions: {
|
||||
hasFullAccess: boolean;
|
||||
hasInteractAccess: boolean;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import { boardGroupPermissions, boards, boardUserPermissions, groupMembers, groups, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CredentialsSignin } from "@auth/core/errors";
|
||||
import { z } from "zod";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { createId, extractErrorMessage } from "@homarr/common";
|
||||
import type { Database, InferInsertModel } from "@homarr/db";
|
||||
import { and, createId, eq } from "@homarr/db";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { users } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { userSignInSchema } from "@homarr/validation/user";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { createId } from "@homarr/db";
|
||||
import { createId } from "@homarr/common";
|
||||
import { users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CredentialsSignin } from "@auth/core/errors";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { and, createId, eq } from "@homarr/db";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { groups, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
|
||||
@@ -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.28.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,13 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"undici": "7.10.0"
|
||||
"undici": "7.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,8 @@ import { db } from "@homarr/db";
|
||||
import type { trustedCertificateHostnames } from "@homarr/db/schema";
|
||||
|
||||
const getCertificateFolder = () => {
|
||||
return env.NODE_ENV === "production"
|
||||
? path.join("/appdata", "trusted-certificates")
|
||||
: process.env.LOCAL_CERTIFICATE_PATH;
|
||||
if (env.NODE_ENV !== "production") return process.env.LOCAL_CERTIFICATE_PATH;
|
||||
return process.env.LOCAL_CERTIFICATE_PATH ?? path.join("/appdata", "trusted-certificates");
|
||||
};
|
||||
|
||||
export const loadCustomRootCertificatesAsync = async () => {
|
||||
|
||||
@@ -28,14 +28,14 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0"
|
||||
"dotenv": "^17.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.28.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { command, string } from "@drizzle-team/brocli";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import { generateSecureRandomToken } from "@homarr/common/server";
|
||||
import { and, count, createId, db, eq } from "@homarr/db";
|
||||
import { and, count, db, eq } from "@homarr/db";
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
||||
import { usernameSchema } from "@homarr/validation/user";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createEnv } from "@homarr/env";
|
||||
import { createEnv } from "@homarr/core/infrastructure/env";
|
||||
|
||||
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
|
||||
|
||||
|
||||
@@ -27,21 +27,22 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.3",
|
||||
"next": "15.4.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"undici": "7.10.0",
|
||||
"zod": "^3.25.56",
|
||||
"zod-validation-error": "^3.4.1"
|
||||
"undici": "7.12.0",
|
||||
"zod": "^3.25.76",
|
||||
"zod-validation-error": "^3.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CookieSerializeOptions } from "cookie";
|
||||
import type { SerializeOptions } from "cookie";
|
||||
import { parse, serialize } from "cookie";
|
||||
|
||||
export function parseCookies(cookieString: string) {
|
||||
return parse(cookieString);
|
||||
}
|
||||
|
||||
export function setClientCookie(name: string, value: string, options: CookieSerializeOptions = {}) {
|
||||
export function setClientCookie(name: string, value: string, options: SerializeOptions = {}) {
|
||||
document.cookie = serialize(name, value, options);
|
||||
}
|
||||
@@ -36,7 +36,12 @@ export const requestErrorMap = {
|
||||
expired: ["CERT_HAS_EXPIRED"],
|
||||
hostnameMismatch: ["ERR_TLS_CERT_ALTNAME_INVALID", "CERT_COMMON_NAME_INVALID"],
|
||||
notYetValid: ["CERT_NOT_YET_VALID"],
|
||||
untrusted: ["DEPTH_ZERO_SELF_SIGNED_CERT", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"],
|
||||
untrusted: [
|
||||
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
||||
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
||||
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
||||
"SELF_SIGNED_CERT_IN_CHAIN",
|
||||
],
|
||||
},
|
||||
connection: {
|
||||
hostUnreachable: "EHOSTUNREACH",
|
||||
|
||||
@@ -10,11 +10,11 @@ const calculateTimeAgo = (timestamp: Date) => {
|
||||
return dayjs().to(timestamp);
|
||||
};
|
||||
|
||||
export const useTimeAgo = (timestamp: Date) => {
|
||||
export const useTimeAgo = (timestamp: Date, updateFrequency = 1000) => {
|
||||
const [timeAgo, setTimeAgo] = useState(calculateTimeAgo(timestamp));
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), 1000); // update every second
|
||||
const intervalId = setInterval(() => setTimeAgo(calculateTimeAgo(timestamp)), updateFrequency);
|
||||
|
||||
return () => clearInterval(intervalId); // clear interval on hook unmount
|
||||
}, [timestamp]);
|
||||
|
||||
File renamed without changes.
@@ -5,9 +5,11 @@ export * from "./array";
|
||||
export * from "./date";
|
||||
export * from "./stopwatch";
|
||||
export * from "./hooks";
|
||||
export * from "./id";
|
||||
export * from "./url";
|
||||
export * from "./number";
|
||||
export * from "./error";
|
||||
export * from "./fetch-with-timeout";
|
||||
export * from "./theme";
|
||||
export * from "./function";
|
||||
export * from "./id";
|
||||
File renamed without changes.
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "@homarr/env",
|
||||
"name": "@homarr/core",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./schemas": "./src/schemas.ts"
|
||||
"./infrastructure/redis": "./src/infrastructure/redis/client.ts",
|
||||
"./infrastructure/env": "./src/infrastructure/env/index.ts",
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
@@ -23,14 +24,15 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.6",
|
||||
"zod": "^3.25.56"
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"ioredis": "5.6.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint": "^9.31.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,6 @@ export const defaultEnvOptions = {
|
||||
} satisfies Partial<Parameters<typeof createEnvT3>[0]>;
|
||||
|
||||
export const createEnv: typeof createEnvT3 = (options) => createEnvT3({ ...defaultEnvOptions, ...options });
|
||||
|
||||
export * from "./prefix";
|
||||
export * from "./schemas";
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export const runtimeEnvWithPrefix = (prefix: `${string}_`) =>
|
||||
Object.entries(process.env)
|
||||
.filter(([key]) => key.startsWith(prefix))
|
||||
.reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (value === undefined) return acc;
|
||||
|
||||
const newKey = key.replace(prefix, "");
|
||||
acc[newKey] = value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
File renamed without changes.
@@ -0,0 +1,26 @@
|
||||
import type { RedisOptions } from "ioredis";
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
import { redisEnv } from "./env";
|
||||
|
||||
const defaultRedisOptions = {
|
||||
connectionName: "homarr",
|
||||
} satisfies RedisOptions;
|
||||
|
||||
export type { Redis as RedisClient } from "ioredis";
|
||||
|
||||
export const createRedisClient = () =>
|
||||
redisEnv.IS_EXTERNAL
|
||||
? new Redis({
|
||||
...defaultRedisOptions,
|
||||
host: redisEnv.HOST,
|
||||
port: redisEnv.PORT,
|
||||
tls: redisEnv.TLS_CA
|
||||
? {
|
||||
ca: redisEnv.TLS_CA,
|
||||
}
|
||||
: undefined,
|
||||
username: redisEnv.USERNAME,
|
||||
password: redisEnv.PASSWORD,
|
||||
})
|
||||
: new Redis(defaultRedisOptions);
|
||||
Loaded 100 of 333 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user