Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b94219002 | ||
|
|
1575716f41 | ||
|
|
8966e2ad8a | ||
|
|
816544a996 | ||
|
|
9c323850bc | ||
|
|
c3936237c0 | ||
|
|
9b8d1cbb66 | ||
|
|
3062948939 | ||
|
|
78f98edc41 | ||
|
|
81ca6ef7b2 | ||
|
|
71186908b9 | ||
|
|
cbbf0c8dcf | ||
|
|
1d27da2230 | ||
|
|
d4ef212042 | ||
|
|
5ea7a20677 | ||
|
|
05e159f566 | ||
|
|
6d9e42cf5d | ||
|
|
437ecd1023 | ||
|
|
85e98d2b60 | ||
|
|
8e24d8362e | ||
|
|
c367f8ac23 | ||
|
|
c67916f72e | ||
|
|
25bd3879db | ||
|
|
939df8f6d1 | ||
|
|
2dc871e531 | ||
|
|
f7e5e823d5 | ||
|
|
240bf16806 | ||
|
|
640bf7b939 | ||
|
|
ec42f265a0 | ||
|
|
ea5a763046 | ||
|
|
e0a9e4f97c | ||
|
|
581746b2c2 | ||
|
|
be8b332f94 | ||
|
|
109f9e7979 | ||
|
|
2afa9b6727 | ||
|
|
09af0a76f6 | ||
|
|
71516e02c0 | ||
|
|
a5f7565468 | ||
|
|
0b224e8835 | ||
|
|
f748a12b58 | ||
|
|
6b746b09b5 | ||
|
|
e1eda534da | ||
|
|
09f4e6785b | ||
|
|
09f3989483 | ||
|
|
4cb004fce0 | ||
|
|
0ef2310776 | ||
|
|
b5bc8379ae | ||
|
|
43b82419e9 | ||
|
|
329d4727f1 | ||
|
|
9840644ca5 | ||
|
|
37d637dcae | ||
|
|
1584114e72 | ||
|
|
49498c870e | ||
|
|
5466ff5047 | ||
|
|
1e7b2e617b | ||
|
|
371803ac50 | ||
|
|
746ada97be | ||
|
|
5080f51a8d | ||
|
|
23404a27f1 | ||
|
|
dae6dedde5 | ||
|
|
caaee503c1 | ||
|
|
a356d61808 | ||
|
|
72bcbe8eff | ||
|
|
145365fab7 | ||
|
|
8c59abdde3 | ||
|
|
47869a3af4 | ||
|
|
5403b9c95e | ||
|
|
50445b560e | ||
|
|
18c465e81e | ||
|
|
eb5d5f8260 | ||
|
|
096d37be37 | ||
|
|
279a4b6add | ||
|
|
22954b8d26 | ||
|
|
92ebc05fbf | ||
|
|
79755c95d3 | ||
|
|
49b7080415 | ||
|
|
5b9f75eb45 | ||
|
|
6e2aa87102 | ||
|
|
17fbb0fc41 | ||
|
|
28be77b9c3 | ||
|
|
adddae6430 | ||
|
|
ef540e980f | ||
|
|
15675d95bd | ||
|
|
16e2f3f1e4 | ||
|
|
bf6b7912f9 | ||
|
|
ee8870b66b | ||
|
|
c6464e582e | ||
|
|
886e95d517 | ||
|
|
25cf9ad7ca | ||
|
|
dec3e9a653 | ||
|
|
ef9a5e9895 | ||
|
|
3daf1c8341 | ||
|
|
b86159ba7c | ||
|
|
42e7fe3273 | ||
|
|
dd250660b8 | ||
|
|
6e03e5a40c | ||
|
|
9b8320c10d | ||
|
|
94134303d7 | ||
|
|
b87e6bcaf2 | ||
|
|
5b16468cab | ||
|
|
b32278164e | ||
|
|
cb81efa42c | ||
|
|
dc34048ec2 | ||
|
|
25efe3d335 | ||
|
|
d96bc898fd | ||
|
|
56427ef794 | ||
|
|
c1e5d77176 | ||
|
|
6703155c61 | ||
|
|
f776cd6934 | ||
|
|
93b122c655 | ||
|
|
42b377d067 | ||
|
|
6c71724e2e | ||
|
|
593d97e27a | ||
|
|
ac49910e6e | ||
|
|
18fe8ecd39 | ||
|
|
388bc7392c | ||
|
|
2f9f561a8e | ||
|
|
72862efae2 | ||
|
|
5686910a47 | ||
|
|
59cae02623 | ||
|
|
e931b19661 | ||
|
|
8ff01eb17f | ||
|
|
b0f7310cb7 | ||
|
|
8c1b365733 | ||
|
|
b8bdf1836a | ||
|
|
01d53eee60 | ||
|
|
7f3574f8ba | ||
|
|
3f9f94ea45 | ||
|
|
98773e7dba | ||
|
|
0d2311975c | ||
|
|
a3af95734e | ||
|
|
17c4090024 | ||
|
|
93b9c4f6f3 | ||
|
|
1def17f4b5 | ||
|
|
b62d47d303 | ||
|
|
b71c5c6fc7 | ||
|
|
2bdb1c75f6 | ||
|
|
347c6d1519 | ||
|
|
d6180ebc73 | ||
|
|
395e03c810 | ||
|
|
f26634745e | ||
|
|
0ab2ae4cbb | ||
|
|
0d5f430bd4 | ||
|
|
dd300c2e1f | ||
|
|
8cb9ddbce3 | ||
|
|
e94c05f3d1 | ||
|
|
b6eb8ca0f6 | ||
|
|
9b6110b9c9 | ||
|
|
273d4edb23 | ||
|
|
36bd2a51b5 | ||
|
|
44853ea64b | ||
|
|
680f7b0c72 | ||
|
|
7c0baf5fba | ||
|
|
57264a5d6c | ||
|
|
7ec1793040 | ||
|
|
2568a9cecd | ||
|
|
861728a7c3 | ||
|
|
0284c5ec39 | ||
|
|
3315053ba1 | ||
|
|
fce8bfe1f4 | ||
|
|
cb50eda8fb | ||
|
|
bee1dddc9f | ||
|
|
0e7eae4553 | ||
|
|
c36e1708be | ||
|
|
f1c001d728 | ||
|
|
8a0557f927 | ||
|
|
4aa6fbb42d | ||
|
|
e5e686d590 | ||
|
|
758b1a0490 | ||
|
|
d512fc042a | ||
|
|
d401086d1b | ||
|
|
76ce6ff1fa | ||
|
|
9bb26ebcc3 | ||
|
|
a87bb54404 | ||
|
|
a9aa0e303b | ||
|
|
dbae2d2fdd | ||
|
|
b9b2398cd6 | ||
|
|
82c5361112 | ||
|
|
2b1c433cef | ||
|
|
a70b0b7047 | ||
|
|
e148698593 | ||
|
|
0d1b40c381 | ||
|
|
a934529146 | ||
|
|
0c9c5b8955 | ||
|
|
4dfde2ba05 | ||
|
|
48aef9e862 | ||
|
|
547a8c34c5 | ||
|
|
9006d3d2fc | ||
|
|
a2934247b2 | ||
|
|
1a07fa9af5 | ||
|
|
ec11c738f2 | ||
|
|
254c248e12 | ||
|
|
70686a0066 | ||
|
|
3299cedc04 | ||
|
|
1012cad0e3 | ||
|
|
b10f33bf0b | ||
|
|
a2dbac5ad7 | ||
|
|
f39a21c296 | ||
|
|
284ea239f7 | ||
|
|
583a6b6f4c | ||
|
|
039fad643e | ||
|
|
2f2c00ba3d | ||
|
|
ebb3012a65 | ||
|
|
f4ce76ac13 | ||
|
|
830660b1ef | ||
|
|
f7451d182a | ||
|
|
bb92f5bb79 | ||
|
|
f7ff1e3911 | ||
|
|
5f80bebef2 | ||
|
|
97fe1f6a3e | ||
|
|
35fee2f380 | ||
|
|
e1e26b27de | ||
|
|
55cdddb274 | ||
|
|
dd85f5a9d5 | ||
|
|
8550c69d51 | ||
|
|
3dcee8cb86 | ||
|
|
d97e74047d | ||
|
|
e36892fc8f | ||
|
|
1d28bf4c93 | ||
|
|
8b076ce761 | ||
|
|
d78b2e1e85 | ||
|
|
f65c0125a9 | ||
|
|
253308357a | ||
|
|
295cc4a3c4 | ||
|
|
767fe946d7 | ||
|
|
aba963c7cf | ||
|
|
4529cabb8a | ||
|
|
7766c3cbbc | ||
|
|
eb95ee285b | ||
|
|
1421c381cc | ||
|
|
573d5f89d6 | ||
|
|
e01b909402 | ||
|
|
bd3535dcf8 | ||
|
|
c4bd513134 | ||
|
|
c64d903f2b | ||
|
|
f98750d0b3 | ||
|
|
c51424717d | ||
|
|
3172e6e0c4 | ||
|
|
7aa379076d | ||
|
|
023ffcf7bf | ||
|
|
fed28bd775 | ||
|
|
09718b8258 | ||
|
|
6040f063ca | ||
|
|
3d508088d5 | ||
|
|
37484ce032 | ||
|
|
ac3c2a6377 | ||
|
|
6e6edc8a0d | ||
|
|
b0d7f9a48c | ||
|
|
fdf700dba5 | ||
|
|
2bf67c3de8 | ||
|
|
593e3ee5a6 | ||
|
|
ad5da4e8ac | ||
|
|
58ef0960b5 | ||
|
|
68d6967d24 | ||
|
|
e5f0bb9792 | ||
|
|
3d68f4d128 | ||
|
|
cfca0ba583 | ||
|
|
9570abb2c3 | ||
|
|
5fcb7a3b9c | ||
|
|
a55acb9eb9 | ||
|
|
d37cdbd080 | ||
|
|
17174b6b96 | ||
|
|
0466a2808a | ||
|
|
1bfe397d68 | ||
|
|
60de6361bb | ||
|
|
47d7abc0d7 | ||
|
|
2d12c1e641 | ||
|
|
00053bda7b | ||
|
|
97390c6030 | ||
|
|
865a52ae7d | ||
|
|
fdca513929 | ||
|
|
ce5784b6c4 | ||
|
|
1a4c136d41 | ||
|
|
089734b293 | ||
|
|
2e59bfcd60 | ||
|
|
02fb1b9dae | ||
|
|
bf4a1cf1e8 | ||
|
|
4a0fb5d0bb | ||
|
|
c57b63b506 | ||
|
|
fa0e694ce0 | ||
|
|
ff1263b217 | ||
|
|
ac78e87042 | ||
|
|
ec7adb03b6 | ||
|
|
f5f39bc094 | ||
|
|
4e6ab037c9 | ||
|
|
a29556ee8e | ||
|
|
71d8f7920d | ||
|
|
835e007334 | ||
|
|
d46b8f63c7 | ||
|
|
a40ebb10c7 | ||
|
|
05dd6227fb | ||
|
|
7a3c836a70 | ||
|
|
4baec7e3ff | ||
|
|
93bd09c85e | ||
|
|
0ed6a79a5b | ||
|
|
0fe4ea9dd9 | ||
|
|
d6a48d132a | ||
|
|
c84dd2fd79 | ||
|
|
94263c445b | ||
|
|
4b0b892250 | ||
|
|
057ff2cfc1 | ||
|
|
45d285a76b | ||
|
|
924a7d076e | ||
|
|
f13be4a708 | ||
|
|
7fb6beceef | ||
|
|
a1fa4297ea | ||
|
|
f5e3138c81 | ||
|
|
d21755426f | ||
|
|
5b332baa75 | ||
|
|
d0b9cbf155 | ||
|
|
3948f7f9a4 | ||
|
|
092c95bbe0 | ||
|
|
0fc7ca7738 | ||
|
|
da34b0a3cf | ||
|
|
75100d1a49 | ||
|
|
bd5dcb991c | ||
|
|
b064304201 | ||
|
|
c01d9a4dc5 | ||
|
|
a821bd71f4 | ||
|
|
17d92727f2 | ||
|
|
8ef2661be4 | ||
|
|
df212cf511 | ||
|
|
85f8d23da3 | ||
|
|
275224ee68 | ||
|
|
ffd1005ff1 | ||
|
|
bf046a715c | ||
|
|
c960c6fc79 | ||
|
|
75ba3f2ae7 | ||
|
|
c1cd563048 | ||
|
|
7caad6fc47 | ||
|
|
e07b9e6a88 | ||
|
|
14bff5d530 | ||
|
|
3e88ecd64a | ||
|
|
c6102edff4 | ||
|
|
646e2c12e6 | ||
|
|
95e7136284 | ||
|
|
9de9aa59a4 | ||
|
|
ee12461452 | ||
|
|
b03941ed80 | ||
|
|
968a82baf5 | ||
|
|
ea8bf43d7b | ||
|
|
964368a06c | ||
|
|
98ecc683e5 | ||
|
|
29ff879dfd | ||
|
|
674253262b | ||
|
|
f9aeb8ffe3 | ||
|
|
76b8b35cc1 | ||
|
|
8886380f0a | ||
|
|
0b41967ec3 | ||
|
|
1b0504198b | ||
|
|
3363eebddd | ||
|
|
85672d9cf1 | ||
|
|
3131082c77 | ||
|
|
f33b1cb5d0 | ||
|
|
4f32f11d8a | ||
|
|
466d0e0053 | ||
|
|
7c4b71af3d | ||
|
|
81d1231d8f | ||
|
|
858fe6e19a | ||
|
|
ff5c31342f | ||
|
|
807997ea76 | ||
|
|
9c12b18c6c | ||
|
|
3951d052de | ||
|
|
5975b53212 | ||
|
|
cc9522d981 | ||
|
|
7f77130978 | ||
|
|
243a97d637 | ||
|
|
4feeb8b173 | ||
|
|
b1a8012bc7 | ||
|
|
22b67188bc | ||
|
|
dd6e2c0f72 | ||
|
|
9db0016252 | ||
|
|
3e3505176a | ||
|
|
3687c4b3d6 | ||
|
|
944010a7b1 | ||
|
|
db13fd03c0 | ||
|
|
0b8a0098bd | ||
|
|
9a77b540bb | ||
|
|
c598579716 | ||
|
|
59b1630532 | ||
|
|
77044e8e0b | ||
|
|
0ec8f23b7c | ||
|
|
c69e331ded | ||
|
|
4273cd9fcd | ||
|
|
98515312a2 | ||
|
|
d1b14aca8b | ||
|
|
c6b4c9cbf2 | ||
|
|
19029f40b8 | ||
|
|
b6bc257726 | ||
|
|
fef50a53b1 | ||
|
|
94ff401d7d | ||
|
|
63328d125a | ||
|
|
457b970d8f | ||
|
|
8d69970562 | ||
|
|
8a071f9ab6 | ||
|
|
76b67ac8e1 | ||
|
|
eba738e5d6 | ||
|
|
996ec548c4 | ||
|
|
40e00987cb | ||
|
|
93c64baec2 | ||
|
|
b5b0b069d2 | ||
|
|
62c7955e48 | ||
|
|
217818573e | ||
|
|
70879ccb90 | ||
|
|
34eb9b366f | ||
|
|
ac77bdd84c | ||
|
|
70ea093eb6 | ||
|
|
08e0dd76ae | ||
|
|
0ed4c741c5 | ||
|
|
569ec6fe5c | ||
|
|
e7e80dbc7d | ||
|
|
b054bdd987 | ||
|
|
84f73d33a0 | ||
|
|
78b55202e7 | ||
|
|
7706f97228 | ||
|
|
a0ee558582 | ||
|
|
94d3dad909 | ||
|
|
1a3a55934d | ||
|
|
8bf893b392 | ||
|
|
43b8cfa4dd | ||
|
|
a05b5965b6 | ||
|
|
68407eaa19 | ||
|
|
6854e4729d | ||
|
|
f03e381aea | ||
|
|
151ccb7086 | ||
|
|
8f7f2110ec | ||
|
|
579dd5763d | ||
|
|
3e1c000d51 | ||
|
|
f624611540 | ||
|
|
07f0abf28c | ||
|
|
c3264b2fe1 | ||
|
|
d0aa48fab3 | ||
|
|
d634fa2c95 | ||
|
|
5a8405a9a8 | ||
|
|
a6f388d076 | ||
|
|
1dd7b1a6b9 | ||
|
|
b7a6d950db | ||
|
|
00f5a877a6 | ||
|
|
75a949eecf | ||
|
|
ce0705e9d4 | ||
|
|
0f4a9af57d | ||
|
|
703a362356 | ||
|
|
826d54a0ca | ||
|
|
46c95e0da3 | ||
|
|
0aacbe95c2 | ||
|
|
18f845b4c4 | ||
|
|
b5ae5f876b | ||
|
|
ef359b6997 | ||
|
|
1d3dc5b9e7 | ||
|
|
ff0b0c659c | ||
|
|
8dee8f8680 | ||
|
|
25e4c814ac | ||
|
|
3edfe6bfed | ||
|
|
22d32ccb8d | ||
|
|
7aa5d2f8ed | ||
|
|
91a69c162a | ||
|
|
e3fcfbe916 | ||
|
|
3e4d24b28e | ||
|
|
425d5edab5 | ||
|
|
b0381cb883 | ||
|
|
a04cb25dd0 | ||
|
|
b4692b62ab | ||
|
|
bb8cb6dded | ||
|
|
4cc37397c2 | ||
|
|
2d2ae5a419 | ||
|
|
15197897ac | ||
|
|
289c7f1a60 | ||
|
|
495432073f | ||
|
|
c9fecaabb6 | ||
|
|
5b49fe1039 | ||
|
|
7ba2067689 | ||
|
|
e0ae5e6705 | ||
|
|
305d259e98 | ||
|
|
fed3d1ce23 | ||
|
|
8f083adf39 | ||
|
|
3e9c8172ef | ||
|
|
f743c76b17 | ||
|
|
7d8650babc | ||
|
|
612f0e4fbf | ||
|
|
75fb9dbf1e | ||
|
|
7f7156a798 | ||
|
|
85d2d61184 | ||
|
|
25f7406283 | ||
|
|
1e50ac411c | ||
|
|
04c17d1c41 | ||
|
|
22f9166690 | ||
|
|
8a02c65648 | ||
|
|
bb20999bf3 | ||
|
|
b607c5702f | ||
|
|
83eacb23b9 | ||
|
|
d8247f2dc9 | ||
|
|
56fe8dd9e9 | ||
|
|
c94159388d | ||
|
|
f55d8a9c2e | ||
|
|
47ebc66c9e | ||
|
|
d669f99ac1 | ||
|
|
3d4d32f377 | ||
|
|
268daee4a6 | ||
|
|
a53e7aaee5 | ||
|
|
fbbfe9af29 | ||
|
|
e774b06fc2 | ||
|
|
6c9fbd6c09 | ||
|
|
2135f9980b | ||
|
|
ba3f1e9eaa | ||
|
|
8486073f27 | ||
|
|
afcde8afe5 | ||
|
|
a102f39518 | ||
|
|
225ed4881b | ||
|
|
d714e53cfa | ||
|
|
483ce9c28d | ||
|
|
ff6ca86a78 | ||
|
|
55ad0b0a65 | ||
|
|
8cb8eaf9cc | ||
|
|
9c582d906b | ||
|
|
d6cfd23068 | ||
|
|
f19aa29607 | ||
|
|
f6f0d7c72b | ||
|
|
2f80c096a7 | ||
|
|
8f8d7884a9 | ||
|
|
d584ade8f4 | ||
|
|
82528dbd96 | ||
|
|
d9720ac596 | ||
|
|
9e48f438b4 | ||
|
|
4472c04967 | ||
|
|
2e62a61f4d | ||
|
|
0b07f227ee | ||
|
|
fd5b1b0c08 | ||
|
|
1db9c3c692 | ||
|
|
fb800a0008 | ||
|
|
c0e1355d7f | ||
|
|
7ec6fbc376 | ||
|
|
df249a0173 | ||
|
|
ac5fc38ec0 | ||
|
|
75bee0be8b | ||
|
|
b3464b3340 | ||
|
|
4a7ccda362 | ||
|
|
d410af3402 | ||
|
|
65f00f19de | ||
|
|
e3ad569cc2 | ||
|
|
f25837f9f8 | ||
|
|
44007b2b4b | ||
|
|
37d471457a | ||
|
|
9881577f47 | ||
|
|
a572aae887 | ||
|
|
4721a97094 | ||
|
|
15026afa46 | ||
|
|
3403579925 | ||
|
|
da4aaba9f0 | ||
|
|
98dd96f37c | ||
|
|
67c739c35b | ||
|
|
6f8b3de903 | ||
|
|
e4a39223a3 | ||
|
|
72860a877b | ||
|
|
0f3089a1bd | ||
|
|
84519bf734 | ||
|
|
478aae6cd4 | ||
|
|
604a05f9d5 | ||
|
|
b3e4f30312 | ||
|
|
a3999bcf52 | ||
|
|
3804d530ec | ||
|
|
e88b29dc9d | ||
|
|
d470ea4ede | ||
|
|
21f3a5a408 | ||
|
|
14f3fbfc82 | ||
|
|
6135b59b7d | ||
|
|
bad07cbf25 | ||
|
|
49b0f962e0 | ||
|
|
c94fa4e999 | ||
|
|
be771e5630 | ||
|
|
a5e0e8c129 | ||
|
|
7dfb108a56 | ||
|
|
7dfc3646b7 | ||
|
|
d66610b324 | ||
|
|
9eb76634d0 | ||
|
|
e1d69f91e7 | ||
|
|
80c2040b13 | ||
|
|
43619e0cc0 | ||
|
|
6fad48f65a | ||
|
|
788304adfb | ||
|
|
c0e354428a | ||
|
|
286cc0fcac | ||
|
|
d9ef1b0470 | ||
|
|
163e3b007f | ||
|
|
f4b302bbf8 | ||
|
|
3c1cdb5e6e | ||
|
|
f0b932432b | ||
|
|
df13d67b42 | ||
|
|
932426550b | ||
|
|
d0ede4d643 | ||
|
|
14da6727e0 | ||
|
|
a88c1ba440 | ||
|
|
5537f95b85 | ||
|
|
d8e7dfa2fa | ||
|
|
06ba0f0e9f | ||
|
|
11d2972713 | ||
|
|
4483dc8bcd | ||
|
|
50367eb2f5 | ||
|
|
fdd91f585d | ||
|
|
add91701de | ||
|
|
8c1308c97f | ||
|
|
b2cc97e316 | ||
|
|
46ff689a7e | ||
|
|
2b40233235 | ||
|
|
d6939e4a6d | ||
|
|
c2d6e2efcf | ||
|
|
0f2fc0505c | ||
|
|
faf7322bcb | ||
|
|
3b830ae098 | ||
|
|
a6f91998c3 | ||
|
|
874414fc4f | ||
|
|
fda3aff170 | ||
|
|
a9a37afd44 | ||
|
|
2f3d297c46 | ||
|
|
8355bb5919 | ||
|
|
46070fa5a3 | ||
|
|
593c50bb8f | ||
|
|
508f9afeb5 | ||
|
|
ef9289d945 | ||
|
|
9c892eacfc | ||
|
|
7761dc29c8 | ||
|
|
2085b5ece2 | ||
|
|
c5107b2b67 | ||
|
|
5dc5d9e04a | ||
|
|
4f09363823 | ||
|
|
c132a07213 | ||
|
|
86e778db1e | ||
|
|
ee1ed0d076 | ||
|
|
675ed5bdc1 | ||
|
|
fb467ac165 | ||
|
|
9d54e938c8 | ||
|
|
f6029fa79d | ||
|
|
039d881418 | ||
|
|
4f220ffca1 | ||
|
|
a54e0783e2 | ||
|
|
1cf36ed4c7 | ||
|
|
afd7bcea84 | ||
|
|
b58f64b05c | ||
|
|
118f19a4f2 | ||
|
|
52549a598d | ||
|
|
311ae80667 | ||
|
|
8b7596642a | ||
|
|
078aacd9fa | ||
|
|
bf600d0ae5 | ||
|
|
de5c34ab07 | ||
|
|
58babbf88f | ||
|
|
3cca0249a4 | ||
|
|
59b1cb48eb | ||
|
|
67d48e11d7 | ||
|
|
6420feee72 | ||
|
|
63e96230e0 | ||
|
|
8b7caf1d7d | ||
|
|
7705bc44ae | ||
|
|
8e71b882db | ||
|
|
e84cc5e6ae | ||
|
|
241037fc2a | ||
|
|
bd0d910137 | ||
|
|
146fe499db | ||
|
|
96a071725e | ||
|
|
91c61f8c06 | ||
|
|
c59fbfc677 | ||
|
|
602faf6221 | ||
|
|
cdc1d38be6 | ||
|
|
4764f41e1b | ||
|
|
f6450cb4b4 | ||
|
|
a4317f9479 | ||
|
|
a62aa09db6 | ||
|
|
f32af66a6a | ||
|
|
4152ca7366 | ||
|
|
ac941362de | ||
|
|
f8fedefde9 | ||
|
|
5154b6d17a | ||
|
|
f6c923017c | ||
|
|
22a341ea4d | ||
|
|
3257f6101b | ||
|
|
9c4e5f9842 | ||
|
|
ffe7259802 | ||
|
|
33ef9f6678 | ||
|
|
91bbc47b00 | ||
|
|
d7822d2b98 | ||
|
|
51bea03099 | ||
|
|
8ee39eb6db | ||
|
|
4074d189e3 | ||
|
|
7a7f40a140 | ||
|
|
1f9442f7f4 | ||
|
|
a38afb05d3 | ||
|
|
fae1aacf20 | ||
|
|
d508fd466d | ||
|
|
4d3cc1d806 | ||
|
|
e1fa92b91e | ||
|
|
10454656a2 | ||
|
|
ee08725d53 | ||
|
|
379675a901 | ||
|
|
8649faca79 | ||
|
|
91260bf0d4 | ||
|
|
51038cc03c | ||
|
|
fc43da59f8 | ||
|
|
2399967868 | ||
|
|
480dae09c4 | ||
|
|
2d2ba2e088 | ||
|
|
0502e47770 | ||
|
|
3c877be44a | ||
|
|
8371f46d34 | ||
|
|
2b9f834d96 | ||
|
|
8ec0080fca | ||
|
|
b7fd2470ee | ||
|
|
749b0c6ee9 | ||
|
|
25b7419d7b | ||
|
|
7edaa48fb9 | ||
|
|
627064b632 | ||
|
|
42a68140b1 | ||
|
|
f6a5c04017 | ||
|
|
2e150d548f | ||
|
|
584c660906 | ||
|
|
7abc4e4e79 | ||
|
|
0e553a5f8f | ||
|
|
dff6cb9d31 | ||
|
|
c04c42dc8a | ||
|
|
60323b2651 | ||
|
|
93235cab6f | ||
|
|
c849109df5 | ||
|
|
0da5a1b0fb | ||
|
|
7a8e671205 | ||
|
|
3cbb656ee9 | ||
|
|
55a493c475 | ||
|
|
7bbbaf343b | ||
|
|
09b6323246 | ||
|
|
3d0b319a88 | ||
|
|
26468a4137 | ||
|
|
62c83b79b4 | ||
|
|
da09826393 | ||
|
|
8058b6207a | ||
|
|
c623a420ca | ||
|
|
d5e36835f6 | ||
|
|
1f12a893ff | ||
|
|
bb67bf5fa2 | ||
|
|
be603badfe | ||
|
|
0d97cc50c2 | ||
|
|
14a4b94911 | ||
|
|
83044d23dd | ||
|
|
12456af101 | ||
|
|
8c656febeb | ||
|
|
2598ade925 | ||
|
|
46cfb2383b | ||
|
|
a385337268 | ||
|
|
bc79c6abab | ||
|
|
5f36d8b125 | ||
|
|
6a9b65f219 | ||
|
|
6ce7bc0ee2 | ||
|
|
36c246cca8 |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
**/node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
|
||||
@@ -12,6 +12,8 @@ AUTH_SECRET="supersecret"
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
DB_DRIVER='better-sqlite3'
|
||||
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
@@ -32,4 +34,7 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
# 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
|
||||
TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
# Enable kubernetes tool
|
||||
# ENABLE_KUBERNETES=true
|
||||
@@ -31,6 +31,26 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.21.0
|
||||
- 1.20.0
|
||||
- 1.19.1
|
||||
- 1.19.0
|
||||
- 1.18.0
|
||||
- 1.17.0
|
||||
- 1.16.0
|
||||
- 1.15.0
|
||||
- 1.14.0
|
||||
- 1.13.1
|
||||
- 1.13.0
|
||||
- 1.12.0
|
||||
- 1.11.0
|
||||
- 1.10.0
|
||||
- 1.9.0
|
||||
- 1.8.0
|
||||
- 1.7.0
|
||||
- 1.6.0
|
||||
- 1.5.0
|
||||
- 1.4.0
|
||||
- 1.3.1
|
||||
- 1.3.0
|
||||
- 1.2.0
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
|
||||
|
||||
- [ ] Builds without warnings or errors (``pnpm buid``, autofix with ``pnpm format:fix``)
|
||||
- [ ] Builds without warnings or errors (``pnpm build``, autofix with ``pnpm format:fix``)
|
||||
- [ ] Pull request targets ``dev`` branch
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] No shorthand variable names are used (eg. ``x``, ``y``, ``i`` or any abbrevation)
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
matchPackagePatterns: ["^@homarr/"],
|
||||
enabled: false,
|
||||
},
|
||||
// Disable Dockerode updates see https://github.com/apocas/dockerode/issues/787
|
||||
{
|
||||
matchPackagePatterns: ["^dockerode$"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||
automerge: true,
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
approve-automatic-prs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D
|
||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -15,13 +15,14 @@ on:
|
||||
description: Send notifications
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: write # Required to update package.json version
|
||||
packages: write # Required for pushing to GHCR
|
||||
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
GHCR_REPO: ghcr.io/${{ github.repository }}
|
||||
TURBO_TELEMETRY_DISABLED: 1
|
||||
|
||||
concurrency:
|
||||
@@ -96,64 +97,129 @@ jobs:
|
||||
run: |
|
||||
git config user.name "Releases Homarr"
|
||||
git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${{ steps.obtainToken.outputs.token }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin dev
|
||||
git checkout dev
|
||||
git pull origin dev
|
||||
git merge ${{ github.ref_name }}
|
||||
git rebase ${{ github.ref_name }}
|
||||
git push origin dev
|
||||
deploy:
|
||||
name: Deploy docker image
|
||||
build-amd64:
|
||||
name: Build docker image for amd64
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEXT_VERSION: ${{ needs.release.outputs.version }}
|
||||
DEPLOY_LATEST: ${{ github.ref_name == 'main' }}
|
||||
DEPLOY_BETA: ${{ github.ref_name == 'beta' }}
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
- name: Discord notification
|
||||
if: ${{ github.events.inputs.send-notifications != false }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@master
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
args: "Deployment of an image for version '${{env.NEXT_VERSION}}' has been triggered: [run ${{ github.run_number }}](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>)"
|
||||
images: "${{ env.GHCR_REPO }}"
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
|
||||
build-arm64:
|
||||
name: Build docker image for arm64
|
||||
needs: release
|
||||
runs-on: ubuntu-24.04-arm
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||
tags: |
|
||||
${{ env.DEPLOY_LATEST == 'true' && 'type=raw,value=latest' || null }}
|
||||
${{ env.DEPLOY_BETA == 'true' && 'type=raw,value=beta' || null }}
|
||||
type=raw,value=${{ env.NEXT_VERSION }}
|
||||
- name: Build and push
|
||||
id: buildPushAction
|
||||
images: "${{ env.GHCR_REPO }}"
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
network: host
|
||||
platforms: linux/arm64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
|
||||
publish:
|
||||
name: Complete deployment and notify
|
||||
needs: [release, build-amd64, build-arm64]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEXT_VERSION: ${{ needs.release.outputs.version }}
|
||||
DEPLOY_LATEST: ${{ github.ref_name == 'main' }}
|
||||
DEPLOY_BETA: ${{ github.ref_name == 'beta' }}
|
||||
steps:
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish beta
|
||||
if: env.DEPLOY_BETA == 'true'
|
||||
run: |
|
||||
docker buildx imagetools create -t ${{ env.GHCR_REPO }}:beta \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }} \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}
|
||||
|
||||
- name: Publish latest
|
||||
if: env.DEPLOY_LATEST == 'true'
|
||||
run: |
|
||||
docker buildx imagetools create -t ${{ env.GHCR_REPO }}:latest \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }} \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}
|
||||
|
||||
- name: Publish version
|
||||
run: |
|
||||
docker buildx imagetools create -t ${{ env.GHCR_REPO }}:${{ env.NEXT_VERSION }} \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }} \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}
|
||||
|
||||
- name: Discord notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: "Deployment of image has completed for branch ${{ github.ref_name }}. Image ID is '${{ steps.buildPushAction.outputs.imageid }}'."
|
||||
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Update Contributors
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 12 * * FRI" # At 12:00 on Friday.
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
CROWDIN_TOKEN: "${{ secrets.CROWDIN_UPDATE_CONTRIBUTORS_TOKEN }}"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-contributors:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [22]
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Run update script
|
||||
run: node ./scripts/update-contributors.mjs
|
||||
|
||||
- name: Commit changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
git config --global user.email "210161987+homarr-update-contributors[bot]@users.noreply.github.com"
|
||||
git config --global user.name "Homarr Update Contributors"
|
||||
git add .
|
||||
git commit -m "chore: update contributors"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-contributors
|
||||
base: dev
|
||||
title: "chore: update contributors"
|
||||
delete-branch: true
|
||||
body: |
|
||||
This PR updates the contributors list in the static-data directory.
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Enable auto-merge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
|
||||
@@ -65,3 +65,6 @@ e2e/shared/tmp
|
||||
|
||||
#personal backgrounds
|
||||
apps/nextjs/public/images/background.png
|
||||
|
||||
# next-intl
|
||||
en.d.json.ts
|
||||
@@ -0,0 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All Tests" type="JavaScriptTestRunnerVitest" nameIsGenerated="true">
|
||||
<node-interpreter value="project" />
|
||||
<vitest-package value="$PROJECT_DIR$/node_modules/vitest" />
|
||||
<working-dir value="$PROJECT_DIR$" />
|
||||
<vitest-options value="--exclude e2e --coverage.enabled" />
|
||||
<envs>
|
||||
<env name="NODE_ENV" value="development" />
|
||||
<env name="CI" value="true" />
|
||||
</envs>
|
||||
<scope-kind value="ALL" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="typecheck" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="typecheck" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -12,17 +12,17 @@ languageIds:
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
usageMatchRegex:
|
||||
# The following example shows how to detect `t("your.i18n.keys")`
|
||||
# the `{key}` will be placed by a proper keypath matching regex,
|
||||
# you can ignore it and use your own matching rules as well
|
||||
# For direct t("your.i18n.keys") usage
|
||||
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
|
||||
# For variable t assigned from getScopedI18n or useScopedI18n
|
||||
- "\\bt\\(['\"`]({key})['\"`]\\)"
|
||||
|
||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||
# and works like how the i18next framework identifies the namespace scope from the
|
||||
# useTranslation() hook.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
scopeRangeRegex: "(getScopedI18n|useScopedI18n)\\(\\s*['\"](.*?)['\"]\\)"
|
||||
scopeRangeRegex: "(?:const|let|var)\\s+t\\s*=\\s*(?:await\\s+)?(?:getScopedI18n|useScopedI18n)\\(\\s*['\"](.*?)['\"]\\)"
|
||||
|
||||
# An array of strings containing refactor templates.
|
||||
# The "$1" will be replaced by the keypath specified.
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"Sabnzbd",
|
||||
"SeDemal",
|
||||
"Sonarr",
|
||||
"sslverify",
|
||||
"superjson",
|
||||
"tabler",
|
||||
"trpc",
|
||||
|
||||
@@ -1,3 +1,570 @@
|
||||
## [1.21.0](https://github.com/homarr-labs/homarr/compare/v1.20.0...v1.21.0) (2025-05-23)
|
||||
|
||||
### Features
|
||||
|
||||
* docker widget ([#2288](https://github.com/homarr-labs/homarr/issues/2288)) ([e1eda53](https://github.com/homarr-labs/homarr/commit/e1eda534da6d97655546f3140f9e36eca8817db3))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.33.0 ([#3132](https://github.com/homarr-labs/homarr/issues/3132)) ([ee8870b](https://github.com/homarr-labs/homarr/commit/ee8870b66bbcb8eeeef359ece9391a496198be0d))
|
||||
* **deps:** update dependency octokit to ^4.1.4 ([#3165](https://github.com/homarr-labs/homarr/issues/3165)) ([50445b5](https://github.com/homarr-labs/homarr/commit/50445b560edc3af2631f8d8d9aa81f3387d848f7))
|
||||
* **deps:** update dependency octokit to v5 ([#3156](https://github.com/homarr-labs/homarr/issues/3156)) ([8c59abd](https://github.com/homarr-labs/homarr/commit/8c59abdde3d8034f22a2b926b3291b55a0d492ea))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.22.0 ([#3178](https://github.com/homarr-labs/homarr/issues/3178)) ([1e7b2e6](https://github.com/homarr-labs/homarr/commit/1e7b2e617b1437f0f98bc791e763bc49b6589b9e))
|
||||
* **deps:** update dependency undici to v7.10.0 ([#3158](https://github.com/homarr-labs/homarr/issues/3158)) ([eb5d5f8](https://github.com/homarr-labs/homarr/commit/eb5d5f8260cc9795806ecdb56fbb3f8d19858ed7))
|
||||
* **deps:** update dependency zod to ^3.25.12 ([#3172](https://github.com/homarr-labs/homarr/issues/3172)) ([caaee50](https://github.com/homarr-labs/homarr/commit/caaee503c128ff2a0b2a7de7a7c353484e4741a5))
|
||||
* **deps:** update dependency zod to ^3.25.13 ([#3173](https://github.com/homarr-labs/homarr/issues/3173)) ([dae6ded](https://github.com/homarr-labs/homarr/commit/dae6dedde544bf28fcdf87c71a9d585078527b82))
|
||||
* **deps:** update dependency zod to ^3.25.14 ([#3174](https://github.com/homarr-labs/homarr/issues/3174)) ([23404a2](https://github.com/homarr-labs/homarr/commit/23404a27f1a9dfec4e9126750f8d97e7c5eda572))
|
||||
* **deps:** update dependency zod to ^3.25.16 ([#3175](https://github.com/homarr-labs/homarr/issues/3175)) ([5080f51](https://github.com/homarr-labs/homarr/commit/5080f51a8dbe1b82571f356f7ae29ed7be69e18e))
|
||||
* **deps:** update dependency zod to ^3.25.17 ([#3177](https://github.com/homarr-labs/homarr/issues/3177)) ([371803a](https://github.com/homarr-labs/homarr/commit/371803ac509efd246367c812a6c0143e7aeda03f))
|
||||
* **deps:** update dependency zod to ^3.25.20 ([#3183](https://github.com/homarr-labs/homarr/issues/3183)) ([49498c8](https://github.com/homarr-labs/homarr/commit/49498c870e494b417e6c63d8547a4c0ffd93768b))
|
||||
* **deps:** update dependency zod to ^3.25.21 ([#3187](https://github.com/homarr-labs/homarr/issues/3187)) ([37d637d](https://github.com/homarr-labs/homarr/commit/37d637dcae6cbf62efda25a8dc01eedba66f2c74))
|
||||
* **deps:** update dependency zod to ^3.25.23 ([#3188](https://github.com/homarr-labs/homarr/issues/3188)) ([9840644](https://github.com/homarr-labs/homarr/commit/9840644ca50d3157223e7da0cf895f2e750afc58))
|
||||
* **deps:** update dependency zod to ^3.25.3 ([#3148](https://github.com/homarr-labs/homarr/issues/3148)) ([6e2aa87](https://github.com/homarr-labs/homarr/commit/6e2aa87102b72319236483b9a1d3e5ddd6fe2c82))
|
||||
* **deps:** update dependency zod to ^3.25.4 ([#3150](https://github.com/homarr-labs/homarr/issues/3150)) ([49b7080](https://github.com/homarr-labs/homarr/commit/49b7080415aaeee576507d48e8cda18acc9943a8))
|
||||
* **deps:** update dependency zod to ^3.25.5 ([#3152](https://github.com/homarr-labs/homarr/issues/3152)) ([79755c9](https://github.com/homarr-labs/homarr/commit/79755c95d3cf58680e434f0113c130db9f649d96))
|
||||
* **deps:** update dependency zod to ^3.25.6 ([#3153](https://github.com/homarr-labs/homarr/issues/3153)) ([92ebc05](https://github.com/homarr-labs/homarr/commit/92ebc05fbfded6d20d242ad477cb0dc30243e8cc))
|
||||
* **deps:** update dependency zod to ^3.25.7 ([#3154](https://github.com/homarr-labs/homarr/issues/3154)) ([22954b8](https://github.com/homarr-labs/homarr/commit/22954b8d2637cdea9a6502cab2d3d40359fa098f))
|
||||
* **deps:** update dependency zod to ^3.25.8 ([#3170](https://github.com/homarr-labs/homarr/issues/3170)) ([72bcbe8](https://github.com/homarr-labs/homarr/commit/72bcbe8effe47fdfb6a94de8defc0d3c12e7c754))
|
||||
* **deps:** update dependency zod-validation-error to ^3.4.1 ([#3127](https://github.com/homarr-labs/homarr/issues/3127)) ([886e95d](https://github.com/homarr-labs/homarr/commit/886e95d51714ce20e03d32b4c2cfb673e7e4c218))
|
||||
* **deps:** update tanstack-query monorepo to ^5.76.2 ([#3186](https://github.com/homarr-labs/homarr/issues/3186)) ([1584114](https://github.com/homarr-labs/homarr/commit/1584114e72246a018b9f49a859e49f7fb985009d))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.27.0 ([#3159](https://github.com/homarr-labs/homarr/issues/3159)) ([18c465e](https://github.com/homarr-labs/homarr/commit/18c465e81e92cf58b7e32bcb70f1ab404a03a135))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.28.0 ([#3191](https://github.com/homarr-labs/homarr/issues/3191)) ([43b8241](https://github.com/homarr-labs/homarr/commit/43b82419e9396f0b80bafa0a707d575c3732dc5b))
|
||||
* **downloads:** reordering columns in widget does not work ([#3194](https://github.com/homarr-labs/homarr/issues/3194)) ([09f4e67](https://github.com/homarr-labs/homarr/commit/09f4e6785b2ce38da3b52003e9c9300cdd0feb85))
|
||||
* esbuild not part of dependencies ([#3139](https://github.com/homarr-labs/homarr/issues/3139)) ([ef540e9](https://github.com/homarr-labs/homarr/commit/ef540e980f90f730020ef081dd105e33b0671232))
|
||||
* **items:** resize handle overlaps header ([#3193](https://github.com/homarr-labs/homarr/issues/3193)) ([09f3989](https://github.com/homarr-labs/homarr/commit/09f398948348fb402600506571a967ba405fbafb))
|
||||
* **releases-widget:** add hardcoded css classNames([#3181](https://github.com/homarr-labs/homarr/issues/3181)) ([4cb004f](https://github.com/homarr-labs/homarr/commit/4cb004fce0750da339f603b944de55135faa4bfa))
|
||||
|
||||
## [1.20.0](https://github.com/homarr-labs/homarr/compare/v1.19.1...v1.20.0) (2025-05-16)
|
||||
|
||||
### Features
|
||||
|
||||
* add css class to logo for custom css ([#3121](https://github.com/homarr-labs/homarr/issues/3121)) ([9b8320c](https://github.com/homarr-labs/homarr/commit/9b8320c10d51cc449b3fb5c1532408bff5655788))
|
||||
* **auth:** add account linking for oidc providers ([#3106](https://github.com/homarr-labs/homarr/issues/3106)) ([3daf1c8](https://github.com/homarr-labs/homarr/commit/3daf1c8341ab40a580777246e742ff5019a483d2))
|
||||
* **integration:** improve integration test connection ([#3005](https://github.com/homarr-labs/homarr/issues/3005)) ([ef9a5e9](https://github.com/homarr-labs/homarr/commit/ef9a5e9895d7079008046df66f6a6d538af106c9))
|
||||
* **releases-widget:** added auto icon search when editing a repository ([#3087](https://github.com/homarr-labs/homarr/issues/3087)) ([9413430](https://github.com/homarr-labs/homarr/commit/94134303d7dc6f2d52e4955dcd9d1afbd8d203fb))
|
||||
* **releases-widget:** limit number of displayed releases and custom name for repositories ([#2974](https://github.com/homarr-labs/homarr/issues/2974)) ([8c1b365](https://github.com/homarr-labs/homarr/commit/8c1b365733c2bccd0838cbf07aa59115bcc0c0cd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **calendar:** some timezones result in offset of one day ([#3120](https://github.com/homarr-labs/homarr/issues/3120)) ([b86159b](https://github.com/homarr-labs/homarr/commit/b86159ba7c1cdaea973466bd7430fc45fd0d8317))
|
||||
* **deps:** missing dependencies in definitions package for docs link codegen ([#3119](https://github.com/homarr-labs/homarr/issues/3119)) ([42e7fe3](https://github.com/homarr-labs/homarr/commit/42e7fe3273b0f0e8f67e38ac3fd758528cc47913))
|
||||
* **deps:** update dependency @auth/core to ^0.39.1 ([#3072](https://github.com/homarr-labs/homarr/issues/3072)) ([0d23119](https://github.com/homarr-labs/homarr/commit/0d2311975c9062f538e7cf7166cc354bc6b21468))
|
||||
* **deps:** update dependency @auth/drizzle-adapter to ^1.9.1 ([#3073](https://github.com/homarr-labs/homarr/issues/3073)) ([3f9f94e](https://github.com/homarr-labs/homarr/commit/3f9f94ea45e001d0f2c966ee21d1e148103a854e))
|
||||
* **deps:** update dependency @extractus/feed-extractor to v7.1.6 ([#3105](https://github.com/homarr-labs/homarr/issues/3105)) ([d96bc89](https://github.com/homarr-labs/homarr/commit/d96bc898fdc014940207c1d7bead371ac4df33a9))
|
||||
* **deps:** update dependency bcrypt to v6 ([#3083](https://github.com/homarr-labs/homarr/issues/3083)) ([5686910](https://github.com/homarr-labs/homarr/commit/5686910a4735801c40caed08a4c8b931fd04418f))
|
||||
* **deps:** update dependency next-auth to v5.0.0-beta.28 ([#3074](https://github.com/homarr-labs/homarr/issues/3074)) ([98773e7](https://github.com/homarr-labs/homarr/commit/98773e7dba3f7f816a50c134cedf81cddb55e5f1))
|
||||
* **deps:** update dependency sass to ^1.88.0 ([#3078](https://github.com/homarr-labs/homarr/issues/3078)) ([b0f7310](https://github.com/homarr-labs/homarr/commit/b0f7310cb79e014e58a72dcf812d3d4cc876d034))
|
||||
* **deps:** update dependency sass to ^1.89.0 ([#3118](https://github.com/homarr-labs/homarr/issues/3118)) ([5b16468](https://github.com/homarr-labs/homarr/commit/5b16468cab53429d4f8cf36771ab6efafea86743))
|
||||
* **deps:** update dependency typescript-eslint to ^8.32.1 ([#3093](https://github.com/homarr-labs/homarr/issues/3093)) ([42b377d](https://github.com/homarr-labs/homarr/commit/42b377d0672973cb3d7b32c0d1e2c0663d68c2a8))
|
||||
* **deps:** update dependency undici to v7.9.0 ([#3077](https://github.com/homarr-labs/homarr/issues/3077)) ([01d53ee](https://github.com/homarr-labs/homarr/commit/01d53eee60f7269158565cfa46f7aebdfa8d41cf))
|
||||
* **deps:** update mantine monorepo to ^8.0.1 ([#3107](https://github.com/homarr-labs/homarr/issues/3107)) ([25efe3d](https://github.com/homarr-labs/homarr/commit/25efe3d33504f8e4f1eb3f29edfdeccf4b15a8e4))
|
||||
* **deps:** update tanstack-query monorepo to ^5.76.0 ([#3090](https://github.com/homarr-labs/homarr/issues/3090)) ([388bc73](https://github.com/homarr-labs/homarr/commit/388bc7392c318e05f85134cf969e10d9627c55f9))
|
||||
* **deps:** update tanstack-query monorepo to ^5.76.1 ([#3101](https://github.com/homarr-labs/homarr/issues/3101)) ([c1e5d77](https://github.com/homarr-labs/homarr/commit/c1e5d77176126c6248f052d2baa3cf09837fba62))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.26.0 ([#3114](https://github.com/homarr-labs/homarr/issues/3114)) ([cb81efa](https://github.com/homarr-labs/homarr/commit/cb81efa42c99f4bd8024b21e0818294db8d22fec))
|
||||
* **releases-widget:** delete the empty repository when canceling `Add Reposotory` ([#3082](https://github.com/homarr-labs/homarr/issues/3082)) ([72862ef](https://github.com/homarr-labs/homarr/commit/72862efae274cfc25fe656aef27f6acb2e1b0ca6))
|
||||
* **releases-widget:** reduced padding for `ul` in release description ([#3085](https://github.com/homarr-labs/homarr/issues/3085)) ([593d97e](https://github.com/homarr-labs/homarr/commit/593d97e27aa85ab023935d12c8cbf534d38c288f))
|
||||
* **releases-widget:** removed version regex in options overview ([#3081](https://github.com/homarr-labs/homarr/issues/3081)) ([59cae02](https://github.com/homarr-labs/homarr/commit/59cae02623b09e60b8c300c18349ea282c872822))
|
||||
* **releases-widget:** validation preventing removal of a previously added icon ([#3086](https://github.com/homarr-labs/homarr/issues/3086)) ([18fe8ec](https://github.com/homarr-labs/homarr/commit/18fe8ecd39f6551135beb0333cbad5b9ff4d6664))
|
||||
* updated contributors contain unnecessary data ([#3124](https://github.com/homarr-labs/homarr/issues/3124)) ([6e03e5a](https://github.com/homarr-labs/homarr/commit/6e03e5a40c9aa0ee5e0e3de4277ffbe44ad45b66))
|
||||
|
||||
## [1.19.1](https://github.com/homarr-labs/homarr/compare/v1.19.0...v1.19.1) (2025-05-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @extractus/feed-extractor to v7.1.5 ([#3020](https://github.com/homarr-labs/homarr/issues/3020)) ([758b1a0](https://github.com/homarr-labs/homarr/commit/758b1a049019d903f152f783eab3b6fc4be4de14))
|
||||
* **deps:** update dependency @kubernetes/client-node to ^1.2.0 ([#3042](https://github.com/homarr-labs/homarr/issues/3042)) ([273d4ed](https://github.com/homarr-labs/homarr/commit/273d4edb233f5d5faa7105c23208d95268119786))
|
||||
* **deps:** update dependency better-sqlite3 to ^11.10.0 ([#3053](https://github.com/homarr-labs/homarr/issues/3053)) ([0d5f430](https://github.com/homarr-labs/homarr/commit/0d5f430bd45e6d29d604d4f93ff57cfc7a64e309))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.1 ([#3013](https://github.com/homarr-labs/homarr/issues/3013)) ([76ce6ff](https://github.com/homarr-labs/homarr/commit/76ce6ff1fab34223e7ff5abf46193f1ae9b011d1))
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.1.3 ([#3047](https://github.com/homarr-labs/homarr/issues/3047)) ([e94c05f](https://github.com/homarr-labs/homarr/commit/e94c05f3d1eea1841b978359d3c41d8a46a08a39))
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.1.5 ([#3061](https://github.com/homarr-labs/homarr/issues/3061)) ([395e03c](https://github.com/homarr-labs/homarr/commit/395e03c8101af873f35388ae1b833dc68d552792))
|
||||
* **deps:** update dependency jotai to ^2.12.4 ([#3038](https://github.com/homarr-labs/homarr/issues/3038)) ([7ec1793](https://github.com/homarr-labs/homarr/commit/7ec179304040d6782e516358aeaae2eaa143dc1a))
|
||||
* **deps:** update dependency ldapts to v8 ([#3028](https://github.com/homarr-labs/homarr/issues/3028)) ([7c0baf5](https://github.com/homarr-labs/homarr/commit/7c0baf5fba19735c2b3fac0728359a9aa8c617e6))
|
||||
* **deps:** update dependency react-error-boundary to v6 ([#3016](https://github.com/homarr-labs/homarr/issues/3016)) ([680f7b0](https://github.com/homarr-labs/homarr/commit/680f7b0c7277ffcbb6a033fd7a2f14146e8a2fca))
|
||||
* **deps:** update dependency typescript-eslint to ^8.32.0 ([#3027](https://github.com/homarr-labs/homarr/issues/3027)) ([bee1ddd](https://github.com/homarr-labs/homarr/commit/bee1dddc9ff1b213ef9fd9cc1696bf22cc816b19))
|
||||
* **deps:** update dependency ws to ^8.18.2 ([#3012](https://github.com/homarr-labs/homarr/issues/3012)) ([9bb26eb](https://github.com/homarr-labs/homarr/commit/9bb26ebcc3a3f400317c85bd1fb13621d52049c2))
|
||||
* **deps:** update dependency zod to ^3.24.4 ([#3021](https://github.com/homarr-labs/homarr/issues/3021)) ([e5e686d](https://github.com/homarr-labs/homarr/commit/e5e686d59086320a565c3367a389d1117fd74d88))
|
||||
* **deps:** update mantine monorepo to v8 (major) ([#3022](https://github.com/homarr-labs/homarr/issues/3022)) ([44853ea](https://github.com/homarr-labs/homarr/commit/44853ea64bed3a093a26cb07904d8beedf220e4e))
|
||||
* **deps:** update nextjs monorepo to v15.3.2 ([#3045](https://github.com/homarr-labs/homarr/issues/3045)) ([b6eb8ca](https://github.com/homarr-labs/homarr/commit/b6eb8ca0f6d0e6b2fac40423a6c0157dff292622))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.2 ([#3018](https://github.com/homarr-labs/homarr/issues/3018)) ([d512fc0](https://github.com/homarr-labs/homarr/commit/d512fc042a0cb06238f3c86f92b12ad152208a9c))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.4 ([#3029](https://github.com/homarr-labs/homarr/issues/3029)) ([861728a](https://github.com/homarr-labs/homarr/commit/861728a7c3156bbe618b9fb186d9f168ba9b339b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.5 ([#3048](https://github.com/homarr-labs/homarr/issues/3048)) ([8cb9ddb](https://github.com/homarr-labs/homarr/commit/8cb9ddbce3f00e6e3df07c796c39fda7dd18b7b1))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.6 ([#3062](https://github.com/homarr-labs/homarr/issues/3062)) ([d6180eb](https://github.com/homarr-labs/homarr/commit/d6180ebc7387249265ad9c296c52aab6d393d88a))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.7 ([#3063](https://github.com/homarr-labs/homarr/issues/3063)) ([b71c5c6](https://github.com/homarr-labs/homarr/commit/b71c5c6fc73cab4ebc1bfe9d70722f166ebcdb47))
|
||||
* **deps:** update tiptap monorepo to v2.12.0 ([#3025](https://github.com/homarr-labs/homarr/issues/3025)) ([8a0557f](https://github.com/homarr-labs/homarr/commit/8a0557f927f6423e37a372c4c4ffea224598ff45))
|
||||
* **deps:** update turbo monorepo to ^2.5.3 ([#3043](https://github.com/homarr-labs/homarr/issues/3043)) ([9b6110b](https://github.com/homarr-labs/homarr/commit/9b6110b9c9ec08bde335cc95d7548e2dddfab45b))
|
||||
* **logs:** log error cause when object, sign in oidc debug messages ([#3054](https://github.com/homarr-labs/homarr/issues/3054)) ([2bdb1c7](https://github.com/homarr-labs/homarr/commit/2bdb1c75f680e7715bbe68c6f50e24ea861302ca))
|
||||
* **releases-widget:** error display, decouple database repository object from responses and batch widget queries ([#2891](https://github.com/homarr-labs/homarr/issues/2891)) ([347c6d1](https://github.com/homarr-labs/homarr/commit/347c6d15191a64899ff844cfe689973f44b9c556))
|
||||
* **releases-widget:** improved provider parsing of missing properties ([#2964](https://github.com/homarr-labs/homarr/issues/2964)) ([b62d47d](https://github.com/homarr-labs/homarr/commit/b62d47d303ca1271b932b6347a489df6f16fc680))
|
||||
|
||||
## [1.19.0](https://github.com/homarr-labs/homarr/compare/v1.18.1...v1.19.0) (2025-05-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **widget:** add prefetch for apps and bookmarks ([#2895](https://github.com/homarr-labs/homarr/issues/2895)) ([82c5361](https://github.com/homarr-labs/homarr/commit/82c53611122dd07e5639a902afa0a6a4fd8ed4e5))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cookie:** parsing of header not working with percentage char ([#2989](https://github.com/homarr-labs/homarr/issues/2989)) ([547a8c3](https://github.com/homarr-labs/homarr/commit/547a8c34c58c88e6f0074df327f49aadf9ab07f4))
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.4 ([#2993](https://github.com/homarr-labs/homarr/issues/2993)) ([1a07fa9](https://github.com/homarr-labs/homarr/commit/1a07fa9af59e93f9ce5d70b72855bb070f5b5787))
|
||||
* **deps:** update dependency tsx to v4.19.4 ([#2981](https://github.com/homarr-labs/homarr/issues/2981)) ([a2dbac5](https://github.com/homarr-labs/homarr/commit/a2dbac5ad73aa50af7f4b40076bb5fcfa61adf80))
|
||||
* **deps:** update dependency typescript-eslint to ^8.31.1 ([#2979](https://github.com/homarr-labs/homarr/issues/2979)) ([284ea23](https://github.com/homarr-labs/homarr/commit/284ea239f71f49c74e3ea60fa5de2fd968f1675b))
|
||||
* **deps:** update mantine monorepo to ^7.17.7 ([#3004](https://github.com/homarr-labs/homarr/issues/3004)) ([a70b0b7](https://github.com/homarr-labs/homarr/commit/a70b0b70479df8fff8a2d256995844bad1fb55a0))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.11 ([#2984](https://github.com/homarr-labs/homarr/issues/2984)) ([3299ced](https://github.com/homarr-labs/homarr/commit/3299cedc04cd87c7f014f69a900e7c6e6c33d954))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.7 ([#2924](https://github.com/homarr-labs/homarr/issues/2924)) ([f4ce76a](https://github.com/homarr-labs/homarr/commit/f4ce76ac13136c2f559d3aff019dcc2add7c98dd))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.9 ([#2983](https://github.com/homarr-labs/homarr/issues/2983)) ([1012cad](https://github.com/homarr-labs/homarr/commit/1012cad0e32f43f46bbb4e89ade3f0eb7fa54313))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.0 ([#2998](https://github.com/homarr-labs/homarr/issues/2998)) ([0d1b40c](https://github.com/homarr-labs/homarr/commit/0d1b40c381fc068c770480f81c6c7dcefce0ec86))
|
||||
* **deps:** update tanstack-query monorepo to ^5.75.1 ([#3006](https://github.com/homarr-labs/homarr/issues/3006)) ([2b1c433](https://github.com/homarr-labs/homarr/commit/2b1c433cefe667d930ce5178cc1218ffb7fa43c0))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.25.0 ([#2997](https://github.com/homarr-labs/homarr/issues/2997)) ([a934529](https://github.com/homarr-labs/homarr/commit/a934529146a0f53a047d2e3e834145c62c8ee891))
|
||||
* **deps:** update tiptap monorepo to v2.11.9 ([#2987](https://github.com/homarr-labs/homarr/issues/2987)) ([70686a0](https://github.com/homarr-labs/homarr/commit/70686a00662a7f690efbb45920e5c45ad4a866ef))
|
||||
* **deps:** update trpc monorepo to ^11.1.2 ([#2982](https://github.com/homarr-labs/homarr/issues/2982)) ([b10f33b](https://github.com/homarr-labs/homarr/commit/b10f33bf0b976217b2076ce84d8d1d9219b36e4c))
|
||||
* **media-request:** add missing completed status ([#2990](https://github.com/homarr-labs/homarr/issues/2990)) ([9006d3d](https://github.com/homarr-labs/homarr/commit/9006d3d2fc21be5ba2eae616e467113356c91ebe))
|
||||
* **releases-widget:** improve ui for smaller widgets ([#2962](https://github.com/homarr-labs/homarr/issues/2962)) ([f39a21c](https://github.com/homarr-labs/homarr/commit/f39a21c2962530298274ee705f3d85da023ea3e0))
|
||||
* **unifi:** port is ignored ([#2995](https://github.com/homarr-labs/homarr/issues/2995)) ([0c9c5b8](https://github.com/homarr-labs/homarr/commit/0c9c5b8955c883191e5b6b137660c1a3cbd16ce4))
|
||||
|
||||
## [1.18.1](https://github.com/homarr-labs/homarr/compare/v1.18.0...v1.18.1) (2025-04-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cannot find module http-cookie-agent/http ([#2972](https://github.com/homarr-labs/homarr/issues/2972)) ([bb92f5b](https://github.com/homarr-labs/homarr/commit/bb92f5bb79e8f236dd149e87d14c1a35955140c7))
|
||||
* **deps:** update dependency mysql2 to v3.14.1 ([#2969](https://github.com/homarr-labs/homarr/issues/2969)) ([5f80beb](https://github.com/homarr-labs/homarr/commit/5f80bebef2bd1598ad02dd1e169a1a6c27a4c143))
|
||||
|
||||
## [1.18.0](https://github.com/homarr-labs/homarr/compare/v1.17.0...v1.18.0) (2025-04-25)
|
||||
|
||||
### Features
|
||||
|
||||
* add releases widget ([#2497](https://github.com/homarr-labs/homarr/issues/2497)) ([3dcee8c](https://github.com/homarr-labs/homarr/commit/3dcee8cb869dd11d54631636253ad17e2af97dd7))
|
||||
* **media-server:** add option to only show playing sessions ([#2899](https://github.com/homarr-labs/homarr/issues/2899)) ([f98750d](https://github.com/homarr-labs/homarr/commit/f98750d0b3e96b234d6f57b82bf7aa8729a091f2))
|
||||
* migrate to unifi package ([#2894](https://github.com/homarr-labs/homarr/issues/2894)) ([8550c69](https://github.com/homarr-labs/homarr/commit/8550c69d51497676b654e204a85cdc84e8a38e30))
|
||||
* **widgets:** add title to advanced options ([#2909](https://github.com/homarr-labs/homarr/issues/2909)) ([c64d903](https://github.com/homarr-labs/homarr/commit/c64d903f2b6375970325fb5031afd468dbf0ae11))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **certificates:** improve validation and prevent crash ([#2910](https://github.com/homarr-labs/homarr/issues/2910)) ([c514247](https://github.com/homarr-labs/homarr/commit/c51424717ddce5a2f4110658a1fbfab375185f66))
|
||||
* **deps:** update dependency @auth/core to ^0.39.0 ([#2904](https://github.com/homarr-labs/homarr/issues/2904)) ([b0d7f9a](https://github.com/homarr-labs/homarr/commit/b0d7f9a48c05d380ff84ed7f48c9a9fd8dc33602))
|
||||
* **deps:** update dependency @auth/drizzle-adapter to ^1.9.0 ([#2905](https://github.com/homarr-labs/homarr/issues/2905)) ([fdf700d](https://github.com/homarr-labs/homarr/commit/fdf700dba51d0bc4e72cdd63a63acb99c2196329))
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.6.0 ([#2914](https://github.com/homarr-labs/homarr/issues/2914)) ([3d50808](https://github.com/homarr-labs/homarr/commit/3d508088d5a7d7fda53f49c196c2c6dd84d598d1))
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.0 ([#2935](https://github.com/homarr-labs/homarr/issues/2935)) ([eb95ee2](https://github.com/homarr-labs/homarr/commit/eb95ee285b727a8bda7ac9df3f75c25ced475358))
|
||||
* **deps:** update dependency dockerode to ^4.0.6 ([#2901](https://github.com/homarr-labs/homarr/issues/2901)) ([593e3ee](https://github.com/homarr-labs/homarr/commit/593e3ee5a6f9cb8d50a51262c85c3c067fce0691))
|
||||
* **deps:** update dependency drizzle-orm to ^0.43.0 ([#2939](https://github.com/homarr-labs/homarr/issues/2939)) ([295cc4a](https://github.com/homarr-labs/homarr/commit/295cc4a3c41ba9ad20e93df9d6ea5627d56d310d))
|
||||
* **deps:** update dependency drizzle-orm to ^0.43.1 ([#2947](https://github.com/homarr-labs/homarr/issues/2947)) ([1d28bf4](https://github.com/homarr-labs/homarr/commit/1d28bf4c93c43c1956c5b386ca8a02ca6ae59eb5))
|
||||
* **deps:** update dependency glob to ^11.0.2 ([#2931](https://github.com/homarr-labs/homarr/issues/2931)) ([573d5f8](https://github.com/homarr-labs/homarr/commit/573d5f89d6576020c3a4393372a56f8f75628e58))
|
||||
* **deps:** update dependency next-auth to v5.0.0-beta.26 ([#2906](https://github.com/homarr-labs/homarr/issues/2906)) ([6e6edc8](https://github.com/homarr-labs/homarr/commit/6e6edc8a0d809614588967b51751257929a6ccd3))
|
||||
* **deps:** update dependency next-auth to v5.0.0-beta.27 ([#2940](https://github.com/homarr-labs/homarr/issues/2940)) ([2533083](https://github.com/homarr-labs/homarr/commit/253308357a55ab2fac7ac4144215beefdc517b8d))
|
||||
* **deps:** update dependency next-intl to v4.0.3 ([#2922](https://github.com/homarr-labs/homarr/issues/2922)) ([3172e6e](https://github.com/homarr-labs/homarr/commit/3172e6e0c4e55f62a7ee32ca84fcaa940e774565))
|
||||
* **deps:** update dependency next-intl to v4.1.0 ([#2938](https://github.com/homarr-labs/homarr/issues/2938)) ([767fe94](https://github.com/homarr-labs/homarr/commit/767fe946d78268eddce6e213665176b8c6397e92))
|
||||
* **deps:** update dependency sass to ^1.87.0 ([#2921](https://github.com/homarr-labs/homarr/issues/2921)) ([fed28bd](https://github.com/homarr-labs/homarr/commit/fed28bd77546be83b9965bd1116996b621f6b639))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.2.0 ([#2903](https://github.com/homarr-labs/homarr/issues/2903)) ([2bf67c3](https://github.com/homarr-labs/homarr/commit/2bf67c3de8609eb504e322d830f507973a55d6fc))
|
||||
* **deps:** update dependency tsdav to ^2.1.4 ([#2944](https://github.com/homarr-labs/homarr/issues/2944)) ([d78b2e1](https://github.com/homarr-labs/homarr/commit/d78b2e1e85a6c72ce1366e50ee4a1e83176b7913))
|
||||
* **deps:** update dependency typescript-eslint to ^8.31.0 ([#2919](https://github.com/homarr-labs/homarr/issues/2919)) ([7aa3790](https://github.com/homarr-labs/homarr/commit/7aa379076da24c90238dd5fe2714dbceb3b84c2a))
|
||||
* **deps:** update mantine monorepo to ^7.17.5 ([#2937](https://github.com/homarr-labs/homarr/issues/2937)) ([aba963c](https://github.com/homarr-labs/homarr/commit/aba963c7cf0d554d51e5fc233e546e4917ecdf67))
|
||||
* **deps:** update trpc monorepo to ^11.1.1 ([#2927](https://github.com/homarr-labs/homarr/issues/2927)) ([bd3535d](https://github.com/homarr-labs/homarr/commit/bd3535dcf84569b87cf647bc5e0b957667b7e199))
|
||||
* **deps:** update turbo monorepo to ^2.5.1 ([#2934](https://github.com/homarr-labs/homarr/issues/2934)) ([7766c3c](https://github.com/homarr-labs/homarr/commit/7766c3cbbc1187f6c38157fe15182c8b7d329acf))
|
||||
* **deps:** update turbo monorepo to ^2.5.2 ([#2949](https://github.com/homarr-labs/homarr/issues/2949)) ([d97e740](https://github.com/homarr-labs/homarr/commit/d97e74047dc107abb05ca65d8a91a41c6071fbd4))
|
||||
|
||||
## [1.17.0](https://github.com/homarr-labs/homarr/compare/v1.16.0...v1.17.0) (2025-04-18)
|
||||
|
||||
### Features
|
||||
|
||||
* add api-key support for tdarr ([#2890](https://github.com/homarr-labs/homarr/issues/2890)) ([3d68f4d](https://github.com/homarr-labs/homarr/commit/3d68f4d1283b6cb27f9532d1adf52cef78a0fb03))
|
||||
* Prefer primary color ([#2665](https://github.com/homarr-labs/homarr/issues/2665)) ([00053bd](https://github.com/homarr-labs/homarr/commit/00053bda7bbcaaf359074c3b35573ee0afc1409d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* calculate available space correctly and filter unmounted drives ([#2885](https://github.com/homarr-labs/homarr/issues/2885)) ([cfca0ba](https://github.com/homarr-labs/homarr/commit/cfca0ba58323ec09f8f64da2fee251ef14ba914a))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.0 ([#2881](https://github.com/homarr-labs/homarr/issues/2881)) ([97390c6](https://github.com/homarr-labs/homarr/commit/97390c60300f3d16dabf0710fa435c6b3ae86a6f))
|
||||
* **deps:** update dependency drizzle-orm to ^0.42.0 ([#2882](https://github.com/homarr-labs/homarr/issues/2882)) ([47d7abc](https://github.com/homarr-labs/homarr/commit/47d7abc0d739b91328834e0eaea033637632f22c))
|
||||
* **deps:** update dependency jotai to ^2.12.3 ([#2868](https://github.com/homarr-labs/homarr/issues/2868)) ([c57b63b](https://github.com/homarr-labs/homarr/commit/c57b63b506733459ea0c09776917042fa878829b))
|
||||
* **deps:** update dependency recharts to ^2.15.3 ([#2893](https://github.com/homarr-labs/homarr/issues/2893)) ([5fcb7a3](https://github.com/homarr-labs/homarr/commit/5fcb7a3b9c279c1b402e120a4a6f08265e8d5912))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.21.0 ([#2866](https://github.com/homarr-labs/homarr/issues/2866)) ([ff1263b](https://github.com/homarr-labs/homarr/commit/ff1263b217e96cbb2bc6b8e98ff117c44ca9f00d))
|
||||
* **deps:** update dependency typescript-eslint to ^8.30.0 ([#2873](https://github.com/homarr-labs/homarr/issues/2873)) ([089734b](https://github.com/homarr-labs/homarr/commit/089734b293a21d6eeaa7c5df5ee9930853ed2fcf))
|
||||
* **deps:** update dependency typescript-eslint to ^8.30.1 ([#2876](https://github.com/homarr-labs/homarr/issues/2876)) ([fdca513](https://github.com/homarr-labs/homarr/commit/fdca513929e9e594af2d59ad17c915cbc7ac2e08))
|
||||
* **deps:** update dependency zod to ^3.24.3 ([#2886](https://github.com/homarr-labs/homarr/issues/2886)) ([1bfe397](https://github.com/homarr-labs/homarr/commit/1bfe397d68a88037e35701aab8a7b4c9027ec1e3))
|
||||
* **deps:** update nextjs monorepo to v15.3.1 ([#2889](https://github.com/homarr-labs/homarr/issues/2889)) ([d37cdbd](https://github.com/homarr-labs/homarr/commit/d37cdbd080c2b3a4df536ab7dad4fc4a4b3c915f))
|
||||
* **deps:** update tanstack-query monorepo to ^5.73.3 ([#2860](https://github.com/homarr-labs/homarr/issues/2860)) ([4e6ab03](https://github.com/homarr-labs/homarr/commit/4e6ab037c9245103b05afe977e9c288082a2b539))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.0 ([#2864](https://github.com/homarr-labs/homarr/issues/2864)) ([ac78e87](https://github.com/homarr-labs/homarr/commit/ac78e8704287f45d6a8ca26cba9b87146e1c6161))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.2 ([#2869](https://github.com/homarr-labs/homarr/issues/2869)) ([bf4a1cf](https://github.com/homarr-labs/homarr/commit/bf4a1cf1e8c99ada97adf6ee118ddaafc00115e9))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.3 ([#2871](https://github.com/homarr-labs/homarr/issues/2871)) ([02fb1b9](https://github.com/homarr-labs/homarr/commit/02fb1b9dae501e475353e8e2510e4f8938c4e124))
|
||||
* **deps:** update tanstack-query monorepo to ^5.74.4 ([#2888](https://github.com/homarr-labs/homarr/issues/2888)) ([17174b6](https://github.com/homarr-labs/homarr/commit/17174b6b96ae16b480e2bc5eb9f8ab7b914bc843))
|
||||
* Tdarr widget empty statistics tab ([#2883](https://github.com/homarr-labs/homarr/issues/2883)) ([2d12c1e](https://github.com/homarr-labs/homarr/commit/2d12c1e641398e20a70f8bfb24a48de02498496a))
|
||||
* **widget:** format stock values and changes for better readability ([#2836](https://github.com/homarr-labs/homarr/issues/2836)) ([a29556e](https://github.com/homarr-labs/homarr/commit/a29556ee8ec50aa384a9a46f6f5a5fa5dfa4cdac))
|
||||
|
||||
## [1.16.0](https://github.com/homarr-labs/homarr/compare/v1.15.0...v1.16.0) (2025-04-11)
|
||||
|
||||
### Features
|
||||
|
||||
* **boards:** add quick app add menu item ([#2681](https://github.com/homarr-labs/homarr/issues/2681)) ([7a3c836](https://github.com/homarr-labs/homarr/commit/7a3c836a70a9b9656c6fe74fe6a4bf8e7166a030))
|
||||
* support aria2 integration ([#2226](https://github.com/homarr-labs/homarr/issues/2226)) ([94263c4](https://github.com/homarr-labs/homarr/commit/94263c445b8a0d0cdbea70ebe45ef932b426204d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **boards:** add missing upload button for background in settings ([#2853](https://github.com/homarr-labs/homarr/issues/2853)) ([4baec7e](https://github.com/homarr-labs/homarr/commit/4baec7e3ff33ac0973daf0ac1f0171e349787bdb))
|
||||
* **deps:** update dependency dotenv to ^16.5.0 ([#2848](https://github.com/homarr-labs/homarr/issues/2848)) ([c84dd2f](https://github.com/homarr-labs/homarr/commit/c84dd2fd797ca674e4f553ddc71926e9b533daa4))
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.1.2 ([#2842](https://github.com/homarr-labs/homarr/issues/2842)) ([924a7d0](https://github.com/homarr-labs/homarr/commit/924a7d076ef8bdd67afcce2e9f850bf334b43c3a))
|
||||
* **deps:** update dependency ioredis to v5.6.1 ([#2851](https://github.com/homarr-labs/homarr/issues/2851)) ([0ed6a79](https://github.com/homarr-labs/homarr/commit/0ed6a79a5bba25fa7a90bec00d3815b3fffae427))
|
||||
* **deps:** update dependency octokit to ^4.1.3 ([#2847](https://github.com/homarr-labs/homarr/issues/2847)) ([4b0b892](https://github.com/homarr-labs/homarr/commit/4b0b89225000137f02f3b89376c46d4d01387128))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.8 ([#2844](https://github.com/homarr-labs/homarr/issues/2844)) ([45d285a](https://github.com/homarr-labs/homarr/commit/45d285a76ba46d4d4c594f1f2e822d784a281b72))
|
||||
* **deps:** update dependency undici to v7.8.0 ([#2841](https://github.com/homarr-labs/homarr/issues/2841)) ([f13be4a](https://github.com/homarr-labs/homarr/commit/f13be4a70870aca99a7f49f5068dadcd7fe01c22))
|
||||
* **deps:** update nextjs monorepo to v15.3.0 ([#2838](https://github.com/homarr-labs/homarr/issues/2838)) ([a1fa429](https://github.com/homarr-labs/homarr/commit/a1fa4297ead54a9f8c842b2d23001f725359e000))
|
||||
* **deps:** update tanstack-query monorepo to ^5.72.2 ([#2834](https://github.com/homarr-labs/homarr/issues/2834)) ([f5e3138](https://github.com/homarr-labs/homarr/commit/f5e3138c8118295d8230c3655af1c3f11ee5d4ff))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.24.2 ([#2846](https://github.com/homarr-labs/homarr/issues/2846)) ([057ff2c](https://github.com/homarr-labs/homarr/commit/057ff2cfc1cf062a3c48deb28699df8e1ba89f75))
|
||||
* **deps:** update trpc monorepo to ^11.1.0 ([#2850](https://github.com/homarr-labs/homarr/issues/2850)) ([0fe4ea9](https://github.com/homarr-labs/homarr/commit/0fe4ea9dd9cbfb2def1bede9db8eaf649f79f1b0))
|
||||
|
||||
## [1.15.0](https://github.com/homarr-labs/homarr/compare/v1.14.0...v1.15.0) (2025-04-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **boards:** add upload buttons for background, favicon and logo ([#2770](https://github.com/homarr-labs/homarr/issues/2770)) ([e07b9e6](https://github.com/homarr-labs/homarr/commit/e07b9e6a88ff0aad0aa5dae2f32f648ad5f0c0a8))
|
||||
* unifi controller integration ([#2236](https://github.com/homarr-labs/homarr/issues/2236)) ([c1cd563](https://github.com/homarr-labs/homarr/commit/c1cd56304872b5f7b2b49624c6f5018f750d126b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @kubernetes/client-node to ^1.1.2 ([#2825](https://github.com/homarr-labs/homarr/issues/2825)) ([b064304](https://github.com/homarr-labs/homarr/commit/b064304201ed2ce52162edd5bc9ab5131da7ac97))
|
||||
* **deps:** update dependency ldapts to v7.4.0 ([#2815](https://github.com/homarr-labs/homarr/issues/2815)) ([275224e](https://github.com/homarr-labs/homarr/commit/275224ee68bb49f329a6765095b46ec249027aca))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.7 ([#2816](https://github.com/homarr-labs/homarr/issues/2816)) ([85f8d23](https://github.com/homarr-labs/homarr/commit/85f8d23da3f3ec17945d79ef8a5212c5ceeefe75))
|
||||
* **deps:** update dependency typescript-eslint to ^8.29.1 ([#2821](https://github.com/homarr-labs/homarr/issues/2821)) ([17d9272](https://github.com/homarr-labs/homarr/commit/17d92727f24cf0e2491dc93fd7c72759a24bbd54))
|
||||
* **deps:** update mantine monorepo to ^7.17.4 ([#2824](https://github.com/homarr-labs/homarr/issues/2824)) ([bd5dcb9](https://github.com/homarr-labs/homarr/commit/bd5dcb991c8bd476cefbae1b8b2f407dd8db1acb))
|
||||
* **deps:** update nextjs monorepo to v15.2.5 ([#2828](https://github.com/homarr-labs/homarr/issues/2828)) ([0fc7ca7](https://github.com/homarr-labs/homarr/commit/0fc7ca7738aee2d86940f43e3593368d4102cc73))
|
||||
* **deps:** update tanstack-query monorepo to ^5.72.0 ([#2817](https://github.com/homarr-labs/homarr/issues/2817)) ([df212cf](https://github.com/homarr-labs/homarr/commit/df212cf5114a970ad4aed84811cfc7a2e4be67b3))
|
||||
* **deps:** update tanstack-query monorepo to ^5.72.1 ([#2823](https://github.com/homarr-labs/homarr/issues/2823)) ([c01d9a4](https://github.com/homarr-labs/homarr/commit/c01d9a4dc5aba540a2b333a94507d027e5fb8d99))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.24.1 ([#2831](https://github.com/homarr-labs/homarr/issues/2831)) ([092c95b](https://github.com/homarr-labs/homarr/commit/092c95bbe0be73ad3dedb13c7f84ba7c36ca14db))
|
||||
* **deps:** update trpc monorepo to ^11.0.3 ([#2822](https://github.com/homarr-labs/homarr/issues/2822)) ([a821bd7](https://github.com/homarr-labs/homarr/commit/a821bd71f47e03a276a96af705bec90a00229e0f))
|
||||
* **deps:** update trpc monorepo to ^11.0.4 ([#2827](https://github.com/homarr-labs/homarr/issues/2827)) ([75100d1](https://github.com/homarr-labs/homarr/commit/75100d1a4926b838682206189eac046d844dba13))
|
||||
* **medias:** upload size limited by nginx to 1mb ([#2813](https://github.com/homarr-labs/homarr/issues/2813)) ([bf046a7](https://github.com/homarr-labs/homarr/commit/bf046a715cd6897792b2f1582d076c055063bf57))
|
||||
* **performance:** remove turbopack flag to no longer go out of memory after ([#2803](https://github.com/homarr-labs/homarr/issues/2803)) ([c6102ed](https://github.com/homarr-labs/homarr/commit/c6102edff467082803769c488ce642500edcdd25))
|
||||
* **security:** missing authorization check for changes to notebook widget ([#2832](https://github.com/homarr-labs/homarr/issues/2832)) ([3948f7f](https://github.com/homarr-labs/homarr/commit/3948f7f9a47cd4562c9b4fd8252f116b8a95c982))
|
||||
|
||||
## [1.14.0](https://github.com/homarr-labs/homarr/compare/v1.13.1...v1.14.0) (2025-04-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add cli command to recreate credentials admin ([#2779](https://github.com/homarr-labs/homarr/issues/2779)) ([6742532](https://github.com/homarr-labs/homarr/commit/674253262bd4853ebde85d229d666a38733a7026))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.5.2 ([#2795](https://github.com/homarr-labs/homarr/issues/2795)) ([ea8bf43](https://github.com/homarr-labs/homarr/commit/ea8bf43d7b56f526fbf2d4a3955c2c179e4e74d8))
|
||||
* **deps:** update dependency @kubernetes/client-node to ^1.1.1 ([#2793](https://github.com/homarr-labs/homarr/issues/2793)) ([76b8b35](https://github.com/homarr-labs/homarr/commit/76b8b35cc1f586797210bdcd3d848d64b5f36d63))
|
||||
* **deps:** update dependency dockerode to ^4.0.5 ([#2757](https://github.com/homarr-labs/homarr/issues/2757)) ([db13fd0](https://github.com/homarr-labs/homarr/commit/db13fd03c018cb2f79f0b094b93b69d69fde97ff))
|
||||
* **deps:** update dependency eslint-plugin-react to ^7.37.5 ([#2794](https://github.com/homarr-labs/homarr/issues/2794)) ([f9aeb8f](https://github.com/homarr-labs/homarr/commit/f9aeb8ffe3ff4cd65cd76c560a687912262a735f))
|
||||
* **deps:** update dependency recharts to ^2.15.2 ([#2785](https://github.com/homarr-labs/homarr/issues/2785)) ([f33b1cb](https://github.com/homarr-labs/homarr/commit/f33b1cb5d0c317df5348e87e0f5f43fdc6044615))
|
||||
* **deps:** update dependency sass to ^1.86.1 ([#2762](https://github.com/homarr-labs/homarr/issues/2762)) ([3e35051](https://github.com/homarr-labs/homarr/commit/3e3505176a7df01bae5173d7f62380fa17859e8f))
|
||||
* **deps:** update dependency sass to ^1.86.2 ([#2784](https://github.com/homarr-labs/homarr/issues/2784)) ([3131082](https://github.com/homarr-labs/homarr/commit/3131082c778191be5568a93bbf5f10219f3c2be9))
|
||||
* **deps:** update dependency sass to ^1.86.3 ([#2796](https://github.com/homarr-labs/homarr/issues/2796)) ([98ecc68](https://github.com/homarr-labs/homarr/commit/98ecc683e563475644530682f08d335ac3671a5e))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.3 ([#2777](https://github.com/homarr-labs/homarr/issues/2777)) ([81d1231](https://github.com/homarr-labs/homarr/commit/81d1231d8fc026dd9f1ba58e28937aba9d94d88e))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.4 ([#2786](https://github.com/homarr-labs/homarr/issues/2786)) ([85672d9](https://github.com/homarr-labs/homarr/commit/85672d9cf15bf48c9cc2fb4e746f65814f46361b))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.5 ([#2787](https://github.com/homarr-labs/homarr/issues/2787)) ([3363eeb](https://github.com/homarr-labs/homarr/commit/3363eebdddf10a028d1cdebcb4d581105ba1d63c))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.6 ([#2798](https://github.com/homarr-labs/homarr/issues/2798)) ([968a82b](https://github.com/homarr-labs/homarr/commit/968a82baf5d6adc64c680508493b77b892fdd5fc))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.1.4 ([#2748](https://github.com/homarr-labs/homarr/issues/2748)) ([c69e331](https://github.com/homarr-labs/homarr/commit/c69e331ded3cfde837dcf28c8b1ba4a9b7a3c8aa))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.1.5 ([#2799](https://github.com/homarr-labs/homarr/issues/2799)) ([b03941e](https://github.com/homarr-labs/homarr/commit/b03941ed80057154f0b4b04c764652ada1fdf18c))
|
||||
* **deps:** update dependency typescript-eslint to ^8.29.0 ([#2761](https://github.com/homarr-labs/homarr/issues/2761)) ([3687c4b](https://github.com/homarr-labs/homarr/commit/3687c4b3d655d9cebb85fb3c3ad80f494e9a8f0a))
|
||||
* **deps:** update dependency undici to v7.7.0 ([#2776](https://github.com/homarr-labs/homarr/issues/2776)) ([ff5c313](https://github.com/homarr-labs/homarr/commit/ff5c31342fe9eb668533d7d9f0d65ad3f01eba0a))
|
||||
* **deps:** update tanstack-query monorepo to ^5.71.0 ([#2752](https://github.com/homarr-labs/homarr/issues/2752)) ([77044e8](https://github.com/homarr-labs/homarr/commit/77044e8e0b8e7267b8547eb1d0f86026948b292a))
|
||||
* **deps:** update tanstack-query monorepo to ^5.71.1 ([#2756](https://github.com/homarr-labs/homarr/issues/2756)) ([0b8a009](https://github.com/homarr-labs/homarr/commit/0b8a0098bd02a71107af9702e39000227028ec20))
|
||||
* **deps:** update tanstack-query monorepo to ^5.71.10 ([#2790](https://github.com/homarr-labs/homarr/issues/2790)) ([964368a](https://github.com/homarr-labs/homarr/commit/964368a06c8385df63a252e2d0759a2524348ac6))
|
||||
* **deps:** update tanstack-query monorepo to ^5.71.3 ([#2767](https://github.com/homarr-labs/homarr/issues/2767)) ([858fe6e](https://github.com/homarr-labs/homarr/commit/858fe6e19a682bb0fa0cfa0d11c2423447fdad2f))
|
||||
* **deps:** update tanstack-query monorepo to ^5.71.5 ([#2780](https://github.com/homarr-labs/homarr/issues/2780)) ([466d0e0](https://github.com/homarr-labs/homarr/commit/466d0e00530903ab4101cd2e94c00ab8a7c1c8ce))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.24.0 ([#2789](https://github.com/homarr-labs/homarr/issues/2789)) ([1b05041](https://github.com/homarr-labs/homarr/commit/1b0504198b41032319ead2604721abaaf08709d8))
|
||||
* **deps:** update tiptap monorepo to v2.11.7 ([#2749](https://github.com/homarr-labs/homarr/issues/2749)) ([0ec8f23](https://github.com/homarr-labs/homarr/commit/0ec8f23b7c655889d8f6fb9fcfd66a9736eb5a10))
|
||||
* **deps:** update tiptap monorepo to v2.11.7 ([#2750](https://github.com/homarr-labs/homarr/issues/2750)) ([59b1630](https://github.com/homarr-labs/homarr/commit/59b16305326ac811ad91e89c0786b9e6b1b7e1df))
|
||||
* **deps:** update trpc monorepo to ^11.0.2 ([#2792](https://github.com/homarr-labs/homarr/issues/2792)) ([8886380](https://github.com/homarr-labs/homarr/commit/8886380f0a9ea7d35cd54182a378b2f47b71c283))
|
||||
* **deps:** update turbo monorepo to ^2.5.0 ([#2791](https://github.com/homarr-labs/homarr/issues/2791)) ([0b41967](https://github.com/homarr-labs/homarr/commit/0b41967ec366d925dbb172d38c3677d88f51f0a9))
|
||||
* **nextcloud:** issue with self signed certificates ([#2760](https://github.com/homarr-labs/homarr/issues/2760)) ([7f77130](https://github.com/homarr-labs/homarr/commit/7f77130978eb7f25e571b7315250c91656a85d78))
|
||||
* update check blocks page loading ([#2782](https://github.com/homarr-labs/homarr/issues/2782)) ([29ff879](https://github.com/homarr-labs/homarr/commit/29ff879dfd1036d44b7c4735a0b269a83173b4d1))
|
||||
|
||||
### Reverts
|
||||
|
||||
* add restriction callback to restrict visibility and modification of widget kinds ([#2746](https://github.com/homarr-labs/homarr/issues/2746)) ([9851531](https://github.com/homarr-labs/homarr/commit/98515312a2439d1ed9fc5b412843ca625120b2ac))
|
||||
|
||||
## [1.13.1](https://github.com/homarr-labs/homarr/compare/v1.13.0...v1.13.1) (2025-03-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update react monorepo to v19.1.0 ([#2734](https://github.com/homarr-labs/homarr/issues/2734)) ([94ff401](https://github.com/homarr-labs/homarr/commit/94ff401d7dface924f48affccbc59e3a98ed8dc1))
|
||||
|
||||
### Reverts
|
||||
|
||||
* ssg integration create ([#2736](https://github.com/homarr-labs/homarr/issues/2736)) ([fef50a5](https://github.com/homarr-labs/homarr/commit/fef50a53b1fb592668dfa9aade6b110e42aec9a8))
|
||||
|
||||
## [1.13.0](https://github.com/homarr-labs/homarr/compare/v1.12.0...v1.13.0) (2025-03-28)
|
||||
|
||||
### Features
|
||||
|
||||
* add default search engines seeding / set homarr docs as global server search engine ([#2663](https://github.com/homarr-labs/homarr/issues/2663)) ([3e1c000](https://github.com/homarr-labs/homarr/commit/3e1c000d515c0edc6f49241fc381187e1829ca7e))
|
||||
* add german swiss locale ([#2726](https://github.com/homarr-labs/homarr/issues/2726)) ([eba738e](https://github.com/homarr-labs/homarr/commit/eba738e5d6594d46b5b7d1eab875fc8060e0fe7b))
|
||||
* add jetbrains run profile ([#2704](https://github.com/homarr-labs/homarr/issues/2704)) ([07f0abf](https://github.com/homarr-labs/homarr/commit/07f0abf28cb31f81860e623c70970e16d17e2836))
|
||||
* add title to dynamic section ([#2614](https://github.com/homarr-labs/homarr/issues/2614)) ([1dd7b1a](https://github.com/homarr-labs/homarr/commit/1dd7b1a6b97f75bca0d1a5c0dbb4254aa4bbd418))
|
||||
* app dropdown add infinity scroll ([#2696](https://github.com/homarr-labs/homarr/issues/2696)) ([8bf893b](https://github.com/homarr-labs/homarr/commit/8bf893b39292f34f5c697c09ba2c9e53c8a6ea90))
|
||||
* **auth:** add env variable to force user-info usage instead of idtoken ([#2711](https://github.com/homarr-labs/homarr/issues/2711)) ([94d3dad](https://github.com/homarr-labs/homarr/commit/94d3dad909c39cec096ff61c0a2f45e8853b6111))
|
||||
* auto sign-in when creating the initial user ([#2664](https://github.com/homarr-labs/homarr/issues/2664)) ([46c95e0](https://github.com/homarr-labs/homarr/commit/46c95e0da3ad27eb072efc45250abb0baf3e2483))
|
||||
* implement auto-select icon ([#2679](https://github.com/homarr-labs/homarr/issues/2679)) ([1a3a559](https://github.com/homarr-labs/homarr/commit/1a3a55934d8b3e4703e0ce44d9d26ad7ff100aca))
|
||||
* **lang:** add english uk language ([#2715](https://github.com/homarr-labs/homarr/issues/2715)) ([93c64ba](https://github.com/homarr-labs/homarr/commit/93c64baec294c39cd490a85f8b06b57d53fc82b3))
|
||||
* **logs:** improve logs by logging errors with causes and metadata ([#2703](https://github.com/homarr-labs/homarr/issues/2703)) ([579dd57](https://github.com/homarr-labs/homarr/commit/579dd5763d139e1644c2e192620728afa54c96d2))
|
||||
* set home board when creating first board ([#2683](https://github.com/homarr-labs/homarr/issues/2683)) ([8f7f211](https://github.com/homarr-labs/homarr/commit/8f7f2110ec29db12ea2d83cd779ec86851209188))
|
||||
* SSG integration creation page ([#2684](https://github.com/homarr-labs/homarr/issues/2684)) ([ce0705e](https://github.com/homarr-labs/homarr/commit/ce0705e9d4a502273a0d84e4e14d61a061fd21f9))
|
||||
* **widget:** add restriction callback to restrict visibility and modification of widget kinds ([#2658](https://github.com/homarr-labs/homarr/issues/2658)) ([84f73d3](https://github.com/homarr-labs/homarr/commit/84f73d33a061a1d243803078b245a8016a615af9))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow deletion of external users ([#2725](https://github.com/homarr-labs/homarr/issues/2725)) ([40e0098](https://github.com/homarr-labs/homarr/commit/40e00987cb6f4ee58fdce1da9489eedd92b8843d))
|
||||
* **auth:** fusion auth returns www-authenticate token response ([#2719](https://github.com/homarr-labs/homarr/issues/2719)) ([b5b0b06](https://github.com/homarr-labs/homarr/commit/b5b0b069d2029110d5618a29af5ae003030584d9))
|
||||
* bookmarks layout ([#2675](https://github.com/homarr-labs/homarr/issues/2675)) ([8a071f9](https://github.com/homarr-labs/homarr/commit/8a071f9ab6bc4c85073dc52cc2e60da69f30950a))
|
||||
* calendar indicators ([#2712](https://github.com/homarr-labs/homarr/issues/2712)) ([70ea093](https://github.com/homarr-labs/homarr/commit/70ea093eb6530d25d3e60e6d496651e3fc457826))
|
||||
* **deps:** update dependency drizzle-kit to ^0.30.6 ([#2706](https://github.com/homarr-labs/homarr/issues/2706)) ([f03e381](https://github.com/homarr-labs/homarr/commit/f03e381aea414c5fb770c7ba092b31f9c53eee11))
|
||||
* **deps:** update dependency drizzle-zod to ^0.7.1 ([#2717](https://github.com/homarr-labs/homarr/issues/2717)) ([569ec6f](https://github.com/homarr-labs/homarr/commit/569ec6fe5ccec980351162024170fabe06dcc3da))
|
||||
* **deps:** update dependency ldapts to v7.3.3 ([#2691](https://github.com/homarr-labs/homarr/issues/2691)) ([00f5a87](https://github.com/homarr-labs/homarr/commit/00f5a877a6f09037eff4c9934fc273c4d2af0f6c))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.2 ([#2708](https://github.com/homarr-labs/homarr/issues/2708)) ([68407ea](https://github.com/homarr-labs/homarr/commit/68407eaa195894db37018cefeeca4ae2da085847))
|
||||
* **deps:** update dependency typescript-eslint to ^8.28.0 ([#2693](https://github.com/homarr-labs/homarr/issues/2693)) ([f624611](https://github.com/homarr-labs/homarr/commit/f6246115405cdc85948059170742b0bae58ab337))
|
||||
* **deps:** update dependency undici to v7.6.0 ([#2710](https://github.com/homarr-labs/homarr/issues/2710)) ([43b8cfa](https://github.com/homarr-labs/homarr/commit/43b8cfa4dd250013fdf15d509a072ba0984f9ab1))
|
||||
* **deps:** update mantine monorepo to ^7.17.3 ([#2707](https://github.com/homarr-labs/homarr/issues/2707)) ([6854e47](https://github.com/homarr-labs/homarr/commit/6854e4729d3ba4a82a318813fec0a20eccdbed18))
|
||||
* **deps:** update nextjs monorepo to v15.2.4 ([#2720](https://github.com/homarr-labs/homarr/issues/2720)) ([ac77bdd](https://github.com/homarr-labs/homarr/commit/ac77bdd84cba47a43d1952725a9c7feccd972766))
|
||||
* **deps:** update tanstack-query monorepo to ^5.69.2 ([#2714](https://github.com/homarr-labs/homarr/issues/2714)) ([78b5520](https://github.com/homarr-labs/homarr/commit/78b55202e7f4739933768d27211556f927b04fed))
|
||||
* **deps:** update tanstack-query monorepo to ^5.69.3 ([#2716](https://github.com/homarr-labs/homarr/issues/2716)) ([e7e80db](https://github.com/homarr-labs/homarr/commit/e7e80dbc7d62830ce1ce150b6e22a67220da8be8))
|
||||
* **deps:** update tanstack-query monorepo to ^5.70.0 ([#2722](https://github.com/homarr-labs/homarr/issues/2722)) ([34eb9b3](https://github.com/homarr-labs/homarr/commit/34eb9b366fdc58c9fc03d18ac7dd68bff1ea83e6))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.22.0 ([#2692](https://github.com/homarr-labs/homarr/issues/2692)) ([b7a6d95](https://github.com/homarr-labs/homarr/commit/b7a6d950db557ea9deadc0be014b5f218611ac83))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.23.0 ([#2699](https://github.com/homarr-labs/homarr/issues/2699)) ([c3264b2](https://github.com/homarr-labs/homarr/commit/c3264b2fe10ad374e92eb16921d43427d7ca866b))
|
||||
* **deps:** update tiptap monorepo to v2.11.6 ([#2718](https://github.com/homarr-labs/homarr/issues/2718)) ([0ed4c74](https://github.com/homarr-labs/homarr/commit/0ed4c741c59c3e498aa247adc292bdbbde1c59d3))
|
||||
* **deps:** update trpc monorepo to ^11.0.1 ([#2724](https://github.com/homarr-labs/homarr/issues/2724)) ([2178185](https://github.com/homarr-labs/homarr/commit/217818573e8bd7d49a821afdf269cd8b3f3d442d))
|
||||
* **import:** autofix missing shapes for sidebars and some sections as well ([#2723](https://github.com/homarr-labs/homarr/issues/2723)) ([62c7955](https://github.com/homarr-labs/homarr/commit/62c7955e48ca62b4bf1baf26ac6a12847795ac18))
|
||||
* Media request via search engine ([#2677](https://github.com/homarr-labs/homarr/issues/2677)) ([5a8405a](https://github.com/homarr-labs/homarr/commit/5a8405a9a81257b023c0265309f7f530b2e23c7a))
|
||||
|
||||
### Reverts
|
||||
|
||||
* add development package and update workspace ([#2700](https://github.com/homarr-labs/homarr/issues/2700))" ([#2728](https://github.com/homarr-labs/homarr/issues/2728)) ([996ec54](https://github.com/homarr-labs/homarr/commit/996ec548c4b9c8561919409b81adc90b0a24fbd6))
|
||||
|
||||
## [1.12.0](https://github.com/homarr-labs/homarr/compare/v1.11.0...v1.12.0) (2025-03-21)
|
||||
|
||||
### Features
|
||||
|
||||
* stock price widget ([#2579](https://github.com/homarr-labs/homarr/issues/2579)) ([91a69c1](https://github.com/homarr-labs/homarr/commit/91a69c162a51cff8899eaa7d5a113f6ec8feafba))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** fix-usernames command only fixes lowercase usernames ([#2650](https://github.com/homarr-labs/homarr/issues/2650)) ([b0381cb](https://github.com/homarr-labs/homarr/commit/b0381cb8836b8477d0990c264aa399cb06b85237))
|
||||
* **deps:** update dependency better-sqlite3 to ^11.9.1 ([#2632](https://github.com/homarr-labs/homarr/issues/2632)) ([289c7f1](https://github.com/homarr-labs/homarr/commit/289c7f1a60477024207261d486c2a88718320df7))
|
||||
* **deps:** update dependency drizzle-orm to ^0.40.1 ([#2624](https://github.com/homarr-labs/homarr/issues/2624)) ([7ba2067](https://github.com/homarr-labs/homarr/commit/7ba2067689ed7fd3ab78e75a6ba385483a97b0a3))
|
||||
* **deps:** update dependency drizzle-orm to ^0.41.0 ([#2666](https://github.com/homarr-labs/homarr/issues/2666)) ([8dee8f8](https://github.com/homarr-labs/homarr/commit/8dee8f8680f2282d76f6e727123220a60e2b596d))
|
||||
* **deps:** update dependency mysql2 to v3.14.0 ([#2652](https://github.com/homarr-labs/homarr/issues/2652)) ([3e4d24b](https://github.com/homarr-labs/homarr/commit/3e4d24b28e998af80c00c8ad141660d4bfe5ef33))
|
||||
* **deps:** update dependency sass to ^1.86.0 ([#2629](https://github.com/homarr-labs/homarr/issues/2629)) ([c9fecaa](https://github.com/homarr-labs/homarr/commit/c9fecaabb6416db5c55d9066ed782712cc96faa0))
|
||||
* **deps:** update dependency typescript-eslint to ^8.27.0 ([#2653](https://github.com/homarr-labs/homarr/issues/2653)) ([e3fcfbe](https://github.com/homarr-labs/homarr/commit/e3fcfbe9166834e9443c5ead21c2f67b2d01ad2e))
|
||||
* **deps:** update tanstack-query monorepo to ^5.69.0 ([#2634](https://github.com/homarr-labs/homarr/issues/2634)) ([2d2ae5a](https://github.com/homarr-labs/homarr/commit/2d2ae5a4191085404f8f7f790a4f0f62b86b408d))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.21.0 ([#2633](https://github.com/homarr-labs/homarr/issues/2633)) ([1519789](https://github.com/homarr-labs/homarr/commit/15197897ac3e2f3ed1dcc24c5f64748eaa52f98f))
|
||||
* **permissions:** unable to edit board with full board permissions ([#2625](https://github.com/homarr-labs/homarr/issues/2625)) ([4cc3739](https://github.com/homarr-labs/homarr/commit/4cc37397c2e56a97e6b1173735da78824c7b15e5))
|
||||
* prohibit dot in board name ([#2643](https://github.com/homarr-labs/homarr/issues/2643)) ([bb8cb6d](https://github.com/homarr-labs/homarr/commit/bb8cb6ddeda6906b3c43270ec9dd8ec66d27f0cd))
|
||||
|
||||
## [1.11.0](https://github.com/homarr-labs/homarr/compare/v1.10.0...v1.11.0) (2025-03-14)
|
||||
|
||||
### Features
|
||||
|
||||
* add nextcloud integration ([#2501](https://github.com/homarr-labs/homarr/issues/2501)) ([2e62a61](https://github.com/homarr-labs/homarr/commit/2e62a61f4d20241fb77065faa00b14d427d4f4e0))
|
||||
* **calendar:** add show-unmonitored option ([#2547](https://github.com/homarr-labs/homarr/issues/2547)) ([f6f0d7c](https://github.com/homarr-labs/homarr/commit/f6f0d7c72bd4b91949f957e5e28ff60e6c82d2a5))
|
||||
* **integrations:** add checkbox to add app ([#2463](https://github.com/homarr-labs/homarr/issues/2463)) ([22f9166](https://github.com/homarr-labs/homarr/commit/22f91666908c6a140fadd89f52f57a57a51cd935))
|
||||
* **kubernetes:** add kubernetes tool ([#1929](https://github.com/homarr-labs/homarr/issues/1929)) ([f19aa29](https://github.com/homarr-labs/homarr/commit/f19aa29607640578f61d1842d4300cc0c0618896))
|
||||
* **logs:** improve error logs with stacked causes ([#2574](https://github.com/homarr-labs/homarr/issues/2574)) ([3d4d32f](https://github.com/homarr-labs/homarr/commit/3d4d32f3773f006edc1d441ce419d7b97c26b526))
|
||||
* **widgets:** add border color option to advanced-options ([#2512](https://github.com/homarr-labs/homarr/issues/2512)) ([b3464b3](https://github.com/homarr-labs/homarr/commit/b3464b3340cf57b4f85da0dd2e1bd8e754f1bec8))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* action icons not being rendered ([#2593](https://github.com/homarr-labs/homarr/issues/2593)) ([85d2d61](https://github.com/homarr-labs/homarr/commit/85d2d6118495f3b466a1f014b90446d03c5ffdcb))
|
||||
* **app:** improve responsive styles ([#2537](https://github.com/homarr-labs/homarr/issues/2537)) ([fb800a0](https://github.com/homarr-labs/homarr/commit/fb800a00084833e785deecdb3b6fde4e35ddf7b8))
|
||||
* **bookmarks:** improve responsive styles ([#2536](https://github.com/homarr-labs/homarr/issues/2536)) ([df249a0](https://github.com/homarr-labs/homarr/commit/df249a0173c3a70711c99df3f7983729a64eaff9))
|
||||
* **calendar:** dropdown spans over whole screen ([#2569](https://github.com/homarr-labs/homarr/issues/2569)) ([fbbfe9a](https://github.com/homarr-labs/homarr/commit/fbbfe9af293961afcb686f3e7a559b67b37f6743))
|
||||
* **calendar:** improve responsive styles ([#2538](https://github.com/homarr-labs/homarr/issues/2538)) ([1db9c3c](https://github.com/homarr-labs/homarr/commit/1db9c3c6925f0baae963c5f079037c2bcd1cee44))
|
||||
* **date-time:** improve responsive styles ([#2539](https://github.com/homarr-labs/homarr/issues/2539)) ([fd5b1b0](https://github.com/homarr-labs/homarr/commit/fd5b1b0c082ee440c6c5137629ef1ec4eead23eb))
|
||||
* **deps:** update dependency @kubernetes/client-node to ^1.1.0 ([#2591](https://github.com/homarr-labs/homarr/issues/2591)) ([8a02c65](https://github.com/homarr-labs/homarr/commit/8a02c656488bf0f239b8f394d73ed63ade8f61ed))
|
||||
* **deps:** update dependency better-sqlite3 to ^11.9.0 ([#2598](https://github.com/homarr-labs/homarr/issues/2598)) ([04c17d1](https://github.com/homarr-labs/homarr/commit/04c17d1c41f0c44e88e39cdfe116972da32624b4))
|
||||
* **deps:** update dependency jotai to ^2.12.2 ([#2560](https://github.com/homarr-labs/homarr/issues/2560)) ([ff6ca86](https://github.com/homarr-labs/homarr/commit/ff6ca86a78ccec4a2dee3d08c1420948c4de28ee))
|
||||
* **deps:** update dependency next-intl to v4 ([#2580](https://github.com/homarr-labs/homarr/issues/2580)) ([f55d8a9](https://github.com/homarr-labs/homarr/commit/f55d8a9c2ebbc612775d66b1f073605fb9e151f3))
|
||||
* **deps:** update dependency next-intl to v4.0.1 ([#2585](https://github.com/homarr-labs/homarr/issues/2585)) ([d8247f2](https://github.com/homarr-labs/homarr/commit/d8247f2dc9a4aaa45d8e193db04295f51c44d4aa))
|
||||
* **deps:** update dependency next-intl to v4.0.2 ([#2589](https://github.com/homarr-labs/homarr/issues/2589)) ([b607c57](https://github.com/homarr-labs/homarr/commit/b607c5702fd2bf86be8b56d7b7b00435363095e0))
|
||||
* **deps:** update dependency prismjs to ^1.30.0 ([#2562](https://github.com/homarr-labs/homarr/issues/2562)) ([8cb8eaf](https://github.com/homarr-labs/homarr/commit/8cb8eaf9cc2aaa842869c6be75e9208b9a19fd77))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.1 ([#2558](https://github.com/homarr-labs/homarr/issues/2558)) ([9c582d9](https://github.com/homarr-labs/homarr/commit/9c582d906bb2702de5db282594293e51e94ca98d))
|
||||
* **deps:** update dependency typescript-eslint to ^8.26.1 ([#2561](https://github.com/homarr-labs/homarr/issues/2561)) ([55ad0b0](https://github.com/homarr-labs/homarr/commit/55ad0b0a6533df44e7d1f6eecf33135654cea104))
|
||||
* **deps:** update dependency undici to v7.5.0 ([#2583](https://github.com/homarr-labs/homarr/issues/2583)) ([56fe8dd](https://github.com/homarr-labs/homarr/commit/56fe8dd9e9ff14dc0c10a93f6798df11c2ef5761))
|
||||
* **deps:** update dependency video.js to ^8.22.0 ([#2564](https://github.com/homarr-labs/homarr/issues/2564)) ([a102f39](https://github.com/homarr-labs/homarr/commit/a102f39518722d898d75d978df91ae2a7f8da3e9))
|
||||
* **deps:** update dependency zod-form-data to ^2.0.6 ([#2572](https://github.com/homarr-labs/homarr/issues/2572)) ([2135f99](https://github.com/homarr-labs/homarr/commit/2135f9980b4eb9e682fd19d2a1536f50201a1bee))
|
||||
* **deps:** update dependency zod-form-data to ^2.0.7 ([#2573](https://github.com/homarr-labs/homarr/issues/2573)) ([6c9fbd6](https://github.com/homarr-labs/homarr/commit/6c9fbd6c09b481b24dad849cc8d367d0c5591cdb))
|
||||
* **deps:** update mantine monorepo to ^7.17.2 ([#2599](https://github.com/homarr-labs/homarr/issues/2599)) ([1e50ac4](https://github.com/homarr-labs/homarr/commit/1e50ac411c9e416de4225881b631e4cc497e7b0d))
|
||||
* **deps:** update tanstack-query monorepo to ^5.67.3 ([#2567](https://github.com/homarr-labs/homarr/issues/2567)) ([8486073](https://github.com/homarr-labs/homarr/commit/8486073f271c69f767d9cc76fdd3f27cf1f9db20))
|
||||
* **deps:** update tanstack-query monorepo to ^5.68.0 ([#2600](https://github.com/homarr-labs/homarr/issues/2600)) ([25f7406](https://github.com/homarr-labs/homarr/commit/25f7406283e2999b85fdc21953e0e6c572d8f23b))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.19.0 ([#2576](https://github.com/homarr-labs/homarr/issues/2576)) ([d669f99](https://github.com/homarr-labs/homarr/commit/d669f99ac161697d6c3db81c51b7bf25532fbbfd))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.20.0 ([#2602](https://github.com/homarr-labs/homarr/issues/2602)) ([75fb9db](https://github.com/homarr-labs/homarr/commit/75fb9dbf1e7c2137d1dd98bc2952ddb28fc0375f))
|
||||
* **dns-controls:** improve responsive styles ([#2534](https://github.com/homarr-labs/homarr/issues/2534)) ([c0e1355](https://github.com/homarr-labs/homarr/commit/c0e1355d7f995e79eec23680568f1830e5f03542))
|
||||
* **dns-hole-summary:** remove inconsistency for position of text ([#2568](https://github.com/homarr-labs/homarr/issues/2568)) ([e774b06](https://github.com/homarr-labs/homarr/commit/e774b06fc2173eb094e2b698a93624a54cd37dff))
|
||||
* **dns-summary:** improve responsive styles ([#2535](https://github.com/homarr-labs/homarr/issues/2535)) ([7ec6fbc](https://github.com/homarr-labs/homarr/commit/7ec6fbc3765491a810356948a294309ab606bdce))
|
||||
* **downloads:** improve responsive styles ([#2552](https://github.com/homarr-labs/homarr/issues/2552)) ([d714e53](https://github.com/homarr-labs/homarr/commit/d714e53cfa88da22a6fc75473455d57be7bac497))
|
||||
* **import:** autofix missing sizes of items with grid-algorithm ([#2522](https://github.com/homarr-labs/homarr/issues/2522)) ([d410af3](https://github.com/homarr-labs/homarr/commit/d410af34029a43e6e11cd766f55c634b00eb8fa2))
|
||||
* **indexer-manager:** improve responsive styles ([#2541](https://github.com/homarr-labs/homarr/issues/2541)) ([4472c04](https://github.com/homarr-labs/homarr/commit/4472c04967f0a3799507785b02f96a84c9626465))
|
||||
* **media-request-list:** improve responsive styles ([#2543](https://github.com/homarr-labs/homarr/issues/2543)) ([9e48f43](https://github.com/homarr-labs/homarr/commit/9e48f438b4550f542130651ea7dccccc36b59da8))
|
||||
* **media-request-stats:** improve responsive styles ([#2544](https://github.com/homarr-labs/homarr/issues/2544)) ([d584ade](https://github.com/homarr-labs/homarr/commit/d584ade8f4a788b284071cb9d17b6d8b606098c8))
|
||||
* **media-server:** improve responsive styles ([#2548](https://github.com/homarr-labs/homarr/issues/2548)) ([82528db](https://github.com/homarr-labs/homarr/commit/82528dbd961b21682d71b104b22804391b6c7d08))
|
||||
* **media-transcoding:** improve responsive styles ([#2550](https://github.com/homarr-labs/homarr/issues/2550)) ([8f8d788](https://github.com/homarr-labs/homarr/commit/8f8d7884a9484aa8e73f4ff114e58df78a8bb470))
|
||||
* **minecraft-status:** wrong api path for bedrock ([#2551](https://github.com/homarr-labs/homarr/issues/2551)) ([2f80c09](https://github.com/homarr-labs/homarr/commit/2f80c096a7e25b1a223336644f40a56311e1c7a8))
|
||||
* **notebook:** css for checkboxes not working ([#2570](https://github.com/homarr-labs/homarr/issues/2570)) ([a53e7aa](https://github.com/homarr-labs/homarr/commit/a53e7aaee5207ab563b6e0144f3154db917f7da1))
|
||||
* **smart-home:** improve responsive styles ([#2540](https://github.com/homarr-labs/homarr/issues/2540)) ([0b07f22](https://github.com/homarr-labs/homarr/commit/0b07f227ee72988fe1cbd37a9a6a8751b942a087))
|
||||
* **system-health:** improve responsive styles ([#2566](https://github.com/homarr-labs/homarr/issues/2566)) ([268daee](https://github.com/homarr-labs/homarr/commit/268daee4a6e90052a19b9d2426c2cf8b2c8a0c00))
|
||||
|
||||
## [1.10.0](https://github.com/homarr-labs/homarr/compare/v1.9.0...v1.10.0) (2025-03-07)
|
||||
|
||||
### Features
|
||||
|
||||
* remove cqmin system ([#2407](https://github.com/homarr-labs/homarr/issues/2407)) ([37d4714](https://github.com/homarr-labs/homarr/commit/37d471457a0a2b10112b67c02923f63de479e617))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.1.1 ([#2524](https://github.com/homarr-labs/homarr/issues/2524)) ([4721a97](https://github.com/homarr-labs/homarr/commit/4721a97094ae1181216923c7fef7a29c343f882b))
|
||||
* **deps:** update dependency ioredis to v5.6.0 ([#2509](https://github.com/homarr-labs/homarr/issues/2509)) ([e4a3922](https://github.com/homarr-labs/homarr/commit/e4a39223a33d4bfccb1e9b4e2c30de1376879a1d))
|
||||
* **deps:** update dependency mysql2 to v3.13.0 ([#2516](https://github.com/homarr-labs/homarr/issues/2516)) ([67c739c](https://github.com/homarr-labs/homarr/commit/67c739c35bcb2d537a349f0ebff080eac67e93b5))
|
||||
* **deps:** update tanstack-query monorepo to ^5.67.2 ([#2525](https://github.com/homarr-labs/homarr/issues/2525)) ([a572aae](https://github.com/homarr-labs/homarr/commit/a572aae887142a7e601f70dcc601bf6335e37c17))
|
||||
* **pi-hole:** wrong fetch used in factory ([#2527](https://github.com/homarr-labs/homarr/issues/2527)) ([9881577](https://github.com/homarr-labs/homarr/commit/9881577f47fbeacf861dd564d57fc6d7c469d27b))
|
||||
|
||||
## [1.9.0](https://github.com/homarr-labs/homarr/compare/v1.8.0...v1.9.0) (2025-03-05)
|
||||
|
||||
### Features
|
||||
|
||||
* add sigterm support ([#2472](https://github.com/homarr-labs/homarr/issues/2472)) ([a5e0e8c](https://github.com/homarr-labs/homarr/commit/a5e0e8c1296ebfbe46c88fbb152b5d0049db349d))
|
||||
* added bordercolor option for dynamic section ([#2334](https://github.com/homarr-labs/homarr/issues/2334)) ([7dfb108](https://github.com/homarr-labs/homarr/commit/7dfb108a56e38c32e712b82d1e1bd65a5c15722b))
|
||||
* **integration:** add emby integration ([#2466](https://github.com/homarr-labs/homarr/issues/2466)) ([7dfc364](https://github.com/homarr-labs/homarr/commit/7dfc3646b709e6bb1fc7ec11336024c7eff1f342))
|
||||
* **media-request:** show request status ([#2465](https://github.com/homarr-labs/homarr/issues/2465)) ([80c2040](https://github.com/homarr-labs/homarr/commit/80c2040b1345b0a60030aa8879442b937863216f))
|
||||
* **pihole:** add support for v6 ([#2448](https://github.com/homarr-labs/homarr/issues/2448)) ([3804d53](https://github.com/homarr-labs/homarr/commit/3804d530ec28c90b6ab1fa0f709468e2075afa28))
|
||||
* use fetch with trusted certificates for oidc ([#2412](https://github.com/homarr-labs/homarr/issues/2412)) ([6fad48f](https://github.com/homarr-labs/homarr/commit/6fad48f65ad7cbb927fd80de49930a8256110331))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* affix hides pagination on mobile ([#2491](https://github.com/homarr-labs/homarr/issues/2491)) ([604a05f](https://github.com/homarr-labs/homarr/commit/604a05f9d528b7d89b09f4f7982921963ba0fb90))
|
||||
* **boards:** case-sensitive board names in url ([#2454](https://github.com/homarr-labs/homarr/issues/2454)) ([c0e3544](https://github.com/homarr-labs/homarr/commit/c0e354428af2c69bf7638911320888f049ec0659))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.31.0 ([#2502](https://github.com/homarr-labs/homarr/issues/2502)) ([478aae6](https://github.com/homarr-labs/homarr/commit/478aae6cd496ef81e3036d8f2a5b6959b7fea12e))
|
||||
* **deps:** update dependency prettier to ^3.5.3 ([#2485](https://github.com/homarr-labs/homarr/issues/2485)) ([49b0f96](https://github.com/homarr-labs/homarr/commit/49b0f962e0b316bbed599d77d97be927aac252da))
|
||||
* **deps:** update dependency typescript-eslint to ^8.26.0 ([#2488](https://github.com/homarr-labs/homarr/issues/2488)) ([21f3a5a](https://github.com/homarr-labs/homarr/commit/21f3a5a408ab45dbf7d92162703e74b9e60aff10))
|
||||
* **deps:** update mantine monorepo to ^7.17.1 ([#2462](https://github.com/homarr-labs/homarr/issues/2462)) ([d9ef1b0](https://github.com/homarr-labs/homarr/commit/d9ef1b04706a3435d4500db2f2966a28db900d9d))
|
||||
* **deps:** update tanstack-query monorepo to ^5.67.0 ([#2486](https://github.com/homarr-labs/homarr/issues/2486)) ([bad07cb](https://github.com/homarr-labs/homarr/commit/bad07cbf251ab64afe99390bcb687d2f58e8fdc7))
|
||||
* **deps:** update tanstack-query monorepo to ^5.67.1 ([#2487](https://github.com/homarr-labs/homarr/issues/2487)) ([6135b59](https://github.com/homarr-labs/homarr/commit/6135b59b7d24029eaf24ae2a2b94b5cbc822242a))
|
||||
* docker add to homarr layout overflow ([#2441](https://github.com/homarr-labs/homarr/issues/2441)) ([a3999bc](https://github.com/homarr-labs/homarr/commit/a3999bcf521d4d339282701b4734b6cd352c9582))
|
||||
* **integration:** store omv session in redis ([#2467](https://github.com/homarr-labs/homarr/issues/2467)) ([b3e4f30](https://github.com/homarr-labs/homarr/commit/b3e4f3031296ffc69d914f542f95895a449ce2c7))
|
||||
* **logs:** add special case for axios error to prevent huge error messages ([#2468](https://github.com/homarr-labs/homarr/issues/2468)) ([9eb7663](https://github.com/homarr-labs/homarr/commit/9eb76634d07b7bf5ff28062714c44e150591d75d))
|
||||
* **media-request:** inconsistent sorting and reordering ([#2464](https://github.com/homarr-labs/homarr/issues/2464)) ([43619e0](https://github.com/homarr-labs/homarr/commit/43619e0cc0311ca8f26a4fdbc0ccb7fae79c3514))
|
||||
* **old-import:** default logo and favicon not replaced ([#2456](https://github.com/homarr-labs/homarr/issues/2456)) ([788304a](https://github.com/homarr-labs/homarr/commit/788304adfbe3c6c9696758e9ad42bca9d9b86290))
|
||||
|
||||
## [1.8.0](https://github.com/homarr-labs/homarr/compare/v1.7.0...v1.8.0) (2025-02-28)
|
||||
|
||||
### Features
|
||||
|
||||
* add app ping url ([#2380](https://github.com/homarr-labs/homarr/issues/2380)) ([fb467ac](https://github.com/homarr-labs/homarr/commit/fb467ac16553d3c498770a531cf77b9f2c7c0a9e))
|
||||
* **boards:** add responsive layout system ([#2271](https://github.com/homarr-labs/homarr/issues/2271)) ([7761dc2](https://github.com/homarr-labs/homarr/commit/7761dc29c809b28cc8275dcabf77c453cea3621c))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* await params for board metadata ([#2406](https://github.com/homarr-labs/homarr/issues/2406)) ([2085b5e](https://github.com/homarr-labs/homarr/commit/2085b5ece24f661086c4896a6ecfbff61de1b7d4))
|
||||
* **boards:** add scrollarea to board-select ([#2409](https://github.com/homarr-labs/homarr/issues/2409)) ([508f9af](https://github.com/homarr-labs/homarr/commit/508f9afeb542d09d06fc34f06ac68cf732011620))
|
||||
* chinese traditional locale ([#2440](https://github.com/homarr-labs/homarr/issues/2440)) ([50367eb](https://github.com/homarr-labs/homarr/commit/50367eb2f5ae9cb6b736197692b3f555e07aca59))
|
||||
* **deps:** downgrade to next 15.1.7 cause of turbopack crash with 15.2.0 and above ([#2438](https://github.com/homarr-labs/homarr/issues/2438)) ([fdd91f5](https://github.com/homarr-labs/homarr/commit/fdd91f585d1c0c66a6c28c6d1e374bdd9fdcb787))
|
||||
* **deps:** update dependency @auth/core to ^0.38.0 ([#2434](https://github.com/homarr-labs/homarr/issues/2434)) ([8c1308c](https://github.com/homarr-labs/homarr/commit/8c1308c97f011336c2a813e92e7cefa195277994))
|
||||
* **deps:** update dependency @auth/drizzle-adapter to ^1.8.0 ([#2435](https://github.com/homarr-labs/homarr/issues/2435)) ([b2cc97e](https://github.com/homarr-labs/homarr/commit/b2cc97e316976e74df8136a7da53a3fae78f815b))
|
||||
* **deps:** update dependency drizzle-kit to ^0.30.5 ([#2423](https://github.com/homarr-labs/homarr/issues/2423)) ([3b830ae](https://github.com/homarr-labs/homarr/commit/3b830ae098c290aaa15356cae5ec2fe8cb618d9f))
|
||||
* **deps:** update dependency drizzle-orm to ^0.40.0 ([#2425](https://github.com/homarr-labs/homarr/issues/2425)) ([faf7322](https://github.com/homarr-labs/homarr/commit/faf7322bcb94a61dbb2b3295cc4a4eca52484d74))
|
||||
* **deps:** update dependency eslint-config-prettier to ^10.0.2 ([#2427](https://github.com/homarr-labs/homarr/issues/2427)) ([c2d6e2e](https://github.com/homarr-labs/homarr/commit/c2d6e2efcf06e97c04bddc2d821c77ad034426b8))
|
||||
* **deps:** update dependency eslint-plugin-react-hooks to ^5.2.0 ([#2449](https://github.com/homarr-labs/homarr/issues/2449)) ([a88c1ba](https://github.com/homarr-labs/homarr/commit/a88c1ba4401bbe6317ccde11cff9a057bfa53e26))
|
||||
* **deps:** update dependency prettier to ^3.5.2 ([#2399](https://github.com/homarr-labs/homarr/issues/2399)) ([86e778d](https://github.com/homarr-labs/homarr/commit/86e778db1ee081288b4174f6b52fcc44a0bbc1e0))
|
||||
* **deps:** update dependency sass to ^1.85.1 ([#2418](https://github.com/homarr-labs/homarr/issues/2418)) ([a9a37af](https://github.com/homarr-labs/homarr/commit/a9a37afd441ad148a42432c24a9c7e6acf804c89))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.20.0 ([#2436](https://github.com/homarr-labs/homarr/issues/2436)) ([add9170](https://github.com/homarr-labs/homarr/commit/add91701de30eaeb61efe3295307756e998703a9))
|
||||
* **deps:** update dependency typescript-eslint to ^8.25.0 ([#2413](https://github.com/homarr-labs/homarr/issues/2413)) ([593c50b](https://github.com/homarr-labs/homarr/commit/593c50bb8f803ecbda8e6d0c4a082e019cc56352))
|
||||
* **deps:** update dependency undici to v7.4.0 ([#2445](https://github.com/homarr-labs/homarr/issues/2445)) ([06ba0f0](https://github.com/homarr-labs/homarr/commit/06ba0f0e9f6da9c22ef531e41811a89c8c5039a8))
|
||||
* **deps:** update nextjs monorepo to ^15.2.0 ([#2428](https://github.com/homarr-labs/homarr/issues/2428)) ([d6939e4](https://github.com/homarr-labs/homarr/commit/d6939e4a6daf7fbe0095ae4028d33189c5866fb2))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.10 ([#2443](https://github.com/homarr-labs/homarr/issues/2443)) ([4483dc8](https://github.com/homarr-labs/homarr/commit/4483dc8bcd062f531badd94ba872cf42a17491df))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.11 ([#2447](https://github.com/homarr-labs/homarr/issues/2447)) ([5537f95](https://github.com/homarr-labs/homarr/commit/5537f95b85b1ae56a35d7b529907ebe22d0b1ae6))
|
||||
* **deps:** update turbo monorepo to ^2.4.3 ([#2422](https://github.com/homarr-labs/homarr/issues/2422)) ([874414f](https://github.com/homarr-labs/homarr/commit/874414fc4fc1130502abc267116ad109dab11efe))
|
||||
* **deps:** update turbo monorepo to ^2.4.4 ([#2424](https://github.com/homarr-labs/homarr/issues/2424)) ([a6f9199](https://github.com/homarr-labs/homarr/commit/a6f91998c3a8192eaa93b57a74b6dfc8f3fdc13a))
|
||||
* omv invalid temperature when not available ([#2322](https://github.com/homarr-labs/homarr/issues/2322)) ([5dc5d9e](https://github.com/homarr-labs/homarr/commit/5dc5d9e04ab747655276494d70557b45fed87b2c))
|
||||
* update checker logs incorrectly ([#2402](https://github.com/homarr-labs/homarr/issues/2402)) ([c5107b2](https://github.com/homarr-labs/homarr/commit/c5107b2b67cad73808dcbae6bde633ac1a7eb2b5))
|
||||
|
||||
## [1.7.0](https://github.com/homarr-labs/homarr/compare/v1.6.0...v1.7.0) (2025-02-21)
|
||||
|
||||
### Features
|
||||
|
||||
* add tile border radius ([#2338](https://github.com/homarr-labs/homarr/issues/2338)) ([7705bc4](https://github.com/homarr-labs/homarr/commit/7705bc44ae7c93751e718b019c98c11369c221af))
|
||||
* **board:** allow to set icon color of widgets ([#2228](https://github.com/homarr-labs/homarr/issues/2228)) ([de5c34a](https://github.com/homarr-labs/homarr/commit/de5c34ab079c308c80cc3b1ba8f9c8a265e3a1c3))
|
||||
* **cli:** add fix-username command to make all usernames lowercase ([#2366](https://github.com/homarr-labs/homarr/issues/2366)) ([8b7caf1](https://github.com/homarr-labs/homarr/commit/8b7caf1d7dfebb8121cefd301fa72ef59601d410))
|
||||
* downgrade log level callback succeeded ([#2329](https://github.com/homarr-labs/homarr/issues/2329)) ([22a341e](https://github.com/homarr-labs/homarr/commit/22a341ea4d7365fb51781f96791394315afd8860))
|
||||
* **groups:** add home board settings ([#2321](https://github.com/homarr-labs/homarr/issues/2321)) ([ffe7259](https://github.com/homarr-labs/homarr/commit/ffe72598026e5e27588b18feeaaf7d30065d5536))
|
||||
* **logging:** add log level env variable ([#2299](https://github.com/homarr-labs/homarr/issues/2299)) ([67d48e1](https://github.com/homarr-labs/homarr/commit/67d48e11d7e1165540c66f86b299e55028d76788))
|
||||
* quick add app modal ([#2248](https://github.com/homarr-labs/homarr/issues/2248)) ([6420fee](https://github.com/homarr-labs/homarr/commit/6420feee729aa641c0545fa39308772e4a4b1af8))
|
||||
* redirect to login screen when no global home board ([#2379](https://github.com/homarr-labs/homarr/issues/2379)) ([311ae80](https://github.com/homarr-labs/homarr/commit/311ae80667715fccffbfa73699c0770d483c3434))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency jotai to ^2.12.1 ([#2354](https://github.com/homarr-labs/homarr/issues/2354)) ([c59fbfc](https://github.com/homarr-labs/homarr/commit/c59fbfc677b10ee20c2e698d9837af1aa098854b))
|
||||
* **deps:** update dependency mantine-react-table to v2.0.0-beta.9 ([#2348](https://github.com/homarr-labs/homarr/issues/2348)) ([4764f41](https://github.com/homarr-labs/homarr/commit/4764f41e1bf77f7934840a84e25b2989fc1e7c7c))
|
||||
* **deps:** update dependency next-intl to v3.26.4 ([#2361](https://github.com/homarr-labs/homarr/issues/2361)) ([241037f](https://github.com/homarr-labs/homarr/commit/241037fc2a699249f317e5e434ac603cee4e460d))
|
||||
* **deps:** update dependency next-intl to v3.26.5 ([#2386](https://github.com/homarr-labs/homarr/issues/2386)) ([afd7bce](https://github.com/homarr-labs/homarr/commit/afd7bcea841e906003ec90516b033214ad412473))
|
||||
* **deps:** update dependency octokit to ^4.1.2 ([#2333](https://github.com/homarr-labs/homarr/issues/2333)) ([33ef9f6](https://github.com/homarr-labs/homarr/commit/33ef9f66782a59047878a3e82330128fdcabfa93))
|
||||
* **deps:** update dependency prettier to ^3.5.1 ([#2284](https://github.com/homarr-labs/homarr/issues/2284)) ([f6c9230](https://github.com/homarr-labs/homarr/commit/f6c923017c6b4ac9670843bba5dee2f52afd1918))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.19.0 ([#2358](https://github.com/homarr-labs/homarr/issues/2358)) ([91c61f8](https://github.com/homarr-labs/homarr/commit/91c61f8c06622893b90f668e30a615437d9bb549))
|
||||
* **deps:** update dependency tsx to v4.19.3 ([#2370](https://github.com/homarr-labs/homarr/issues/2370)) ([58babbf](https://github.com/homarr-labs/homarr/commit/58babbf88fecc401bf4e7baa52636a4a937bead2))
|
||||
* **deps:** update dependency typescript-eslint to ^8.24.1 ([#2360](https://github.com/homarr-labs/homarr/issues/2360)) ([146fe49](https://github.com/homarr-labs/homarr/commit/146fe499db0fe36c8822ad30a762738425fb6f50))
|
||||
* **deps:** update dependency ws to ^8.18.1 ([#2387](https://github.com/homarr-labs/homarr/issues/2387)) ([b58f64b](https://github.com/homarr-labs/homarr/commit/b58f64b05c09070d8c5cdc7f89b23768b9997405))
|
||||
* **deps:** update mantine monorepo to ^7.17.0 ([#2359](https://github.com/homarr-labs/homarr/issues/2359)) ([96a0717](https://github.com/homarr-labs/homarr/commit/96a071725e557e64d8951091063f047cb43a385b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.2 ([#2341](https://github.com/homarr-labs/homarr/issues/2341)) ([f8fedef](https://github.com/homarr-labs/homarr/commit/f8fedefde9ae65923369b46876009f9f6cb27683))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.3 ([#2343](https://github.com/homarr-labs/homarr/issues/2343)) ([f32af66](https://github.com/homarr-labs/homarr/commit/f32af66a6ad4676530265bfd1052b56308d4d40f))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.4 ([#2355](https://github.com/homarr-labs/homarr/issues/2355)) ([602faf6](https://github.com/homarr-labs/homarr/commit/602faf622140af1cc4ac92f8a54a56924d809f67))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.6 ([#2357](https://github.com/homarr-labs/homarr/issues/2357)) ([bd0d910](https://github.com/homarr-labs/homarr/commit/bd0d910137194caef989f4dba360bcaf616e9ea4))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.7 ([#2364](https://github.com/homarr-labs/homarr/issues/2364)) ([e84cc5e](https://github.com/homarr-labs/homarr/commit/e84cc5e6ae32c78d4e2be4d57e2b97ad84094f5e))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.8 ([#2376](https://github.com/homarr-labs/homarr/issues/2376)) ([8b75966](https://github.com/homarr-labs/homarr/commit/8b7596642a8398af817eedbe2dd89e9bad3fd937))
|
||||
* **deps:** update tanstack-query monorepo to ^5.66.9 ([#2391](https://github.com/homarr-labs/homarr/issues/2391)) ([1cf36ed](https://github.com/homarr-labs/homarr/commit/1cf36ed4c776d6c99e1e1058dde9129475069aaf))
|
||||
* **medias:** svg files don't load in list ([#2381](https://github.com/homarr-labs/homarr/issues/2381)) ([52549a5](https://github.com/homarr-labs/homarr/commit/52549a598d039a9fa2165983698212564eb5233b))
|
||||
* missing cover types media organizers ([#2328](https://github.com/homarr-labs/homarr/issues/2328)) ([3257f61](https://github.com/homarr-labs/homarr/commit/3257f6101bdd8967089655507144dc4368390b8c))
|
||||
* omv integrations is unable to correctly retrieve the file system when using omv-zfs ([#2337](https://github.com/homarr-labs/homarr/issues/2337)) ([63e9623](https://github.com/homarr-labs/homarr/commit/63e96230e0e2831c787d3e520a6b8e9b4bddb2a7))
|
||||
* typo in pr template ([#2344](https://github.com/homarr-labs/homarr/issues/2344)) ([a62aa09](https://github.com/homarr-labs/homarr/commit/a62aa09db6c87573d579bcebc9f5d5c3ac133af2))
|
||||
* **widgets:** app icon not visible ([#2390](https://github.com/homarr-labs/homarr/issues/2390)) ([a54e078](https://github.com/homarr-labs/homarr/commit/a54e0783e2ad452c4db3d7ddb4e4bcd501a8687a))
|
||||
|
||||
## [1.6.0](https://github.com/homarr-labs/homarr/compare/v1.5.0...v1.6.0) (2025-02-14)
|
||||
|
||||
### Features
|
||||
|
||||
* **lang:** add catalan language ([#2283](https://github.com/homarr-labs/homarr/issues/2283)) ([749b0c6](https://github.com/homarr-labs/homarr/commit/749b0c6ee9dc7802ae10393b90e19bf3c43bf9e0))
|
||||
* **settings:** add simple-ping settings ([#2118](https://github.com/homarr-labs/homarr/issues/2118)) ([dff6cb9](https://github.com/homarr-labs/homarr/commit/dff6cb9d31c3884bf811774a7c90dad27ac19691))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add error handling for available updates ([#2289](https://github.com/homarr-labs/homarr/issues/2289)) ([0502e47](https://github.com/homarr-labs/homarr/commit/0502e477707932db96b25372ddab8263572b389c))
|
||||
* add mysql pool with keep-alive to not close the connection ([#2282](https://github.com/homarr-labs/homarr/issues/2282)) ([a38afb0](https://github.com/homarr-labs/homarr/commit/a38afb05d35cf2bdba378be3744f3a8b6c9d38f6))
|
||||
* **calendar:** info popover overlap ([#2268](https://github.com/homarr-labs/homarr/issues/2268)) ([584c660](https://github.com/homarr-labs/homarr/commit/584c660906f5ae7e8ab1c88ce9cfbbbbdcdb5145))
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.3.0 ([#2310](https://github.com/homarr-labs/homarr/issues/2310)) ([8649fac](https://github.com/homarr-labs/homarr/commit/8649faca7929daff426607f104057fce26edd049))
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.4.0 ([#2315](https://github.com/homarr-labs/homarr/issues/2315)) ([4d3cc1d](https://github.com/homarr-labs/homarr/commit/4d3cc1d80698e659a669de7d4bd0d185ad68605e))
|
||||
* **deps:** update dependency @extractus/feed-extractor to v7.1.4 ([#2281](https://github.com/homarr-labs/homarr/issues/2281)) ([7edaa48](https://github.com/homarr-labs/homarr/commit/7edaa48fb9bcf48e8b734beec5a8bad003124233))
|
||||
* **deps:** update dependency drizzle-orm to ^0.39.3 ([#2295](https://github.com/homarr-labs/homarr/issues/2295)) ([8ec0080](https://github.com/homarr-labs/homarr/commit/8ec0080fca17c530cdd0576ba101299a0f769463))
|
||||
* **deps:** update dependency jotai to ^2.12.0 ([#2306](https://github.com/homarr-labs/homarr/issues/2306)) ([fc43da5](https://github.com/homarr-labs/homarr/commit/fc43da59f845934da428bcafd4ec46551ace8a81))
|
||||
* **deps:** update dependency sass to ^1.85.0 ([#2326](https://github.com/homarr-labs/homarr/issues/2326)) ([8ee39eb](https://github.com/homarr-labs/homarr/commit/8ee39eb6db89fdeaec0fae7988ffa97280c2f5ab))
|
||||
* **deps:** update dependency typescript-eslint to ^8.24.0 ([#2297](https://github.com/homarr-labs/homarr/issues/2297)) ([8371f46](https://github.com/homarr-labs/homarr/commit/8371f46d34ad36fd25f23d91804d3f95439d7012))
|
||||
* **deps:** update dependency zod to ^3.24.2 ([#2311](https://github.com/homarr-labs/homarr/issues/2311)) ([379675a](https://github.com/homarr-labs/homarr/commit/379675a901637a1b878595a80d0776d05311218d))
|
||||
* **deps:** update mantine monorepo to ^7.16.3 ([#2275](https://github.com/homarr-labs/homarr/issues/2275)) ([42a6814](https://github.com/homarr-labs/homarr/commit/42a68140b1493cc171977d996eea16d876dce60c))
|
||||
* **deps:** update nextjs monorepo to ^15.1.7 ([#2302](https://github.com/homarr-labs/homarr/issues/2302)) ([2399967](https://github.com/homarr-labs/homarr/commit/2399967868652436ee6827bce8883c1b4a00af0e))
|
||||
* **deps:** update turbo monorepo to ^2.4.1 ([#2296](https://github.com/homarr-labs/homarr/issues/2296)) ([2b9f834](https://github.com/homarr-labs/homarr/commit/2b9f834d9645aaf5f5aa1b8a5f1e48300389ac8b))
|
||||
* **deps:** update turbo monorepo to ^2.4.2 ([#2313](https://github.com/homarr-labs/homarr/issues/2313)) ([1045465](https://github.com/homarr-labs/homarr/commit/10454656a286c320a8d3b5ff12a138025d871b20))
|
||||
* **users:** edit profile username not lowercase ([#2279](https://github.com/homarr-labs/homarr/issues/2279)) ([b7fd247](https://github.com/homarr-labs/homarr/commit/b7fd2470ee0d85821f79a34e839e62fbab6989c6))
|
||||
|
||||
## [1.5.0](https://github.com/homarr-labs/homarr/compare/v1.4.0...v1.5.0) (2025-02-07)
|
||||
|
||||
### Features
|
||||
|
||||
* add api key delete ([#2210](https://github.com/homarr-labs/homarr/issues/2210)) ([8058b62](https://github.com/homarr-labs/homarr/commit/8058b6207ad4be1d646182b67b810c397097c054))
|
||||
* custom time/date format in clock widget ([#2180](https://github.com/homarr-labs/homarr/issues/2180)) ([be603ba](https://github.com/homarr-labs/homarr/commit/be603badfed63ad7583a5265bef3b9e85f6b6b61))
|
||||
* improve consistency and design ([#1867](https://github.com/homarr-labs/homarr/issues/1867)) ([5f36d8b](https://github.com/homarr-labs/homarr/commit/5f36d8b12528fd2c8c3da783b266d614203ec46a))
|
||||
* open all apps in category ([#2212](https://github.com/homarr-labs/homarr/issues/2212)) ([bb67bf5](https://github.com/homarr-labs/homarr/commit/bb67bf5fa2968d91ad3522ebae960a6880f85987))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.30.0 ([#2266](https://github.com/homarr-labs/homarr/issues/2266)) ([c849109](https://github.com/homarr-labs/homarr/commit/c849109df5b622aca8cf7b392b94685581bf6b32))
|
||||
* **deps:** update dependency drizzle-orm to ^0.39.2 ([#2255](https://github.com/homarr-labs/homarr/issues/2255)) ([3d0b319](https://github.com/homarr-labs/homarr/commit/3d0b319a88bdb8d955d7942553d92988bf5b2c28))
|
||||
* **deps:** update dependency ioredis to v5.5.0 ([#2264](https://github.com/homarr-labs/homarr/issues/2264)) ([0da5a1b](https://github.com/homarr-labs/homarr/commit/0da5a1b0fb86ff2074f12a5e028fcc09ff94a4a5))
|
||||
* **deps:** update dependency jotai to ^2.11.3 ([#2215](https://github.com/homarr-labs/homarr/issues/2215)) ([a385337](https://github.com/homarr-labs/homarr/commit/a3853372687aed543fad2dd89bfb13871af0f2c1))
|
||||
* **deps:** update dependency octokit to ^4.1.1 ([#2262](https://github.com/homarr-labs/homarr/issues/2262)) ([3cbb656](https://github.com/homarr-labs/homarr/commit/3cbb656ee9b540d2dd4c7db37946efacbbfdc522))
|
||||
* **deps:** update dependency sass to ^1.84.0 ([#2257](https://github.com/homarr-labs/homarr/issues/2257)) ([7bbbaf3](https://github.com/homarr-labs/homarr/commit/7bbbaf343befaaa3d03a91e932f683c5aec4b41e))
|
||||
* **deps:** update dependency semver-parser to ^4.1.8 ([#2216](https://github.com/homarr-labs/homarr/issues/2216)) ([46cfb23](https://github.com/homarr-labs/homarr/commit/46cfb2383b6628472d3972da2166884330bf6d24))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.1.3 ([#2244](https://github.com/homarr-labs/homarr/issues/2244)) ([c623a42](https://github.com/homarr-labs/homarr/commit/c623a420cadce677e39b99fceeb3f3a391f879ea))
|
||||
* **deps:** update dependency typescript-eslint to ^8.23.0 ([#2232](https://github.com/homarr-labs/homarr/issues/2232)) ([83044d2](https://github.com/homarr-labs/homarr/commit/83044d23dd7accb1b3a21cdde1e034969a494ccb))
|
||||
* **deps:** update testcontainers-node monorepo to ^10.18.0 ([#2259](https://github.com/homarr-labs/homarr/issues/2259)) ([7a8e671](https://github.com/homarr-labs/homarr/commit/7a8e67120576b333bfcf73171b54ccfeeda0d44a))
|
||||
* **deps:** update turbo monorepo to ^2.4.0 ([#2207](https://github.com/homarr-labs/homarr/issues/2207)) ([6ce7bc0](https://github.com/homarr-labs/homarr/commit/6ce7bc0ee2dcd5150d72fdbc2070c05ba836ebd5))
|
||||
* foreign key reference default search engine ([#2211](https://github.com/homarr-labs/homarr/issues/2211)) ([26468a4](https://github.com/homarr-labs/homarr/commit/26468a413795afa7cc3444058bfc73435fb7f2a0))
|
||||
* make system health temp optional ([#2247](https://github.com/homarr-labs/homarr/issues/2247)) ([da09826](https://github.com/homarr-labs/homarr/commit/da098263930c9c9611a1505d52d3816bd2a49e7e))
|
||||
|
||||
## [1.4.0](https://github.com/homarr-labs/homarr/compare/v1.3.1...v1.4.0) (2025-01-31)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.13.1-alpine AS base
|
||||
FROM node:22.16.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
@@ -10,6 +10,8 @@ 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
|
||||
@@ -17,6 +19,8 @@ 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
|
||||
@@ -66,4 +70,4 @@ ENV AUTH_PROVIDERS='credentials'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -2,18 +2,22 @@
|
||||
import "@homarr/auth/env";
|
||||
import "@homarr/db/env";
|
||||
import "@homarr/common/env";
|
||||
import "@homarr/log/env";
|
||||
import "@homarr/docker/env";
|
||||
|
||||
import type { NextConfig } from "next";
|
||||
import MillionLint from "@million/lint";
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
import "./src/env.ts";
|
||||
|
||||
// Package path does not work... so we need to use relative path
|
||||
const withNextIntl = createNextIntlPlugin("../../packages/translation/src/request.ts");
|
||||
const withNextIntl = createNextIntlPlugin({
|
||||
experimental: {
|
||||
createMessagesDeclaration: "../../packages/translation/src/lang/en.json",
|
||||
},
|
||||
requestConfig: "../../packages/translation/src/request.ts",
|
||||
});
|
||||
|
||||
interface WebpackConfig {
|
||||
interface _WebpackConfig {
|
||||
module: {
|
||||
rules: {
|
||||
test: RegExp;
|
||||
@@ -28,7 +32,7 @@ const nextConfig: NextConfig = {
|
||||
/** We already do linting and typechecking as separate tasks in CI */
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
webpack: (config: WebpackConfig, { isServer }) => {
|
||||
/*webpack: (config: WebpackConfig, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.module.rules.push({
|
||||
test: /\.node$/,
|
||||
@@ -37,14 +41,51 @@ const nextConfig: NextConfig = {
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
},*/
|
||||
experimental: {
|
||||
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
||||
},
|
||||
transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"],
|
||||
serverExternalPackages: [
|
||||
"dockerode",
|
||||
"@kubernetes/client-node",
|
||||
"@ctrl/deluge",
|
||||
"@ctrl/qbittorrent",
|
||||
"@ctrl/transmission",
|
||||
"node-ical",
|
||||
"tsdav",
|
||||
"proxmox-api",
|
||||
"@jellyfin/sdk",
|
||||
],
|
||||
images: {
|
||||
domains: ["cdn.jsdelivr.net"],
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/(.*)", // Apply CSP to all routes
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: `
|
||||
default-src 'self';
|
||||
script-src * 'unsafe-inline' 'unsafe-eval';
|
||||
base-uri 'self';
|
||||
connect-src *;
|
||||
style-src 'self' 'unsafe-inline';
|
||||
frame-ancestors *;
|
||||
frame-src *;
|
||||
form-action 'self';
|
||||
img-src * data:;
|
||||
font-src * data:;
|
||||
`
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
// Skip transform is used because of webpack loader, without it for example 'Tooltip.Floating' will not work and show an error
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "pnpm with-env next build",
|
||||
"clean": "git clean -xdf .next .turbo node_modules",
|
||||
"dev": "pnpm with-env next dev --turbopack",
|
||||
"dev": "pnpm with-env next dev",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"start": "pnpm with-env next start",
|
||||
@@ -15,9 +15,14 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/boards": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
@@ -25,6 +30,7 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||
"@homarr/gridstack": "^1.12.0",
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
@@ -42,60 +48,59 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^7.16.2",
|
||||
"@mantine/core": "^7.16.2",
|
||||
"@mantine/dropzone": "^7.16.2",
|
||||
"@mantine/hooks": "^7.16.2",
|
||||
"@mantine/modals": "^7.16.2",
|
||||
"@mantine/tiptap": "^7.16.2",
|
||||
"@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",
|
||||
"@million/lint": "1.0.14",
|
||||
"@t3-oss/env-nextjs": "^0.12.0",
|
||||
"@tabler/icons-react": "^3.29.0",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"@tanstack/react-query-devtools": "^5.66.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.66.0",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
"@trpc/react-query": "next",
|
||||
"@trpc/server": "next",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@tanstack/react-query": "^5.77.2",
|
||||
"@tanstack/react-query-devtools": "^5.77.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.77.2",
|
||||
"@trpc/client": "^11.1.3",
|
||||
"@trpc/next": "^11.1.3",
|
||||
"@trpc/react-query": "^11.1.3",
|
||||
"@trpc/server": "^11.1.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.4.7",
|
||||
"dotenv": "^16.5.0",
|
||||
"flag-icons": "^7.3.2",
|
||||
"glob": "^11.0.1",
|
||||
"jotai": "^2.11.2",
|
||||
"mantine-react-table": "2.0.0-beta.8",
|
||||
"next": "15.1.6",
|
||||
"glob": "^11.0.2",
|
||||
"jotai": "^2.12.5",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.2",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-error-boundary": "^5.0.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.83.4",
|
||||
"sass": "^1.89.0",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.18.3",
|
||||
"swagger-ui-react": "^5.22.0",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.24.1"
|
||||
"zod": "^3.25.30"
|
||||
},
|
||||
"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.12.0",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/react": "19.1.6",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint": "^9.27.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.3"
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 139.3 512.2 233.5"><path d="M256 139.3c-53 0-97.9 35.8-112.1 84.4-12.2-25.5-38.2-43.4-68.2-43.4C34.2 180.2 0 214.4 0 256s34.2 75.8 75.8 75.8c30 0 55.9-17.9 68.2-43.4 14.1 48.6 59.1 84.4 112.1 84.4S354 337 368.2 288.4c12.2 25.5 38.2 43.4 68.2 43.4 41.6 0 75.8-34.2 75.8-75.8s-34.2-75.8-75.8-75.8c-30 0-55.9 17.9-68.2 43.4-14.3-48.5-59.2-84.3-112.2-84.3m0 45c39.9 0 71.7 31.8 71.7 71.7s-31.8 71.7-71.7 71.7-71.7-31.8-71.7-71.7 31.8-71.7 71.7-71.7m-180.2 41c17.2 0 30.7 13.5 30.7 30.7S93 286.7 75.8 286.7 45.1 273.2 45.1 256s13.4-30.7 30.7-30.7m360.4 0c17.2 0 30.7 13.5 30.7 30.7s-13.5 30.7-30.7 30.7-30.7-13.5-30.7-30.7 13.5-30.7 30.7-30.7" style="fill:#3784c9"/></svg>
|
||||
|
After Width: | Height: | Size: 739 B |
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="cm.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="33.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="771"
|
||||
inkscape:window-x="1"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g3349"
|
||||
transform="translate(0.11778981,0.45794291)">
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 8.236948,6.2914262 5.825001,0"
|
||||
id="path876" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.723058,6.2914262 1.45834,0"
|
||||
id="path880" />
|
||||
<a
|
||||
id="a3346">
|
||||
<path
|
||||
id="path884"
|
||||
d="m 10.353619,8.4080928 3.70833,0"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
inkscape:export-ydpi="376.57999" />
|
||||
</a>
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 7.839728,8.4080928 1.4583305,0"
|
||||
id="path888" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 10.353619,10.52476 3.70833,0"
|
||||
id="path892" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 7.839728,10.52476 1.4583305,0"
|
||||
id="path896" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 8.236948,12.641428 5.825001,0"
|
||||
id="path900" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.723058,12.641428 1.45834,0"
|
||||
id="path904" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ing.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="28.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
id="path7709"
|
||||
pointer-events="none"
|
||||
d="m 12.75799,13.997178 -2.270701,0 -4.9209009,-6.1558617 -1.42366,0 0,-2.0149069 2.31473,0 4.9230119,6.1558536 1.37752,0 0,-1.593474 3.119869,2.599882 -3.119869,2.601983 z m -2.47616,-4.7552751 1.09864,-1.3754256 1.37752,0 0,1.593475 3.119869,-2.5998829 -3.119869,-2.601983 0,1.593483 -2.270701,0 -1.4571904,1.8241102 z m -3.5979219,1.3649431 -1.11752,1.400578 -1.42366,0 0,2.014915 2.31473,0 1.4781699,-1.849278 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.20966817" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ns.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="23.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<rect
|
||||
y="6.5793304"
|
||||
x="6.1734986"
|
||||
height="6.6900792"
|
||||
width="7.6735892"
|
||||
id="rect8790"
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.40000001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:0.80000001, 0.4;stroke-dashoffset:3.44000006;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="node.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="16.847496"
|
||||
inkscape:cy="33.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccscccccccccccccccc"
|
||||
id="path1994"
|
||||
d="M 9.9921803,5.0101759 C 9.8538505,5.0057759 5.9970805,6.902049 5.9466504,6.999295 c -0.12117,0.233682 -0.9989,4.281827 -0.94731,4.369074 0.03,0.05065 0.66219,0.851861 1.40458,1.780327 l 1.3498201,1.688014 2.2211901,9.31e-4 2.2216404,9.31e-4 1.41321,-1.765731 1.41365,-1.765228 -0.49479,-2.1685759 C 14.256441,7.946271 14.012981,6.950423 13.988111,6.926433 13.918611,6.859553 10.067151,5.0126389 9.9921803,5.0102179 Z m 0.1961407,0.947753 0.90893,0.2635771 -0.90893,0.263576 -0.9089209,-0.263576 z m -0.9089209,0.36452 0.8511209,0.2532261 -0.004,1.183289 -0.8468109,-0.469347 z m 1.8178509,0 0,0.9671681 -0.84679,0.469347 -0.004,-1.183289 z M 8.8997705,7.937127 9.8087101,8.2007027 8.8997705,8.4642797 7.9908504,8.2007027 Z m 2.2087005,0 0.90894,0.2635757 -0.90894,0.263577 -0.90893,-0.263577 z m -3.1176206,0.3645197 0.8511202,0.252792 -0.004,1.1832908 -0.8468199,-0.468915 z m 1.8178597,0 0,0.9671678 -0.8468098,0.468915 -0.004,-1.1832908 z m 0.3908309,0 0.85113,0.252792 -0.004,1.1832908 -0.84682,-0.468915 z m 1.81787,0 0,0.9671678 -0.84682,0.468915 -0.004,-1.1832908 z m -3.4496605,1.515028 c 0.2706299,0.0096 0.0611,0.2819093 0.3684101,0.4279353 0.3277495,0.155764 0.3953995,-0.235354 0.6013395,0.06341 0.20599,0.298766 -0.18339,0.223239 -0.15443,0.584957 0.029,0.361718 0.40165,0.224858 0.24589,0.552606 -0.15575,0.327746 -0.28532,-0.04764 -0.5840895,0.158317 -0.2987401,0.205957 0.006,0.460208 -0.35546,0.489192 -0.3617401,0.02898 -0.1015001,-0.270447 -0.42924,-0.426208 -0.32775,-0.155765 -0.3953801,0.234921 -0.6013402,-0.06385 -0.2059599,-0.298767 0.1838299,-0.22281 0.15485,-0.584528 -0.029,-0.361718 -0.4016499,-0.224859 -0.24587,-0.552604 0.1557501,-0.327749 0.2848801,0.04764 0.5836502,-0.15832 0.2987898,-0.205956 -0.006,-0.4602083 0.3559099,-0.4891903 0.022499,-0.0018 0.0424,-0.0023 0.0604,-0.0018 z m 2.3359605,0.3627943 c 0.48335,0.01358 0.0146,0.467218 0.45596,0.664766 0.44144,0.197548 0.46714,-0.454103 0.79937,-0.102669 0.33221,0.351432 -0.31997,0.34064 -0.14753,0.792455 0.17243,0.451813 0.65163,0.0092 0.63802,0.49264 -0.0137,0.483411 -0.46723,0.01456 -0.66477,0.455977 -0.19755,0.441412 0.4541,0.467143 0.10266,0.799357 -0.35141,0.332212 -0.34021,-0.319974 -0.79202,-0.147534 -0.45183,0.172437 -0.009,0.65161 -0.49265,0.638019 -0.48339,-0.01358 -0.0146,-0.467216 -0.45596,-0.664764 -0.4414105,-0.197551 -0.4675805,0.454102 -0.7997909,0.102669 -0.3322097,-0.351431 0.3199804,-0.340209 0.14754,-0.792025 -0.17245,-0.451815 -0.6516296,-0.0092 -0.6380295,-0.492642 0.013699,-0.483408 0.4672095,-0.01499 0.6647795,-0.456405 0.1975204,-0.441414 -0.45411,-0.467143 -0.10269,-0.799357 0.3514505,-0.332213 0.3406505,0.319971 0.7924609,0.147534 0.45184,-0.17244 0.009,-0.651611 0.49265,-0.638021 z m -2.3148207,0.253655 c -0.2936499,9e-5 -0.5316098,0.238249 -0.5314498,0.531898 7.99e-5,0.293481 0.2379701,0.531377 0.5314498,0.531467 0.2936602,1.59e-4 0.5318202,-0.23781 0.5319,-0.531467 1.601e-4,-0.293825 -0.2380699,-0.532057 -0.5319,-0.531898 z m 2.2643607,0.480564 c -0.58689,-1.96e-4 -1.0627109,0.475614 -1.0625209,1.062502 5e-5,0.586719 0.4758009,1.062267 1.0625209,1.062071 0.58654,-5.8e-5 1.06201,-0.475531 1.06206,-1.062071 1.9e-4,-0.586708 -0.47535,-1.062444 -1.06206,-1.062502 z"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#eeeeee;stroke-width:0;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:11.23642349;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="pod.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="33.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g3341"
|
||||
transform="translate(0.12766661,0.35147801)">
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="M 6.2617914,7.036086 9.8826317,5.986087 13.503462,7.036086 9.8826317,8.086087 Z"
|
||||
id="path910" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 6.2617914,7.43817 0,3.852778 3.3736103,1.868749 0.0167,-4.713193 z"
|
||||
id="path912" />
|
||||
<path
|
||||
inkscape:export-ydpi="376.57999"
|
||||
inkscape:export-xdpi="376.57999"
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 13.503462,7.43817 0,3.852778 -3.37361,1.868749 -0.0167,-4.713193 z"
|
||||
id="path914" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="secret.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="28.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g3347"
|
||||
transform="translate(0.05710921,0.77487342)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 10.414299,8.0912253 3.708331,0"
|
||||
id="path922" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79400003;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 8.2976282,5.9745582 5.8250018,0"
|
||||
id="path930" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.7837382,5.9745582 1.45834,0"
|
||||
id="path934" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#326ce5;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 7.7183782,8.4321603 0,0 c 0,-1.1667021 1.0110897,-2.1125011 2.2583305,-2.1125011 1.2472403,0 2.2583303,0.945799 2.2583303,2.1125011 l -1.05624,0 0,0 c 0,-0.583351 -0.5382,-1.0562501 -1.2020903,-1.0562501 -0.6638904,0 -1.2020808,0.4728991 -1.2020808,1.0562501 z"
|
||||
id="path936" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 10.414299,10.207893 3.708331,0"
|
||||
id="path942" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 8.2976282,12.32456 5.8250018,0"
|
||||
id="path950" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.7837382,12.32456 1.45834,0"
|
||||
id="path954" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#326ce5;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 7.1622182,8.3691063 5.6166608,0 0,3.6583337 -5.6166608,0 z"
|
||||
id="path956" />
|
||||
<circle
|
||||
r="0.55515254"
|
||||
cy="10.198272"
|
||||
cx="9.9705448"
|
||||
id="path7280"
|
||||
style="opacity:1;fill:#326ce5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26478875;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:0.52957746, 0.26478873;stroke-dashoffset:5.01860619;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="svc.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-2.090004"
|
||||
inkscape:cy="28.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g3345"
|
||||
transform="translate(0.09238801,0.66897746)">
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 4.4949896,11.260826 2.9083311,0 0,2.041667 -2.9083311,0 z"
|
||||
id="path964" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 8.4637407,11.260826 2.9083303,0 0,2.041667 -2.9083303,0 z"
|
||||
id="path966" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 12.432491,11.260826 2.90833,0 0,2.041667 -2.90833,0 z"
|
||||
id="path968" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 7.6137407,5.2082921 4.6083303,0 0,2.041667 -4.6083303,0 z"
|
||||
id="path970" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 9.9179005,7.2499601 0,2.005449 -3.966671,0 0,2.0028859"
|
||||
id="path978" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.52899998;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 9.9179005,7.2499601 0,2.005449 3.9666705,0 0,2.0028859"
|
||||
id="path986" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 9.9095538,7.2512251 0,2.005449 0.0167,0 0,2.0028859"
|
||||
id="path982" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="18.035334mm"
|
||||
height="17.500378mm"
|
||||
viewBox="0 0 18.035334 17.500378"
|
||||
version="1.1"
|
||||
id="svg13826"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="pv.svg">
|
||||
<defs
|
||||
id="defs13820" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="-12.090004"
|
||||
inkscape:cy="28.752239"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="775"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata13823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.99262638,-1.174181)">
|
||||
<g
|
||||
id="g70"
|
||||
transform="matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)">
|
||||
<path
|
||||
inkscape:export-ydpi="250.55"
|
||||
inkscape:export-xdpi="250.55"
|
||||
inkscape:export-filename="new.png"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3055"
|
||||
d="m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z"
|
||||
style="fill:#326ce5;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3054-2-9"
|
||||
d="M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;marker:none;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g3341"
|
||||
transform="translate(-0.18983289,0.49258906)">
|
||||
<path
|
||||
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.5709614,7.9105849 0,0 c 0,0.621121 2.0725401,1.124639 4.6291706,1.124639 2.556609,0 4.629159,-0.503518 4.629159,-1.124639 l 0,3.0423911 c 0,0.62112 -2.07255,1.124638 -4.629159,1.124638 -2.5566305,0 -4.6291706,-0.503518 -4.6291706,-1.124638 z"
|
||||
id="path1114" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-linecap:square;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 5.5709614,7.9105849 0,0 c 0,-0.621119 2.0725401,-1.124637 4.6291706,-1.124637 2.556609,0 4.629159,0.503518 4.629159,1.124637 l 0,0 c 0,0.621121 -2.07255,1.124639 -4.629159,1.124639 -2.5566305,0 -4.6291706,-0.503518 -4.6291706,-1.124639 z"
|
||||
id="path1116" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#326ce5;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 14.829291,7.9105849 0,0 c 0,0.621121 -2.07255,1.124639 -4.629159,1.124639 -2.5566205,0 -4.6291706,-0.503518 -4.6291706,-1.124639 l 0,0 c 0,-0.621119 2.0725501,-1.124637 4.6291706,-1.124637 2.556609,0 4.629159,0.503518 4.629159,1.124637 l 0,3.0423911 c 0,0.62112 -2.07255,1.124638 -4.629159,1.124638 -2.5566205,0 -4.6291706,-0.503518 -4.6291706,-1.124638 l 0,-3.0423911"
|
||||
id="path1120" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -7,11 +7,11 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
|
||||
import {
|
||||
createWSClient,
|
||||
httpBatchStreamLink,
|
||||
httpLink,
|
||||
isNonJsonSerializable,
|
||||
loggerLink,
|
||||
splitLink,
|
||||
unstable_httpBatchStreamLink,
|
||||
wsLink,
|
||||
} from "@trpc/client";
|
||||
import superjson from "superjson";
|
||||
@@ -20,8 +20,7 @@ import type { SuperJSONResult } from "superjson";
|
||||
import type { AppRouter } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/shared";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { env } from "@homarr/common/env";
|
||||
|
||||
const getWebSocketProtocol = () => {
|
||||
// window is not defined on server side
|
||||
@@ -66,7 +65,7 @@ export function TRPCReactProvider(props: PropsWithChildren) {
|
||||
links: [
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
process.env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error),
|
||||
env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error),
|
||||
}),
|
||||
splitLink({
|
||||
condition: ({ type }) => type === "subscription",
|
||||
@@ -91,7 +90,7 @@ export function TRPCReactProvider(props: PropsWithChildren) {
|
||||
url: getTrpcUrl(),
|
||||
headers: createHeadersCallbackForSource("nextjs-react (form-data)"),
|
||||
}),
|
||||
false: unstable_httpBatchStreamLink({
|
||||
false: httpBatchStreamLink({
|
||||
transformer: superjson,
|
||||
url: getTrpcUrl(),
|
||||
maxURLLength: 2083, // Suggested by tRPC: https://trpc.io/docs/client/links/httpBatchLink#setting-a-maximum-url-length
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { CustomPasswordInput } from "@homarr/ui";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { userRegistrationSchema } from "@homarr/validation/user";
|
||||
|
||||
interface RegistrationFormProps {
|
||||
invite: {
|
||||
@@ -22,7 +22,7 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
|
||||
const t = useScopedI18n("user");
|
||||
const router = useRouter();
|
||||
const { mutate, isPending } = clientApi.user.register.useMutation();
|
||||
const form = useZodForm(validation.user.registration, {
|
||||
const form = useZodForm(userRegistrationSchema, {
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
@@ -30,7 +30,7 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: z.infer<typeof validation.user.registration>) => {
|
||||
const handleSubmit = (values: z.infer<typeof userRegistrationSchema>) => {
|
||||
mutate(
|
||||
{
|
||||
...values,
|
||||
|
||||
@@ -63,7 +63,8 @@ export default async function InviteUsagePage(props: InviteUsagePageProps) {
|
||||
<RegistrationForm invite={invite} />
|
||||
</Card>
|
||||
<Text size="xs" c="gray.5" ta="center">
|
||||
{t("description", { username: invite.creator.name })}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
{t("description", { username: invite.creator.name! })}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { z } from "zod";
|
||||
@@ -13,7 +13,9 @@ import type { useForm } from "@homarr/form";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { userSignInSchema } from "@homarr/validation/user";
|
||||
|
||||
type Provider = "credentials" | "ldap" | "oidc";
|
||||
|
||||
interface LoginFormProps {
|
||||
providers: string[];
|
||||
@@ -22,10 +24,12 @@ interface LoginFormProps {
|
||||
callbackUrl: string;
|
||||
}
|
||||
|
||||
const extendedValidation = validation.user.signIn.extend({ provider: z.enum(["credentials", "ldap"]) });
|
||||
const extendedValidation = userSignInSchema.extend({ provider: z.enum(["credentials", "ldap"]) });
|
||||
|
||||
export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => {
|
||||
const t = useScopedI18n("user");
|
||||
const searchParams = useSearchParams();
|
||||
const isError = searchParams.has("error");
|
||||
const router = useRouter();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const form = useZodForm(extendedValidation, {
|
||||
@@ -39,22 +43,34 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
|
||||
const credentialInputsVisible = providers.includes("credentials") || providers.includes("ldap");
|
||||
|
||||
const onSuccess = useCallback(
|
||||
async (response: Awaited<ReturnType<typeof signIn>>) => {
|
||||
if (response && (!response.ok || response.error)) {
|
||||
async (provider: Provider, response: Awaited<ReturnType<typeof signIn>>) => {
|
||||
if (!response.ok || response.error) {
|
||||
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
||||
throw response.error;
|
||||
}
|
||||
|
||||
if (provider === "oidc") {
|
||||
if (!response.url) {
|
||||
showErrorNotification({
|
||||
title: t("action.login.notification.error.title"),
|
||||
message: t("action.login.notification.error.message"),
|
||||
autoClose: 10000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(response.url);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessNotification({
|
||||
title: t("action.login.notification.success.title"),
|
||||
message: t("action.login.notification.success.message"),
|
||||
});
|
||||
|
||||
// Redirect to the callback URL if the response is defined and comes from a credentials provider (ldap or credentials). oidc is redirected automatically.
|
||||
if (response) {
|
||||
await revalidatePathActionAsync("/");
|
||||
router.push(callbackUrl);
|
||||
}
|
||||
await revalidatePathActionAsync("/");
|
||||
router.push(callbackUrl);
|
||||
},
|
||||
[t, router, callbackUrl],
|
||||
);
|
||||
@@ -70,14 +86,14 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
|
||||
}, [t]);
|
||||
|
||||
const signInAsync = useCallback(
|
||||
async (provider: string, options?: Parameters<typeof signIn>[1]) => {
|
||||
async (provider: Provider, options?: Parameters<typeof signIn>[1]) => {
|
||||
setIsPending(true);
|
||||
await signIn(provider, {
|
||||
...options,
|
||||
redirect: false,
|
||||
callbackUrl: new URL(callbackUrl, window.location.href).href,
|
||||
})
|
||||
.then(onSuccess)
|
||||
.then((response) => onSuccess(provider, response))
|
||||
.catch(onError);
|
||||
},
|
||||
[setIsPending, onSuccess, onError, callbackUrl],
|
||||
@@ -86,11 +102,12 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c
|
||||
const isLoginInProgress = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) return;
|
||||
if (isOidcAutoLoginEnabled && !isPending && !isLoginInProgress.current) {
|
||||
isLoginInProgress.current = true;
|
||||
void signInAsync("oidc");
|
||||
}
|
||||
}, [signInAsync, isOidcAutoLoginEnabled, isPending]);
|
||||
}, [signInAsync, isOidcAutoLoginEnabled, isPending, isError]);
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Box, LoadingOverlay, Stack } from "@mantine/core";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
|
||||
|
||||
import { BoardCategorySection } from "~/components/board/sections/category-section";
|
||||
import { BoardEmptySection } from "~/components/board/sections/empty-section";
|
||||
import { BoardBackgroundVideo } from "~/components/layout/background";
|
||||
import { fullHeightWithoutHeaderAndFooter } from "~/constants";
|
||||
import { useIsBoardReady, useRequiredBoard } from "./_context";
|
||||
import { useIsBoardReady } from "./_ready-context";
|
||||
|
||||
let boardName: string | null = null;
|
||||
|
||||
@@ -42,6 +43,7 @@ export const useUpdateBoard = () => {
|
||||
|
||||
export const ClientBoard = () => {
|
||||
const board = useRequiredBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
const isReady = useIsBoardReady();
|
||||
|
||||
const fullWidthSortedSections = board.sections
|
||||
@@ -62,9 +64,10 @@ export const ClientBoard = () => {
|
||||
<Stack ref={ref} h="100%" style={{ visibility: isReady ? "visible" : "hidden" }}>
|
||||
{fullWidthSortedSections.map((section) =>
|
||||
section.kind === "empty" ? (
|
||||
<BoardEmptySection key={section.id} section={section} />
|
||||
// Unique keys per layout to always reinitialize the gridstack
|
||||
<BoardEmptySection key={`${currentLayoutId}-${section.id}`} section={section} />
|
||||
) : (
|
||||
<BoardCategorySection key={section.id} section={section} />
|
||||
<BoardCategorySection key={`${currentLayoutId}-${section.id}`} section={section} />
|
||||
),
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -4,16 +4,22 @@ import { TRPCError } from "@trpc/server";
|
||||
// Placed here because gridstack styles are used for board content
|
||||
import "~/styles/gridstack.scss";
|
||||
|
||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||
|
||||
import { getQueryClient } from "@homarr/api/server";
|
||||
import { IntegrationProvider } from "@homarr/auth/client";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { getIntegrationsWithPermissionsAsync } from "@homarr/auth/server";
|
||||
import { isNullOrWhitespace } from "@homarr/common";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
import { prefetchForKindAsync } from "@homarr/widgets/prefetch";
|
||||
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { createBoardLayout } from "../_layout-creator";
|
||||
import type { Board } from "../_types";
|
||||
import { ClientBoard } from "./_client";
|
||||
import type { Board, Item } from "../_types";
|
||||
import { DynamicClientBoard } from "./_dynamic-client";
|
||||
import { BoardContentHeaderActions } from "./_header-actions";
|
||||
|
||||
export type Params = Record<string, unknown>;
|
||||
@@ -31,19 +37,41 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
|
||||
getInitialBoardAsync: getInitialBoard,
|
||||
}),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
page: async () => {
|
||||
page: async ({ params }: { params: Promise<TParams> }) => {
|
||||
const session = await auth();
|
||||
const integrations = await getIntegrationsWithPermissionsAsync(session);
|
||||
|
||||
const board = await getInitialBoard(await params);
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
// Prefetch item data
|
||||
const itemsMap = board.items.reduce((acc, item) => {
|
||||
const existing = acc.get(item.kind);
|
||||
if (existing) {
|
||||
existing.push(item);
|
||||
} else {
|
||||
acc.set(item.kind, [item]);
|
||||
}
|
||||
return acc;
|
||||
}, new Map<WidgetKind, Item[]>());
|
||||
|
||||
for (const [kind, items] of itemsMap) {
|
||||
await prefetchForKindAsync(kind, queryClient, items).catch((error) => {
|
||||
logger.error(new Error("Failed to prefetch widget", { cause: error }));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<IntegrationProvider integrations={integrations}>
|
||||
<ClientBoard />
|
||||
</IntegrationProvider>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<IntegrationProvider integrations={integrations}>
|
||||
<DynamicClientBoard />
|
||||
</IntegrationProvider>
|
||||
</HydrationBoundary>
|
||||
);
|
||||
},
|
||||
generateMetadataAsync: async ({ params }: { params: TParams }): Promise<Metadata> => {
|
||||
generateMetadataAsync: async ({ params }: { params: Promise<TParams> }): Promise<Metadata> => {
|
||||
try {
|
||||
const board = await getInitialBoard(params);
|
||||
const board = await getInitialBoard(await params);
|
||||
const t = await getI18n();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useRequiredBoard } from "./_context";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
|
||||
export const CustomCss = () => {
|
||||
const board = useRequiredBoard();
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
export const DynamicClientBoard = dynamic(() => import("./_client").then((mod) => mod.ClientBoard), {
|
||||
ssr: false,
|
||||
});
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import type { MouseEvent } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Group, Menu } from "@mantine/core";
|
||||
import { Group, Menu, ScrollArea } from "@mantine/core";
|
||||
import { useHotkeys } from "@mantine/hooks";
|
||||
import {
|
||||
IconBox,
|
||||
@@ -20,19 +19,22 @@ import {
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useEditMode } from "@homarr/boards/edit-mode";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { AppSelectModal } from "@homarr/modals-collection";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { useItemActions } from "~/components/board/items/item-actions";
|
||||
import { ItemSelectModal } from "~/components/board/items/item-select-modal";
|
||||
import { useBoardPermissions } from "~/components/board/permissions/client";
|
||||
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
|
||||
import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal";
|
||||
import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions";
|
||||
import { HeaderButton } from "~/components/layout/header/button";
|
||||
import { env } from "~/env";
|
||||
import { useEditMode, useRequiredBoard } from "./_context";
|
||||
|
||||
export const BoardContentHeaderActions = () => {
|
||||
const [isEditMode] = useEditMode();
|
||||
@@ -61,8 +63,10 @@ export const BoardContentHeaderActions = () => {
|
||||
const AddMenu = () => {
|
||||
const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal);
|
||||
const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal);
|
||||
const { openModal: openAppSelectModal } = useModalAction(AppSelectModal);
|
||||
const { addCategoryToEnd } = useCategoryActions();
|
||||
const { addDynamicSection } = useDynamicSectionActions();
|
||||
const { createItem } = useItemActions();
|
||||
const t = useI18n();
|
||||
|
||||
const handleAddCategory = useCallback(
|
||||
@@ -89,6 +93,17 @@ const AddMenu = () => {
|
||||
openItemSelectModal();
|
||||
}, [openItemSelectModal]);
|
||||
|
||||
const handleSelectApp = useCallback(() => {
|
||||
openAppSelectModal({
|
||||
onSelect: (appId) => {
|
||||
createItem({
|
||||
kind: "app",
|
||||
options: { appId },
|
||||
});
|
||||
},
|
||||
});
|
||||
}, [openAppSelectModal, createItem]);
|
||||
|
||||
return (
|
||||
<Menu position="bottom-end" withArrow>
|
||||
<Menu.Target>
|
||||
@@ -100,10 +115,14 @@ const AddMenu = () => {
|
||||
</HeaderButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown style={{ transform: "translate(-3px, 0)" }}>
|
||||
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectItem}>
|
||||
<Menu.Item leftSection={<IconResize size={20} />} onClick={handleSelectItem}>
|
||||
{t("item.action.create")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectApp}>
|
||||
{t("app.action.add")}
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Item leftSection={<IconBoxAlignTop size={20} />} onClick={handleAddCategory}>
|
||||
@@ -119,7 +138,7 @@ const AddMenu = () => {
|
||||
};
|
||||
|
||||
const EditModeMenu = () => {
|
||||
const [isEditMode, setEditMode] = useEditMode();
|
||||
const [isEditMode, { open, close }] = useEditMode();
|
||||
const board = useRequiredBoard();
|
||||
const utils = clientApi.useUtils();
|
||||
const t = useScopedI18n("board.action.edit");
|
||||
@@ -131,7 +150,7 @@ const EditModeMenu = () => {
|
||||
});
|
||||
void utils.board.getBoardByName.invalidate({ name: board.name });
|
||||
void revalidatePathActionAsync(`/boards/${board.name}`);
|
||||
setEditMode(false);
|
||||
close();
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
@@ -143,8 +162,8 @@ const EditModeMenu = () => {
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
if (isEditMode) return saveBoard(board);
|
||||
setEditMode(true);
|
||||
}, [board, isEditMode, saveBoard, setEditMode]);
|
||||
open();
|
||||
}, [board, isEditMode, saveBoard, open]);
|
||||
|
||||
useHotkeys([["mod+e", toggle]]);
|
||||
usePreventLeaveWithDirty(isEditMode);
|
||||
@@ -167,32 +186,39 @@ const SelectBoardsMenu = () => {
|
||||
</HeaderButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
|
||||
{boards.map((board) => (
|
||||
<Menu.Item
|
||||
key={board.id}
|
||||
component={Link}
|
||||
href={`/boards/${board.name}`}
|
||||
leftSection={<IconLayoutBoard size={20} />}
|
||||
>
|
||||
{board.name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
<ScrollArea.Autosize mah={300}>
|
||||
{boards.map((board) => (
|
||||
<Menu.Item
|
||||
key={board.id}
|
||||
component={Link}
|
||||
href={`/boards/${board.name}`}
|
||||
leftSection={<IconLayoutBoard size={20} />}
|
||||
>
|
||||
{board.name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</ScrollArea.Autosize>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const anchorSelector = "a[href]:not([target='_blank'])";
|
||||
const usePreventLeaveWithDirty = (isDirty: boolean) => {
|
||||
const t = useI18n();
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
||||
if (!isDirty) return;
|
||||
|
||||
const handleClick = (event: Event) => {
|
||||
const target = (event.target as HTMLElement).closest("a");
|
||||
|
||||
if (!target) return;
|
||||
if (!isDirty) return;
|
||||
if (!target) {
|
||||
console.warn("No anchor element found for click event", event);
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -209,33 +235,29 @@ const usePreventLeaveWithDirty = (isDirty: boolean) => {
|
||||
};
|
||||
|
||||
const handlePopState = (event: Event) => {
|
||||
if (isDirty) {
|
||||
window.history.pushState(null, document.title, window.location.href);
|
||||
event.preventDefault();
|
||||
} else {
|
||||
window.history.back();
|
||||
}
|
||||
window.history.pushState(null, document.title, window.location.href);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (!isDirty) return;
|
||||
if (env.NODE_ENV === "development") return; // Allow to reload in development
|
||||
|
||||
event.preventDefault();
|
||||
event.returnValue = true;
|
||||
};
|
||||
|
||||
document.querySelectorAll("a").forEach((link) => {
|
||||
link.addEventListener("click", handleClick as never);
|
||||
const anchors = document.querySelectorAll(anchorSelector);
|
||||
anchors.forEach((link) => {
|
||||
link.addEventListener("click", handleClick);
|
||||
});
|
||||
window.addEventListener("popstate", handlePopState);
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
|
||||
return () => {
|
||||
document.querySelectorAll("a").forEach((link) => {
|
||||
link.removeEventListener("click", handleClick as never);
|
||||
window.removeEventListener("popstate", handlePopState);
|
||||
anchors.forEach((link) => {
|
||||
link.removeEventListener("click", handleClick);
|
||||
});
|
||||
window.removeEventListener("popstate", handlePopState);
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
|
||||
const BoardReadyContext = createContext<{
|
||||
isReady: boolean;
|
||||
markAsReady: (id: string) => void;
|
||||
} | null>(null);
|
||||
|
||||
export const BoardReadyProvider = ({ children }: PropsWithChildren) => {
|
||||
const pathname = usePathname();
|
||||
const utils = clientApi.useUtils();
|
||||
const board = useRequiredBoard();
|
||||
const [readySections, setReadySections] = useState<string[]>([]);
|
||||
|
||||
// Reset sections required for ready state
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setReadySections([]);
|
||||
};
|
||||
}, [pathname, utils]);
|
||||
|
||||
useEffect(() => {
|
||||
setReadySections((previous) => previous.filter((id) => board.sections.some((section) => section.id === id)));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [board.sections.length, setReadySections]);
|
||||
|
||||
const markAsReady = useCallback((id: string) => {
|
||||
setReadySections((previous) => (previous.includes(id) ? previous : [...previous, id]));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BoardReadyContext.Provider
|
||||
value={{
|
||||
isReady: board.sections.length === readySections.length,
|
||||
markAsReady,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BoardReadyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMarkSectionAsReady = () => {
|
||||
const context = useContext(BoardReadyContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("BoardReadyProvider is required");
|
||||
}
|
||||
|
||||
return context.markAsReady;
|
||||
};
|
||||
|
||||
export const useIsBoardReady = () => {
|
||||
const context = useContext(BoardReadyContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("BoardReadyProvider is required");
|
||||
}
|
||||
|
||||
return context.isReady;
|
||||
};
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { MantineColorsTuple } from "@mantine/core";
|
||||
import { createTheme, darken, lighten, MantineProvider } from "@mantine/core";
|
||||
import { colorsTuple, createTheme, darken, lighten, MantineProvider, rem } from "@mantine/core";
|
||||
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import type { ColorScheme } from "@homarr/definitions";
|
||||
|
||||
import { useColorSchemeManager } from "../../_client-providers/mantine";
|
||||
import { useRequiredBoard } from "./_context";
|
||||
|
||||
export const BoardMantineProvider = ({
|
||||
children,
|
||||
@@ -20,9 +20,15 @@ export const BoardMantineProvider = ({
|
||||
colors: {
|
||||
primaryColor: generateColors(board.primaryColor),
|
||||
secondaryColor: generateColors(board.secondaryColor),
|
||||
iconColor: board.iconColor ? generateColors(board.iconColor) : colorsTuple("#000000"),
|
||||
},
|
||||
primaryColor: "primaryColor",
|
||||
autoContrast: true,
|
||||
fontSizes: {
|
||||
"2xl": rem(24),
|
||||
"3xl": rem(28),
|
||||
"4xl": rem(36),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Group,
|
||||
InputWrapper,
|
||||
isLightColor,
|
||||
Select,
|
||||
Slider,
|
||||
Stack,
|
||||
Text,
|
||||
@@ -19,7 +20,7 @@ import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||
|
||||
import type { Board } from "../../_types";
|
||||
import { generateColors } from "../../(content)/_theme";
|
||||
@@ -34,11 +35,13 @@ const hexRegex = /^#[0-9a-fA-F]{6}$/;
|
||||
const progressPercentageLabel = (value: number) => `${value}%`;
|
||||
|
||||
export const ColorSettingsContent = ({ board }: Props) => {
|
||||
const form = useZodForm(validation.board.savePartialSettings, {
|
||||
const form = useZodForm(boardSavePartialSettingsSchema, {
|
||||
initialValues: {
|
||||
primaryColor: board.primaryColor,
|
||||
secondaryColor: board.secondaryColor,
|
||||
opacity: board.opacity,
|
||||
iconColor: board.iconColor ?? "",
|
||||
itemRadius: board.itemRadius,
|
||||
},
|
||||
});
|
||||
const [showPreview, { toggle }] = useDisclosure(false);
|
||||
@@ -98,9 +101,29 @@ 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")}
|
||||
/>
|
||||
<Select
|
||||
label={t("board.field.itemRadius.label")}
|
||||
description={t("board.field.itemRadius.description")}
|
||||
data={[
|
||||
{ label: t("board.field.itemRadius.option.xs"), value: "xs" },
|
||||
{ label: t("board.field.itemRadius.option.sm"), value: "sm" },
|
||||
{ label: t("board.field.itemRadius.option.md"), value: "md" },
|
||||
{ label: t("board.field.itemRadius.option.lg"), value: "lg" },
|
||||
{ label: t("board.field.itemRadius.option.xl"), value: "xl" },
|
||||
]}
|
||||
{...form.getInputProps("itemRadius")}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -1,14 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Grid, Group, Stack, TextInput } from "@mantine/core";
|
||||
import { startTransition } from "react";
|
||||
import { ActionIcon, Autocomplete, Button, Center, Grid, Group, Popover, Stack, Text } from "@mantine/core";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { IconPhotoOff, IconUpload } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { UploadMedia } from "@homarr/forms-collection";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import type { SelectItemWithDescriptionBadge } from "@homarr/ui";
|
||||
import { SelectWithDescriptionBadge } from "@homarr/ui";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||
|
||||
import type { Board } from "../../_types";
|
||||
import { useSavePartialSettingsMutation } from "./_shared";
|
||||
@@ -18,8 +24,9 @@ interface Props {
|
||||
}
|
||||
export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
const t = useI18n();
|
||||
const { data: session } = useSession();
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(validation.board.savePartialSettings, {
|
||||
const form = useZodForm(boardSavePartialSettingsSchema, {
|
||||
initialValues: {
|
||||
backgroundImageUrl: board.backgroundImageUrl ?? "",
|
||||
backgroundImageAttachment: board.backgroundImageAttachment,
|
||||
@@ -28,6 +35,16 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(form.values.backgroundImageUrl, 200);
|
||||
const medias = clientApi.media.getPaginated.useQuery({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
includeFromAllUsers: true,
|
||||
search: debouncedSearch ?? "",
|
||||
});
|
||||
const images = medias.data?.items.filter((media) => media.contentType.startsWith("image/")) ?? [];
|
||||
const imageMap = new Map(images.map((image) => [`/api/user-medias/${image.id}`, image]));
|
||||
|
||||
const backgroundImageAttachmentData = useBackgroundOptionData(
|
||||
"backgroundImageAttachment",
|
||||
backgroundImageAttachments,
|
||||
@@ -47,10 +64,76 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
<Stack>
|
||||
<Grid>
|
||||
<Grid.Col span={12}>
|
||||
<TextInput
|
||||
label={t("board.field.backgroundImageUrl.label")}
|
||||
{...form.getInputProps("backgroundImageUrl")}
|
||||
/>
|
||||
<Group wrap="nowrap" gap="xs" w="100%" align="start">
|
||||
<Autocomplete
|
||||
flex={1}
|
||||
leftSection={
|
||||
form.values.backgroundImageUrl &&
|
||||
form.values.backgroundImageUrl.trim().length >= 2 && (
|
||||
<Popover width={300} withArrow>
|
||||
<Popover.Target>
|
||||
<Center h="100%">
|
||||
<ImagePreview src={form.values.backgroundImageUrl} w={20} h={20} />
|
||||
</Center>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<ImagePreview src={form.values.backgroundImageUrl} w="100%" />
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
// We filter it on the server
|
||||
filter={({ options }) => options}
|
||||
label={t("board.field.backgroundImageUrl.label")}
|
||||
placeholder={`${t("board.field.backgroundImageUrl.placeholder")}...`}
|
||||
renderOption={({ option }) => {
|
||||
const current = imageMap.get(option.value);
|
||||
if (!current) return null;
|
||||
|
||||
return (
|
||||
<Group gap="sm">
|
||||
<ImagePreview src={option.value} w={20} h={20} />
|
||||
<Stack gap={0}>
|
||||
<Text size="sm">{current.name}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{option.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}}
|
||||
data={[
|
||||
{
|
||||
group: t("board.field.backgroundImageUrl.group.your"),
|
||||
items: images
|
||||
.filter((media) => media.creatorId === session?.user.id)
|
||||
.map((media) => `/api/user-medias/${media.id}`),
|
||||
},
|
||||
{
|
||||
group: t("board.field.backgroundImageUrl.group.other"),
|
||||
items: images
|
||||
.filter((media) => media.creatorId !== session?.user.id)
|
||||
.map((media) => `/api/user-medias/${media.id}`),
|
||||
},
|
||||
]}
|
||||
{...form.getInputProps("backgroundImageUrl")}
|
||||
/>
|
||||
{session?.user.permissions.includes("media-upload") && (
|
||||
<UploadMedia
|
||||
onSuccess={({ url }) =>
|
||||
startTransition(() => {
|
||||
form.setFieldValue("backgroundImageUrl", url);
|
||||
})
|
||||
}
|
||||
>
|
||||
{({ onClick, loading }) => (
|
||||
<ActionIcon onClick={onClick} loading={loading} mt={24} size={36} variant="default">
|
||||
<IconUpload size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</UploadMedia>
|
||||
)}
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<SelectWithDescriptionBadge
|
||||
@@ -76,7 +159,7 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
</Grid>
|
||||
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -85,6 +168,21 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface ImagePreviewProps {
|
||||
src: string;
|
||||
w: string | number;
|
||||
h?: string | number;
|
||||
}
|
||||
|
||||
const ImagePreview = ({ src, w, h }: ImagePreviewProps) => {
|
||||
if (!["/", "http://", "https://"].some((prefix) => src.startsWith(prefix))) {
|
||||
return <IconPhotoOff size={w} />;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
return <img src={src} alt="preview image" style={{ width: w, height: h, objectFit: "contain" }} />;
|
||||
};
|
||||
|
||||
type BackgroundImageKey = "backgroundImageAttachment" | "backgroundImageSize" | "backgroundImageRepeat";
|
||||
|
||||
type inferOptions<TKey extends BackgroundImageKey> = TranslationObject["board"]["field"][TKey]["option"];
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Group, Stack, Switch } from "@mantine/core";
|
||||
|
||||
import { useForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { Board } from "../../_types";
|
||||
import { useSavePartialSettingsMutation } from "./_shared";
|
||||
|
||||
interface Props {
|
||||
board: Board;
|
||||
}
|
||||
|
||||
export const BehaviorSettingsContent = ({ board }: Props) => {
|
||||
const t = useI18n();
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
disableStatus: board.disableStatus,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
savePartialSettings({
|
||||
id: board.id,
|
||||
...values,
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<Switch
|
||||
label={t("board.field.disableStatus.label")}
|
||||
description={t("board.field.disableStatus.description")}
|
||||
{...form.getInputProps("disableStatus", { type: "checkbox" })}
|
||||
/>
|
||||
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const CustomCssSettingsContent = ({ board }: Props) => {
|
||||
</Alert>
|
||||
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useRouter } from "next/navigation";
|
||||
import { Button, Divider, Group, Stack, Text } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { BoardRenameModal } from "~/components/board/modals/board-rename-modal";
|
||||
import { useRequiredBoard } from "../../(content)/_context";
|
||||
import classes from "./danger.module.css";
|
||||
|
||||
export const DangerZoneSettingsContent = ({ hideVisibility }: { hideVisibility: boolean }) => {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Button, Grid, Group, Loader, Stack, TextInput, Tooltip } from "@mantine/core";
|
||||
import { Button, Grid, Group, Loader, Stack, TextInput } from "@mantine/core";
|
||||
import { useDebouncedValue, useDocumentTitle, useFavicon } from "@mantine/hooks";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { IconPicker } from "@homarr/forms-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import type { Board } from "../../_types";
|
||||
import { useUpdateBoard } from "../../(content)/_client";
|
||||
import { useSavePartialSettingsMutation } from "./_shared";
|
||||
|
||||
interface Props {
|
||||
@@ -28,7 +28,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(
|
||||
validation.board.savePartialSettings
|
||||
boardSavePartialSettingsSchema
|
||||
.pick({
|
||||
pageTitle: true,
|
||||
logoImageUrl: true,
|
||||
@@ -52,9 +52,9 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
},
|
||||
);
|
||||
|
||||
useLogoPreview(form.values.logoImageUrl);
|
||||
useFaviconPreview(form.values.faviconImageUrl);
|
||||
const metaTitleStatus = useMetaTitlePreview(form.values.metaTitle);
|
||||
const faviconStatus = useFaviconPreview(form.values.faviconImageUrl);
|
||||
const logoStatus = useLogoPreview(form.values.logoImageUrl);
|
||||
|
||||
// Cleanup for not applied changes of the page title and logo image URL
|
||||
useEffect(() => {
|
||||
@@ -94,29 +94,29 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
<TextInput
|
||||
label={t("board.field.metaTitle.label")}
|
||||
placeholder={createMetaTitle(t("board.content.metaTitle", { boardName: board.name }))}
|
||||
rightSection={<PendingOrInvalidIndicator {...metaTitleStatus} />}
|
||||
rightSection={metaTitleStatus.isPending && <Loader size="xs" />}
|
||||
{...form.getInputProps("metaTitle")}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ xs: 12, md: 6 }}>
|
||||
<TextInput
|
||||
<IconPicker
|
||||
{...form.getInputProps("logoImageUrl")}
|
||||
label={t("board.field.logoImageUrl.label")}
|
||||
placeholder="/logo/logo.png"
|
||||
rightSection={<PendingOrInvalidIndicator {...logoStatus} />}
|
||||
{...form.getInputProps("logoImageUrl")}
|
||||
withAsterisk={false}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ xs: 12, md: 6 }}>
|
||||
<TextInput
|
||||
<IconPicker
|
||||
{...form.getInputProps("faviconImageUrl")}
|
||||
label={t("board.field.faviconImageUrl.label")}
|
||||
placeholder="/logo/logo.png"
|
||||
rightSection={<PendingOrInvalidIndicator {...faviconStatus} />}
|
||||
{...form.getInputProps("faviconImageUrl")}
|
||||
withAsterisk={false}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -125,40 +125,16 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PendingOrInvalidIndicator = ({ isPending, isInvalid }: { isPending: boolean; isInvalid?: boolean }) => {
|
||||
const t = useI18n();
|
||||
|
||||
if (isInvalid) {
|
||||
return (
|
||||
<Tooltip multiline w={220} label={t("board.setting.section.general.unrecognizedLink")}>
|
||||
<IconAlertTriangle size="1rem" color="red" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return <Loader size="xs" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const useLogoPreview = (url: string | null) => {
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
const [logoDebounced] = useDebouncedValue(url ?? "", 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (!logoDebounced.includes(".") && logoDebounced.length >= 1) return;
|
||||
updateBoard((previous) => ({
|
||||
...previous,
|
||||
logoImageUrl: logoDebounced.length >= 1 ? logoDebounced : null,
|
||||
}));
|
||||
}, [logoDebounced, updateBoard]);
|
||||
|
||||
return {
|
||||
isPending: (url ?? "") !== logoDebounced,
|
||||
isInvalid: logoDebounced.length >= 1 && !logoDebounced.includes("."),
|
||||
};
|
||||
};
|
||||
|
||||
const useMetaTitlePreview = (title: string | null) => {
|
||||
@@ -170,16 +146,7 @@ const useMetaTitlePreview = (title: string | null) => {
|
||||
};
|
||||
};
|
||||
|
||||
const validFaviconExtensions = ["ico", "png", "svg", "gif"];
|
||||
const isValidUrl = (url: string) =>
|
||||
url.includes("/") && validFaviconExtensions.some((extension) => url.endsWith(`.${extension}`));
|
||||
|
||||
const useFaviconPreview = (url: string | null) => {
|
||||
const [faviconDebounced] = useDebouncedValue(url ?? "", 500);
|
||||
useFavicon(isValidUrl(faviconDebounced) ? faviconDebounced : "");
|
||||
|
||||
return {
|
||||
isPending: (url ?? "") !== faviconDebounced,
|
||||
isInvalid: faviconDebounced.length >= 1 && !isValidUrl(faviconDebounced),
|
||||
};
|
||||
useFavicon(faviconDebounced);
|
||||
};
|
||||
@@ -1,45 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Grid, Group, Input, Slider, Stack } from "@mantine/core";
|
||||
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 { useZodForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { boardSaveLayoutsSchema } from "@homarr/validation/board";
|
||||
|
||||
import type { Board } from "../../_types";
|
||||
import { useSavePartialSettingsMutation } from "./_shared";
|
||||
|
||||
interface Props {
|
||||
board: Board;
|
||||
}
|
||||
export const LayoutSettingsContent = ({ board }: Props) => {
|
||||
const t = useI18n();
|
||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||
const form = useZodForm(validation.board.savePartialSettings.pick({ columnCount: true }).required(), {
|
||||
const utils = clientApi.useUtils();
|
||||
const { mutate: saveLayouts, isPending } = clientApi.board.saveLayouts.useMutation({
|
||||
onSettled() {
|
||||
void utils.board.getBoardByName.invalidate({ name: board.name });
|
||||
void utils.board.getHomeBoard.invalidate();
|
||||
},
|
||||
});
|
||||
const form = useZodForm(boardSaveLayoutsSchema.omit({ id: true }).required(), {
|
||||
initialValues: {
|
||||
columnCount: board.columnCount,
|
||||
layouts: board.layouts,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
savePartialSettings({
|
||||
saveLayouts({
|
||||
id: board.id,
|
||||
...values,
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<Grid>
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<Input.Wrapper label={t("board.field.columnCount.label")}>
|
||||
<Slider mt="xs" min={1} max={24} step={1} {...form.getInputProps("columnCount")} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text fw={500}>{t("board.setting.section.layout.responsive.title")}</Text>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
form.setValues({
|
||||
layouts: [
|
||||
...form.values.layouts,
|
||||
{
|
||||
id: createId(),
|
||||
name: "",
|
||||
columnCount: 10,
|
||||
breakpoint: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("board.setting.section.layout.responsive.action.add")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{form.values.layouts.map((layout, index) => (
|
||||
<Fieldset key={layout.id} legend={layout.name} bg="transparent">
|
||||
<Grid>
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<TextInput {...form.getInputProps(`layouts.${index}.name`)} label={t("layout.field.name.label")} />
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<Input.Wrapper label={t("layout.field.columnCount.label")}>
|
||||
<Slider mt="xs" min={1} max={24} step={1} {...form.getInputProps(`layouts.${index}.columnCount`)} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ sm: 12, md: 6 }}>
|
||||
<NumberInput
|
||||
{...form.getInputProps(`layouts.${index}.breakpoint`)}
|
||||
label={t("layout.field.breakpoint.label")}
|
||||
description={t("layout.field.breakpoint.description")}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{form.values.layouts.length >= 2 && (
|
||||
<Group justify="end">
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
form.setValues((previous) =>
|
||||
previous.layouts !== undefined && previous.layouts.length >= 2
|
||||
? {
|
||||
layouts: form.values.layouts.filter((filteredLayout) => filteredLayout.id !== layout.id),
|
||||
}
|
||||
: previous,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("common.action.remove")}
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Fieldset>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Group justify="end">
|
||||
<Button type="submit" loading={isPending} color="teal">
|
||||
<Button type="submit" loading={isPending}>
|
||||
{t("common.action.saveChanges")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AccordionControl, AccordionItem, AccordionPanel, Container, Stack, Text
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
IconBrush,
|
||||
IconClick,
|
||||
IconFileTypeCss,
|
||||
IconLayout,
|
||||
IconPhoto,
|
||||
@@ -22,9 +23,10 @@ import type { TablerIcon } from "@homarr/ui";
|
||||
|
||||
import { getBoardPermissionsAsync } from "~/components/board/permissions/server";
|
||||
import { ActiveTabAccordion } from "../../../../../components/active-tab-accordion";
|
||||
import { ColorSettingsContent } from "./_appereance";
|
||||
import { BackgroundSettingsContent } from "./_background";
|
||||
import { BehaviorSettingsContent } from "./_behavior";
|
||||
import { BoardAccessSettings } from "./_board-access";
|
||||
import { ColorSettingsContent } from "./_colors";
|
||||
import { CustomCssSettingsContent } from "./_customCss";
|
||||
import { DangerZoneSettingsContent } from "./_danger";
|
||||
import { GeneralSettingsContent } from "./_general";
|
||||
@@ -89,12 +91,15 @@ export default async function BoardSettingsPage(props: Props) {
|
||||
<AccordionItemFor value="background" icon={IconPhoto}>
|
||||
<BackgroundSettingsContent board={board} />
|
||||
</AccordionItemFor>
|
||||
<AccordionItemFor value="color" icon={IconBrush}>
|
||||
<AccordionItemFor value="appearance" icon={IconBrush}>
|
||||
<ColorSettingsContent board={board} />
|
||||
</AccordionItemFor>
|
||||
<AccordionItemFor value="customCss" icon={IconFileTypeCss}>
|
||||
<CustomCssSettingsContent board={board} />
|
||||
</AccordionItemFor>
|
||||
<AccordionItemFor value="behavior" icon={IconClick}>
|
||||
<BehaviorSettingsContent board={board} />
|
||||
</AccordionItemFor>
|
||||
{hasFullAccess && (
|
||||
<>
|
||||
<AccordionItemFor value="access" icon={IconUser}>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { IconLayoutBoard } from "@tabler/icons-react";
|
||||
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
|
||||
import { HeaderButton } from "~/components/layout/header/button";
|
||||
import { useRequiredBoard } from "./(content)/_context";
|
||||
|
||||
export const BoardOtherHeaderActions = () => {
|
||||
const board = useRequiredBoard();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { JSX, PropsWithChildren } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { AppShellMain } from "@mantine/core";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { BoardProvider } from "@homarr/boards/context";
|
||||
import { EditModeProvider } from "@homarr/boards/edit-mode";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { MainHeader } from "~/components/layout/header";
|
||||
@@ -10,9 +13,9 @@ import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo";
|
||||
import { ClientShell } from "~/components/layout/shell";
|
||||
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
|
||||
import type { Board } from "./_types";
|
||||
import { BoardProvider } from "./(content)/_context";
|
||||
import type { Params } from "./(content)/_creator";
|
||||
import { CustomCss } from "./(content)/_custom-css";
|
||||
import { BoardReadyProvider } from "./(content)/_ready-context";
|
||||
import { BoardMantineProvider } from "./(content)/_theme";
|
||||
|
||||
interface CreateBoardLayoutProps<TParams extends Params> {
|
||||
@@ -30,8 +33,14 @@ export const createBoardLayout = <TParams extends Params>({
|
||||
}: PropsWithChildren<{
|
||||
params: Promise<TParams>;
|
||||
}>) => {
|
||||
const session = await auth();
|
||||
const initialBoard = await getInitialBoard(await params).catch((error) => {
|
||||
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
|
||||
if (!session) {
|
||||
logger.debug("No home board found for anonymous user, redirecting to login");
|
||||
redirect("/auth/login");
|
||||
}
|
||||
|
||||
logger.warn(error);
|
||||
notFound();
|
||||
}
|
||||
@@ -42,17 +51,21 @@ export const createBoardLayout = <TParams extends Params>({
|
||||
|
||||
return (
|
||||
<BoardProvider initialBoard={initialBoard}>
|
||||
<BoardMantineProvider defaultColorScheme={colorScheme}>
|
||||
<CustomCss />
|
||||
<ClientShell hasNavigation={false}>
|
||||
<MainHeader
|
||||
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
|
||||
actions={headerActions}
|
||||
hasNavigation={false}
|
||||
/>
|
||||
<AppShellMain>{children}</AppShellMain>
|
||||
</ClientShell>
|
||||
</BoardMantineProvider>
|
||||
<BoardReadyProvider>
|
||||
<EditModeProvider>
|
||||
<BoardMantineProvider defaultColorScheme={colorScheme}>
|
||||
<CustomCss />
|
||||
<ClientShell hasNavigation={false}>
|
||||
<MainHeader
|
||||
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
|
||||
actions={headerActions}
|
||||
hasNavigation={false}
|
||||
/>
|
||||
<AppShellMain>{children}</AppShellMain>
|
||||
</ClientShell>
|
||||
</BoardMantineProvider>
|
||||
</EditModeProvider>
|
||||
</BoardReadyProvider>
|
||||
</BoardProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,10 +3,14 @@ import type { WidgetKind } from "@homarr/definitions";
|
||||
|
||||
export type Board = RouterOutputs["board"]["getHomeBoard"];
|
||||
export type Section = Board["sections"][number];
|
||||
export type Item = Section["items"][number];
|
||||
export type Item = Board["items"][number];
|
||||
export type ItemLayout = Item["layouts"][number];
|
||||
export type SectionItem = Omit<Item, "layouts"> & ItemLayout & { type: "item" };
|
||||
|
||||
export type CategorySection = Extract<Section, { kind: "category" }>;
|
||||
export type EmptySection = Extract<Section, { kind: "empty" }>;
|
||||
export type DynamicSection = Extract<Section, { kind: "dynamic" }>;
|
||||
export type DynamicSectionLayout = DynamicSection["layouts"][number];
|
||||
export type DynamicSectionItem = Omit<DynamicSection, "layouts"> & DynamicSectionLayout & { type: "section" };
|
||||
|
||||
export type ItemOfKind<TKind extends WidgetKind> = Extract<Item, { kind: TKind }>;
|
||||
@@ -8,18 +8,18 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { groupCreateSchema } from "@homarr/validation/group";
|
||||
|
||||
export const InitGroup = () => {
|
||||
const t = useI18n();
|
||||
const { mutateAsync } = clientApi.group.createInitialExternalGroup.useMutation();
|
||||
const form = useZodForm(validation.group.create, {
|
||||
const form = useZodForm(groupCreateSchema, {
|
||||
initialValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmitAsync = async (values: z.infer<typeof validation.group.create>) => {
|
||||
const handleSubmitAsync = async (values: z.infer<typeof groupCreateSchema>) => {
|
||||
await mutateAsync(values, {
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/init");
|
||||
|
||||
@@ -12,13 +12,13 @@ import type { CheckboxProps } from "@homarr/form/types";
|
||||
import { defaultServerSettings } from "@homarr/server-settings";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||
|
||||
export const InitSettings = () => {
|
||||
const tSection = useScopedI18n("management.page.settings.section");
|
||||
const t = useI18n();
|
||||
const { mutateAsync } = clientApi.serverSettings.initSettings.useMutation();
|
||||
const form = useZodForm(validation.settings.init, { initialValues: defaultServerSettings });
|
||||
const form = useZodForm(settingsInitSchema, { initialValues: defaultServerSettings });
|
||||
|
||||
form.watch("analytics.enableGeneral", ({ value }) => {
|
||||
if (!value) {
|
||||
@@ -30,7 +30,7 @@ export const InitSettings = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const handleSubmitAsync = async (values: z.infer<typeof validation.settings.init>) => {
|
||||
const handleSubmitAsync = async (values: z.infer<typeof settingsInitSchema>) => {
|
||||
await mutateAsync(values, {
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/init");
|
||||
|
||||
@@ -4,18 +4,19 @@ import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { signIn } from "@homarr/auth/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { CustomPasswordInput } from "@homarr/ui";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { userInitSchema } from "@homarr/validation/user";
|
||||
|
||||
export const InitUserForm = () => {
|
||||
const t = useScopedI18n("user");
|
||||
const tUser = useScopedI18n("init.step.user");
|
||||
const { mutateAsync, isPending } = clientApi.user.initUser.useMutation();
|
||||
const form = useZodForm(validation.user.init, {
|
||||
const form = useZodForm(userInitSchema, {
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
@@ -30,6 +31,13 @@ export const InitUserForm = () => {
|
||||
title: tUser("notification.success.title"),
|
||||
message: tUser("notification.success.message"),
|
||||
});
|
||||
|
||||
await signIn("credentials", {
|
||||
name: values.username,
|
||||
password: values.password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
await revalidatePathActionAsync("/init");
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -66,4 +74,4 @@ export const InitUserForm = () => {
|
||||
);
|
||||
};
|
||||
|
||||
type FormType = z.infer<typeof validation.user.init>;
|
||||
type FormType = z.infer<typeof userInitSchema>;
|
||||
@@ -7,6 +7,7 @@ import "@homarr/ui/styles.css";
|
||||
import "~/styles/scroll-area.scss";
|
||||
|
||||
import { notFound } from "next/navigation";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -20,7 +21,6 @@ import { SettingsProvider } from "@homarr/settings";
|
||||
import { SpotlightProvider } from "@homarr/spotlight";
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
|
||||
import { getI18nMessages } from "@homarr/translation/server";
|
||||
|
||||
import { Analytics } from "~/components/layout/analytics";
|
||||
import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization";
|
||||
@@ -81,7 +81,6 @@ export default async function Layout(props: {
|
||||
const serverSettings = await getServerSettingsAsync(db);
|
||||
const colorScheme = await getCurrentColorSchemeAsync();
|
||||
const direction = isLocaleRTL((await props.params).locale) ? "rtl" : "ltr";
|
||||
const i18nMessages = await getI18nMessages();
|
||||
|
||||
const StackedProvider = composeWrappers([
|
||||
(innerProps) => {
|
||||
@@ -89,11 +88,21 @@ export default async function Layout(props: {
|
||||
},
|
||||
(innerProps) => (
|
||||
<SettingsProvider
|
||||
user={user}
|
||||
user={
|
||||
user
|
||||
? {
|
||||
...user,
|
||||
// Convert type, because output schema is not smart enough to infer $type from drizzle
|
||||
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
|
||||
}
|
||||
: null
|
||||
}
|
||||
serverSettings={{
|
||||
board: {
|
||||
homeBoardId: serverSettings.board.homeBoardId,
|
||||
mobileHomeBoardId: serverSettings.board.mobileHomeBoardId,
|
||||
enableStatusByDefault: serverSettings.board.enableStatusByDefault,
|
||||
forceDisableStatus: serverSettings.board.forceDisableStatus,
|
||||
},
|
||||
search: { defaultSearchEngineId: serverSettings.search.defaultSearchEngineId },
|
||||
}}
|
||||
@@ -103,7 +112,7 @@ export default async function Layout(props: {
|
||||
(innerProps) => <JotaiProvider {...innerProps} />,
|
||||
(innerProps) => <TRPCReactProvider {...innerProps} />,
|
||||
(innerProps) => <DayJsLoader {...innerProps} />,
|
||||
(innerProps) => <NextIntlClientProvider {...innerProps} messages={i18nMessages} />,
|
||||
(innerProps) => <NextIntlClientProvider {...innerProps} />,
|
||||
(innerProps) => <CustomMantineProvider {...innerProps} defaultColorScheme={colorScheme} />,
|
||||
(innerProps) => <ModalProvider {...innerProps} />,
|
||||
(innerProps) => <SpotlightProvider {...innerProps} />,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.bannerContainer {
|
||||
border-radius: 8px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
@mixin dark {
|
||||
background: linear-gradient(
|
||||
|
||||
@@ -80,7 +80,7 @@ export default async function AboutPage() {
|
||||
<Text>{t("accordion.contributors.title")}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("accordion.contributors.subtitle", {
|
||||
count: githubContributors.length,
|
||||
count: String(githubContributors.length),
|
||||
})}
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -104,7 +104,7 @@ export default async function AboutPage() {
|
||||
<Text>{t("accordion.translators.title")}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("accordion.translators.subtitle", {
|
||||
count: crowdinContributors.length,
|
||||
count: String(crowdinContributors.length),
|
||||
})}
|
||||
</Text>
|
||||
</Stack>
|
||||
@@ -128,7 +128,7 @@ export default async function AboutPage() {
|
||||
<Text>{t("accordion.libraries.title")}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("accordion.libraries.subtitle", {
|
||||
count: Object.keys(attributes.dependencies).length,
|
||||
count: String(Object.keys(attributes.dependencies).length),
|
||||
})}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -23,7 +23,9 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
openConfirmModal({
|
||||
title: t("title"),
|
||||
children: t("message", app),
|
||||
children: t("message", {
|
||||
name: app.name,
|
||||
}),
|
||||
onConfirm: () => {
|
||||
mutate(
|
||||
{ id: app.id },
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Button, Group, Stack, Textarea, TextInput } from "@mantine/core";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { IconPicker } from "~/components/icons/picker/icon-picker";
|
||||
|
||||
type FormType = z.infer<typeof validation.app.manage>;
|
||||
|
||||
interface AppFormProps {
|
||||
submitButtonTranslation: (t: TranslationFunction) => string;
|
||||
initialValues?: FormType;
|
||||
handleSubmit: (values: FormType) => void;
|
||||
isPending: boolean;
|
||||
}
|
||||
|
||||
export const AppForm = (props: AppFormProps) => {
|
||||
const { submitButtonTranslation, handleSubmit, initialValues, isPending } = props;
|
||||
const t = useI18n();
|
||||
|
||||
const form = useZodForm(validation.app.manage, {
|
||||
initialValues: {
|
||||
name: initialValues?.name ?? "",
|
||||
description: initialValues?.description ?? "",
|
||||
iconUrl: initialValues?.iconUrl ?? "",
|
||||
href: initialValues?.href ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput {...form.getInputProps("name")} withAsterisk label={t("app.field.name.label")} />
|
||||
<IconPicker initialValue={initialValues?.iconUrl} {...form.getInputProps("iconUrl")} />
|
||||
<Textarea {...form.getInputProps("description")} label={t("app.field.description.label")} />
|
||||
<TextInput {...form.getInputProps("href")} label={t("app.field.url.label")} />
|
||||
|
||||
<Group justify="end">
|
||||
<Button variant="default" component={Link} href="/manage/apps">
|
||||
{t("common.action.backToOverview")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending}>
|
||||
{submitButtonTranslation(t)}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -7,26 +7,25 @@ import type { z } from "zod";
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { AppForm } from "@homarr/forms-collection";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { validation } from "@homarr/validation";
|
||||
|
||||
import { AppForm } from "../../_form";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import type { appManageSchema } from "@homarr/validation/app";
|
||||
|
||||
interface AppEditFormProps {
|
||||
app: RouterOutputs["app"]["byId"];
|
||||
}
|
||||
|
||||
export const AppEditForm = ({ app }: AppEditFormProps) => {
|
||||
const t = useScopedI18n("app.page.edit.notification");
|
||||
const tScoped = useScopedI18n("app.page.edit.notification");
|
||||
const t = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
const { mutate, isPending } = clientApi.app.update.useMutation({
|
||||
onSuccess: () => {
|
||||
showSuccessNotification({
|
||||
title: t("success.title"),
|
||||
message: t("success.message"),
|
||||
title: tScoped("success.title"),
|
||||
message: tScoped("success.message"),
|
||||
});
|
||||
void revalidatePathActionAsync("/manage/apps").then(() => {
|
||||
router.push("/manage/apps");
|
||||
@@ -34,14 +33,14 @@ export const AppEditForm = ({ app }: AppEditFormProps) => {
|
||||
},
|
||||
onError: () => {
|
||||
showErrorNotification({
|
||||
title: t("error.title"),
|
||||
message: t("error.message"),
|
||||
title: tScoped("error.title"),
|
||||
message: tScoped("error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(values: z.infer<typeof validation.app.manage>) => {
|
||||
(values: z.infer<typeof appManageSchema>) => {
|
||||
mutate({
|
||||
id: app.id,
|
||||
...values,
|
||||
@@ -50,14 +49,15 @@ export const AppEditForm = ({ app }: AppEditFormProps) => {
|
||||
[mutate, app.id],
|
||||
);
|
||||
|
||||
const submitButtonTranslation = useCallback((t: TranslationFunction) => t("common.action.save"), []);
|
||||
|
||||
return (
|
||||
<AppForm
|
||||
submitButtonTranslation={submitButtonTranslation}
|
||||
buttonLabels={{
|
||||
submit: t("common.action.save"),
|
||||
}}
|
||||
initialValues={app}
|
||||
handleSubmit={handleSubmit}
|
||||
isPending={isPending}
|
||||
showBackToOverview
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { validation } from "@homarr/validation";
|
||||
|
||||
import { AppForm } from "../_form";
|
||||
|
||||
export const AppNewForm = () => {
|
||||
const t = useScopedI18n("app.page.create.notification");
|
||||
const router = useRouter();
|
||||
|
||||
const { mutate, isPending } = clientApi.app.create.useMutation({
|
||||
onSuccess: () => {
|
||||
showSuccessNotification({
|
||||
title: t("success.title"),
|
||||
message: t("success.message"),
|
||||
});
|
||||
void revalidatePathActionAsync("/manage/apps").then(() => {
|
||||
router.push("/manage/apps");
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
showErrorNotification({
|
||||
title: t("error.title"),
|
||||
message: t("error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(values: z.infer<typeof validation.app.manage>) => {
|
||||
mutate(values);
|
||||
},
|
||||
[mutate],
|
||||
);
|
||||
|
||||
const submitButtonTranslation = useCallback((t: TranslationFunction) => t("common.action.create"), []);
|
||||
|
||||
return (
|
||||
<AppForm submitButtonTranslation={submitButtonTranslation} handleSubmit={handleSubmit} isPending={isPending} />
|
||||
);
|
||||
};
|
||||
@@ -2,10 +2,10 @@ import { notFound } from "next/navigation";
|
||||
import { Container, Stack, Title } from "@mantine/core";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { AppNewForm } from "@homarr/forms-collection";
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { AppNewForm } from "./_app-new-form";
|
||||
|
||||
export default async function AppNewPage() {
|
||||
const session = await auth();
|
||||
@@ -22,7 +22,7 @@ export default async function AppNewPage() {
|
||||
<Container>
|
||||
<Stack>
|
||||
<Title>{t("app.page.create.title")}</Title>
|
||||
<AppNewForm />
|
||||
<AppNewForm showBackToOverview showCreateAnother />
|
||||
</Stack>
|
||||
</Container>
|
||||
</>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function AppsPage(props: AppsPageProps) {
|
||||
<Stack>
|
||||
<Title>{t("page.list.title")}</Title>
|
||||
<Group justify="space-between" align="center">
|
||||
<SearchInput placeholder={`${t("search")}...`} defaultValue={searchParams.search} />
|
||||
<SearchInput placeholder={`${t("search")}...`} defaultValue={searchParams.search} flexExpand />
|
||||
{session.user.permissions.includes("app-create") && (
|
||||
<MobileAffixButton component={Link} href="/manage/apps/new">
|
||||
{t("page.create.title")}
|
||||
@@ -61,7 +61,8 @@ export default async function AppsPage(props: AppsPageProps) {
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Group justify="end">
|
||||
{/* Added margin to not hide pagination behind affix-button */}
|
||||
<Group justify="end" mb={48}>
|
||||
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -67,7 +67,7 @@ const BoardCard = async ({ board }: BoardCardProps) => {
|
||||
const VisibilityIcon = board.isPublic ? IconWorld : IconLock;
|
||||
|
||||
return (
|
||||
<Card withBorder>
|
||||
<Card radius="lg" withBorder>
|
||||
<CardSection p="sm" withBorder>
|
||||
<Group justify="space-between" align="center">
|
||||
<Group gap="sm">
|
||||
@@ -106,15 +106,25 @@ const BoardCard = async ({ board }: BoardCardProps) => {
|
||||
</Group>
|
||||
</CardSection>
|
||||
|
||||
<CardSection p="sm">
|
||||
<Group wrap="nowrap">
|
||||
<Button component={Link} href={`/boards/${board.name}`} variant="default" fullWidth>
|
||||
<CardSection>
|
||||
<Group gap={0} wrap="nowrap">
|
||||
<Button
|
||||
style={{ border: "none", borderRadius: 0 }}
|
||||
component={Link}
|
||||
href={`/boards/${board.name}`}
|
||||
variant="default"
|
||||
fullWidth
|
||||
>
|
||||
{t("action.open.label")}
|
||||
</Button>
|
||||
{isMenuVisible && (
|
||||
<Menu position="bottom-end">
|
||||
<MenuTarget>
|
||||
<ActionIcon variant="default" size="lg">
|
||||
<ActionIcon
|
||||
style={{ borderTop: "none", borderBottom: "none", borderRight: "none", borderRadius: 0 }}
|
||||
variant="default"
|
||||
size="lg"
|
||||
>
|
||||
<IconDotsVertical size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</MenuTarget>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useMemo } from "react";
|
||||
import { Accordion, Anchor, Card, Stack, Text } from "@mantine/core";
|
||||
import { IconSubtask } from "@tabler/icons-react";
|
||||
|
||||
import { getMantineColor } from "@homarr/common";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { CertificateErrorDetails } from "./test-connection-certificate";
|
||||
import type { AnyMappedTestConnectionError, MappedError } from "./types";
|
||||
|
||||
interface IntegrationTestConnectionErrorProps {
|
||||
error: AnyMappedTestConnectionError;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const IntegrationTestConnectionError = ({ error, url }: IntegrationTestConnectionErrorProps) => {
|
||||
const t = useI18n();
|
||||
const causeArray = useMemo(() => toCauseArray(error.cause), [error.cause]);
|
||||
|
||||
return (
|
||||
<Card withBorder style={{ borderColor: getMantineColor("red", 8) }}>
|
||||
<Stack>
|
||||
<Stack gap="sm">
|
||||
<Text size="lg" fw={500} c="red.8">
|
||||
{t(`integration.testConnection.error.${error.type}.title`)}
|
||||
</Text>
|
||||
|
||||
{error.type !== "request" && error.type !== "certificate" && error.type !== "statusCode" ? (
|
||||
<Text size="md">{t(`integration.testConnection.error.${error.type}.description`)}</Text>
|
||||
) : null}
|
||||
|
||||
{error.type === "request" ? (
|
||||
<Text size="md">
|
||||
{t(
|
||||
`integration.testConnection.error.request.description.${error.data.type}.${error.data.reason}` as never,
|
||||
)}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
{error.type === "statusCode" ? (
|
||||
error.data.reason === "other" ? (
|
||||
<Text size="md">
|
||||
{t.rich("integration.testConnection.error.statusCode.otherDescription", {
|
||||
statusCode: error.data.statusCode.toString(),
|
||||
url: () => <Anchor href={error.data.url}>{error.data.url}</Anchor>,
|
||||
})}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="md">
|
||||
{t.rich("integration.testConnection.error.statusCode.description", {
|
||||
reason: t(`integration.testConnection.error.statusCode.reason.${error.data.reason}`),
|
||||
statusCode: error.data.statusCode.toString(),
|
||||
url: () => <Anchor href={error.data.url}>{error.data.url}</Anchor>,
|
||||
})}
|
||||
</Text>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{error.type === "certificate" ? <CertificateErrorDetails error={error} url={url} /> : null}
|
||||
</Stack>
|
||||
|
||||
{error.cause ? (
|
||||
<Accordion variant="contained">
|
||||
<Accordion.Item value="cause">
|
||||
<Accordion.Control icon={<IconSubtask size={16} stroke={1.5} />}>
|
||||
{t("integration.testConnection.error.common.cause.title")}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<pre style={{ whiteSpace: "pre-wrap" }}>
|
||||
{error.name}: {error.message}
|
||||
{"\n"}
|
||||
{causeArray
|
||||
.map(
|
||||
(cause) =>
|
||||
`caused by ${cause.name}${cause.message ? `: ${cause.message}` : ""} ${cause.metadata.map((item) => `${item.key}=${item.value}`).join(" ")}`,
|
||||
)
|
||||
.join("\n")}
|
||||
</pre>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const toCauseArray = (cause: MappedError | undefined) => {
|
||||
const causeArray: MappedError[] = [];
|
||||
let currentCause: MappedError | undefined = cause;
|
||||
while (currentCause) {
|
||||
causeArray.push(currentCause);
|
||||
currentCause = currentCause.cause;
|
||||
}
|
||||
return causeArray.map(({ cause: _innerCause, ...cause }) => cause);
|
||||
};
|
||||
@@ -0,0 +1,327 @@
|
||||
import { useState } from "react";
|
||||
import { ActionIcon, Alert, Anchor, Button, Card, CopyButton, Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle, IconCheck, IconCopy, IconExclamationCircle, IconRepeat } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { getMantineColor } from "@homarr/common";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import { createDocumentationLink } from "@homarr/definitions";
|
||||
import { createModal, useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { AddCertificateModal } from "@homarr/modals-collection";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useCurrentLocale, useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { MappedCertificate, MappedTestConnectionCertificateError } from "./types";
|
||||
|
||||
interface CertificateErrorDetailsProps {
|
||||
error: MappedTestConnectionCertificateError;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsProps) => {
|
||||
const tError = useScopedI18n("integration.testConnection.error");
|
||||
const { data: session } = useSession();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
const [showRetryButton, setShowRetryButton] = useState(false);
|
||||
|
||||
const { openModal: openUploadModal } = useModalAction(AddCertificateModal);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const { mutateAsync: trustHostnameAsync } = clientApi.certificates.trustHostnameMismatch.useMutation();
|
||||
const { mutateAsync: addCertificateAsync } = clientApi.certificates.addCertificate.useMutation();
|
||||
|
||||
const handleTrustHostname = () => {
|
||||
const { hostname } = new URL(url);
|
||||
openConfirmModal({
|
||||
title: tError("certificate.hostnameMismatch.confirm.title"),
|
||||
children: tError("certificate.hostnameMismatch.confirm.message"),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
async onConfirm() {
|
||||
await trustHostnameAsync(
|
||||
{
|
||||
hostname,
|
||||
certificate: error.data.certificate.pem,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
showSuccessNotification({
|
||||
title: tError("certificate.hostnameMismatch.notification.success.title"),
|
||||
message: tError("certificate.hostnameMismatch.notification.success.message"),
|
||||
});
|
||||
setShowRetryButton(true);
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: tError("certificate.hostnameMismatch.notification.error.title"),
|
||||
message: tError("certificate.hostnameMismatch.notification.error.message"),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleTrustSelfSigned = () => {
|
||||
const { hostname } = new URL(url);
|
||||
openConfirmModal({
|
||||
title: tError("certificate.selfSigned.confirm.title"),
|
||||
children: tError("certificate.selfSigned.confirm.message"),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
async onConfirm() {
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
"file",
|
||||
new File([error.data.certificate.pem], `${hostname}-${createId()}.crt`, {
|
||||
type: "application/x-x509-ca-cert",
|
||||
}),
|
||||
);
|
||||
await addCertificateAsync(formData, {
|
||||
onSuccess() {
|
||||
showSuccessNotification({
|
||||
title: tError("certificate.selfSigned.notification.success.title"),
|
||||
message: tError("certificate.selfSigned.notification.success.message"),
|
||||
});
|
||||
setShowRetryButton(true);
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: tError("certificate.selfSigned.notification.error.title"),
|
||||
message: tError("certificate.selfSigned.notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const description = <Text size="md">{tError(`certificate.description.${error.data.reason}`)}</Text>;
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<>
|
||||
{description}
|
||||
<NotEnoughPermissionsAlert />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{description}
|
||||
|
||||
<CertificateDetailsCard certificate={error.data.certificate} />
|
||||
|
||||
{error.data.reason === "hostnameMismatch" && <HostnameMismatchAlert />}
|
||||
|
||||
{!error.data.certificate.isSelfSigned && error.data.reason === "untrusted" && <CertificateExtractAlert />}
|
||||
|
||||
{showRetryButton && (
|
||||
<Button
|
||||
variant="default"
|
||||
fullWidth
|
||||
leftSection={<IconRepeat size={16} color={getMantineColor("blue", 6)} stroke={1.5} />}
|
||||
type="submit"
|
||||
>
|
||||
{tError("certificate.action.retry.label")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{(error.data.reason === "untrusted" && error.data.certificate.isSelfSigned) ||
|
||||
error.data.reason === "hostnameMismatch" ? (
|
||||
<Button
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={error.data.reason === "hostnameMismatch" ? handleTrustHostname : handleTrustSelfSigned}
|
||||
>
|
||||
{tError("certificate.action.trust.label")}
|
||||
</Button>
|
||||
) : null}
|
||||
{error.data.reason === "untrusted" && !error.data.certificate.isSelfSigned ? (
|
||||
<Button
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={() =>
|
||||
openUploadModal({
|
||||
onSuccess() {
|
||||
setShowRetryButton(true);
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
{tError("certificate.action.upload.label")}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const NotEnoughPermissionsAlert = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<Alert
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
title={t("integration.testConnection.error.certificate.alert.permission.title")}
|
||||
color="yellow"
|
||||
>
|
||||
{t("integration.testConnection.error.certificate.alert.permission.message")}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
const HostnameMismatchAlert = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<Alert
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
title={t("integration.testConnection.error.certificate.alert.hostnameMismatch.title")}
|
||||
color="yellow"
|
||||
>
|
||||
{t("integration.testConnection.error.certificate.alert.hostnameMismatch.message")}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
const CertificateExtractAlert = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<Alert
|
||||
icon={<IconExclamationCircle size={16} />}
|
||||
title={t("integration.testConnection.error.certificate.alert.extract.title")}
|
||||
color="red"
|
||||
>
|
||||
{t.rich("integration.testConnection.error.certificate.alert.extract.message", {
|
||||
docsLink: () => (
|
||||
<Anchor
|
||||
href={createDocumentationLink("/docs/management/certificates", "#obtaining-certificates")}
|
||||
target="_blank"
|
||||
>
|
||||
{t("common.here")}
|
||||
</Anchor>
|
||||
),
|
||||
})}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
interface CertificateDetailsProps {
|
||||
certificate: MappedCertificate;
|
||||
}
|
||||
|
||||
export const CertificateDetailsCard = ({ certificate }: CertificateDetailsProps) => {
|
||||
const { openModal } = useModalAction(PemContentModal);
|
||||
const locale = useCurrentLocale();
|
||||
const tDetails = useScopedI18n("integration.testConnection.error.certificate.details");
|
||||
const tCertificateField = useScopedI18n("certificate.field");
|
||||
|
||||
return (
|
||||
<Card withBorder>
|
||||
<Text fw={500}>{tDetails("title")}</Text>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">
|
||||
{tDetails("description")}
|
||||
</Text>
|
||||
<Anchor
|
||||
size="sm"
|
||||
ta="start"
|
||||
component="button"
|
||||
type="button"
|
||||
onClick={() => openModal({ content: certificate.pem })}
|
||||
>
|
||||
{tDetails("content.action")}
|
||||
</Anchor>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} mt="md">
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("subject.label")}
|
||||
</Text>
|
||||
<Text size="sm">{certificate.subject}</Text>
|
||||
</Stack>
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("issuer.label")}
|
||||
</Text>
|
||||
<Text size="sm">{certificate.issuer}</Text>
|
||||
</Stack>
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("validFrom.label")}
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{new Intl.DateTimeFormat(locale, {
|
||||
dateStyle: "full",
|
||||
timeStyle: "long",
|
||||
}).format(certificate.validFrom)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("validTo.label")}
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{new Intl.DateTimeFormat(locale, {
|
||||
dateStyle: "full",
|
||||
timeStyle: "long",
|
||||
}).format(certificate.validTo)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("serialNumber.label")}
|
||||
</Text>
|
||||
<Text size="sm">{certificate.serialNumber}</Text>
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
|
||||
<SimpleGrid cols={1} mt="md">
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{tCertificateField("fingerprint.label")}
|
||||
</Text>
|
||||
<Text size="sm">{certificate.fingerprint}</Text>
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const PemContentModal = createModal<{ content: string }>(({ actions, innerProps }) => {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Card w="100%" pos="relative" bg="dark.6" fz="xs" p="sm">
|
||||
<pre
|
||||
style={{
|
||||
whiteSpace: "pre-wrap",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
{innerProps.content}
|
||||
</pre>
|
||||
<CopyButton value={innerProps.content}>
|
||||
{({ copy, copied }) => (
|
||||
<ActionIcon onClick={copy} pos="absolute" top={8} right={8} variant="default">
|
||||
{copied ? (
|
||||
<IconCheck size={16} stroke={1.5} color={getMantineColor("green", 6)} />
|
||||
) : (
|
||||
<IconCopy size={16} stroke={1.5} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Card>
|
||||
|
||||
<Button variant="light" color="gray" onClick={actions.closeModal}>
|
||||
{t("common.action.close")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle(t) {
|
||||
return t("integration.testConnection.error.certificate.details.content.title");
|
||||
},
|
||||
size: "lg",
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
|
||||
export type AnyMappedTestConnectionError = Exclude<RouterOutputs["integration"]["create"], undefined>["error"];
|
||||
export type MappedTestConnectionCertificateError = Extract<AnyMappedTestConnectionError, { type: "certificate" }>;
|
||||
export type MappedCertificate = MappedTestConnectionCertificateError["data"]["certificate"];
|
||||
export type MappedError = Exclude<AnyMappedTestConnectionError["cause"], undefined>;
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
|
||||
@@ -10,14 +11,15 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
||||
import { useConfirmModal } from "@homarr/modals";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { integrationUpdateSchema } from "@homarr/validation/integration";
|
||||
|
||||
import { SecretCard } from "../../_components/secrets/integration-secret-card";
|
||||
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
|
||||
import { IntegrationTestConnectionError } from "../../_components/test-connection/integration-test-connection-error";
|
||||
import type { AnyMappedTestConnectionError } from "../../_components/test-connection/types";
|
||||
|
||||
interface EditIntegrationForm {
|
||||
integration: RouterOutputs["integration"]["byId"];
|
||||
@@ -32,7 +34,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
) ?? getDefaultSecretKinds(integration.kind);
|
||||
|
||||
const router = useRouter();
|
||||
const form = useZodForm(validation.integration.update.omit({ id: true }), {
|
||||
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
|
||||
initialValues: {
|
||||
name: integration.name,
|
||||
url: integration.url,
|
||||
@@ -43,6 +45,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
},
|
||||
});
|
||||
const { mutateAsync, isPending } = clientApi.integration.update.useMutation();
|
||||
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
|
||||
|
||||
const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret]));
|
||||
|
||||
@@ -57,26 +60,24 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
})),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
// We do it this way as we are unable to send a typesafe error through onError
|
||||
if (data?.error) {
|
||||
setError(data.error);
|
||||
showErrorNotification({
|
||||
title: t("integration.page.edit.notification.error.title"),
|
||||
message: t("integration.page.edit.notification.error.message"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessNotification({
|
||||
title: t("integration.page.edit.notification.success.title"),
|
||||
message: t("integration.page.edit.notification.success.message"),
|
||||
});
|
||||
void revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
|
||||
},
|
||||
onError: (error) => {
|
||||
const testConnectionError = convertIntegrationTestConnectionError(error.data?.error);
|
||||
|
||||
if (testConnectionError) {
|
||||
showErrorNotification({
|
||||
title: t(`integration.testConnection.notification.${testConnectionError.key}.title`),
|
||||
message:
|
||||
testConnectionError.message ??
|
||||
t(`integration.testConnection.notification.${testConnectionError.key}.message`),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onError: () => {
|
||||
showErrorNotification({
|
||||
title: t("integration.page.edit.notification.error.title"),
|
||||
message: t("integration.page.edit.notification.error.message"),
|
||||
@@ -128,6 +129,8 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
|
||||
{error !== null && <IntegrationTestConnectionError error={error} url={form.values.url} />}
|
||||
|
||||
<Group justify="end" align="center">
|
||||
<Button variant="default" component={Link} href="/manage/integrations">
|
||||
{t("common.action.backToOverview")}
|
||||
@@ -141,4 +144,4 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
);
|
||||
};
|
||||
|
||||
type FormType = Omit<z.infer<typeof validation.integration.update>, "id">;
|
||||
type FormType = Omit<z.infer<typeof integrationUpdateSchema>, "id">;
|
||||
@@ -33,6 +33,7 @@ export const IntegrationCreateDropdownContent = () => {
|
||||
value={search}
|
||||
data-autofocus
|
||||
onChange={handleSearch}
|
||||
variant="filled"
|
||||
/>
|
||||
|
||||
{filteredKinds.length > 0 ? (
|
||||
|
||||
@@ -1,36 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Alert, Button, Checkbox, Fieldset, Group, SegmentedControl, Stack, Text, TextInput } from "@mantine/core";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Collapse,
|
||||
Fieldset,
|
||||
Group,
|
||||
SegmentedControl,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
import type { z } from "zod";
|
||||
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, getIntegrationName, integrationDefs } from "@homarr/definitions";
|
||||
import { getAllSecretKindOptions, getIconUrl, getIntegrationName, integrationDefs } from "@homarr/definitions";
|
||||
import type { UseFormReturnType } from "@homarr/form";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { appHrefSchema } from "@homarr/validation/app";
|
||||
import { integrationCreateSchema } from "@homarr/validation/integration";
|
||||
|
||||
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
|
||||
import { IntegrationTestConnectionError } from "../_components/test-connection/integration-test-connection-error";
|
||||
import type { AnyMappedTestConnectionError } from "../_components/test-connection/types";
|
||||
|
||||
interface NewIntegrationFormProps {
|
||||
searchParams: Partial<z.infer<typeof validation.integration.create>> & {
|
||||
searchParams: Partial<z.infer<typeof integrationCreateSchema>> & {
|
||||
kind: IntegrationKind;
|
||||
};
|
||||
}
|
||||
|
||||
const formSchema = integrationCreateSchema.omit({ kind: true }).and(
|
||||
z.object({
|
||||
createApp: z.boolean(),
|
||||
appHref: appHrefSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) => {
|
||||
const t = useI18n();
|
||||
const secretKinds = getAllSecretKindOptions(searchParams.kind);
|
||||
const router = useRouter();
|
||||
const form = useZodForm(validation.integration.create.omit({ kind: true }), {
|
||||
const [opened, setOpened] = useState(false);
|
||||
const form = useZodForm(formSchema, {
|
||||
initialValues: {
|
||||
name: searchParams.name ?? getIntegrationName(searchParams.kind),
|
||||
url: searchParams.url ?? "",
|
||||
@@ -39,37 +60,73 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
value: "",
|
||||
})),
|
||||
attemptSearchEngineCreation: true,
|
||||
createApp: false,
|
||||
appHref: "",
|
||||
},
|
||||
onValuesChange(values, previous) {
|
||||
if (values.createApp !== previous.createApp) {
|
||||
setOpened(values.createApp);
|
||||
}
|
||||
},
|
||||
});
|
||||
const { mutateAsync, isPending } = clientApi.integration.create.useMutation();
|
||||
|
||||
const { mutateAsync: createIntegrationAsync, isPending: isPendingIntegration } =
|
||||
clientApi.integration.create.useMutation();
|
||||
const { mutateAsync: createAppAsync, isPending: isPendingApp } = clientApi.app.create.useMutation();
|
||||
const isPending = isPendingIntegration || isPendingApp;
|
||||
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
|
||||
|
||||
const handleSubmitAsync = async (values: FormType) => {
|
||||
await mutateAsync(
|
||||
await createIntegrationAsync(
|
||||
{
|
||||
kind: searchParams.kind,
|
||||
...values,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
showSuccessNotification({
|
||||
title: t("integration.page.create.notification.success.title"),
|
||||
message: t("integration.page.create.notification.success.message"),
|
||||
});
|
||||
void revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
|
||||
},
|
||||
onError: (error) => {
|
||||
const testConnectionError = convertIntegrationTestConnectionError(error.data?.error);
|
||||
|
||||
if (testConnectionError) {
|
||||
async onSuccess(data) {
|
||||
// We do it this way as we are unable to send a typesafe error through onError
|
||||
if (data?.error) {
|
||||
setError(data.error);
|
||||
showErrorNotification({
|
||||
title: t(`integration.testConnection.notification.${testConnectionError.key}.title`),
|
||||
message:
|
||||
testConnectionError.message ??
|
||||
t(`integration.testConnection.notification.${testConnectionError.key}.message`),
|
||||
title: t("integration.page.create.notification.error.title"),
|
||||
message: t("integration.page.create.notification.error.message"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessNotification({
|
||||
title: t("integration.page.create.notification.success.title"),
|
||||
message: t("integration.page.create.notification.success.message"),
|
||||
});
|
||||
|
||||
if (!values.createApp) {
|
||||
await revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasCustomHref = values.appHref !== null && values.appHref.trim().length >= 1;
|
||||
await createAppAsync(
|
||||
{
|
||||
name: values.name,
|
||||
href: hasCustomHref ? values.appHref : values.url,
|
||||
iconUrl: getIconUrl(searchParams.kind),
|
||||
description: null,
|
||||
pingUrl: values.url,
|
||||
},
|
||||
{
|
||||
async onSettled() {
|
||||
await revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("app.page.create.notification.error.title"),
|
||||
message: t("app.page.create.notification.error.message"),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
showErrorNotification({
|
||||
title: t("integration.page.create.notification.error.title"),
|
||||
message: t("integration.page.create.notification.error.message"),
|
||||
@@ -107,6 +164,8 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
|
||||
{error !== null && <IntegrationTestConnectionError error={error} url={form.values.url} />}
|
||||
|
||||
{supportsSearchEngine && (
|
||||
<Checkbox
|
||||
label={t("integration.field.attemptSearchEngineCreation.label")}
|
||||
@@ -117,6 +176,16 @@ export const NewIntegrationForm = ({ searchParams }: NewIntegrationFormProps) =>
|
||||
/>
|
||||
)}
|
||||
|
||||
<Checkbox
|
||||
{...form.getInputProps("createApp", { type: "checkbox" })}
|
||||
label={t("integration.field.createApp.label")}
|
||||
description={t("integration.field.createApp.description")}
|
||||
/>
|
||||
|
||||
<Collapse in={opened}>
|
||||
<TextInput placeholder={t("integration.field.appHref.placeholder")} {...form.getInputProps("appHref")} />
|
||||
</Collapse>
|
||||
|
||||
<Group justify="end" align="center">
|
||||
<Button variant="default" component={Link} href="/manage/integrations">
|
||||
{t("common.action.backToOverview")}
|
||||
@@ -166,4 +235,4 @@ const SecretKindsSegmentedControl = ({ secretKinds, form }: SecretKindsSegmented
|
||||
return <SegmentedControl fullWidth data={secretKindGroups} onChange={onChange}></SegmentedControl>;
|
||||
};
|
||||
|
||||
type FormType = Omit<z.infer<typeof validation.integration.create>, "kind">;
|
||||
type FormType = z.infer<typeof formSchema>;
|
||||
@@ -7,14 +7,14 @@ import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
import type { validation } from "@homarr/validation";
|
||||
import type { integrationCreateSchema } from "@homarr/validation/integration";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { NewIntegrationForm } from "./_integration-new-form";
|
||||
|
||||
interface NewIntegrationPageProps {
|
||||
searchParams: Promise<
|
||||
Partial<z.infer<typeof validation.integration.create>> & {
|
||||
Partial<z.infer<typeof integrationCreateSchema>> & {
|
||||
kind: IntegrationKind;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
.root {
|
||||
border-radius: var(--mantine-radius-lg);
|
||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||
}
|
||||
|
||||
.item {
|
||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||
border: 1px solid transparent;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
transition: transform 150ms ease;
|
||||
overflow: hidden;
|
||||
|
||||
&[data-first="true"] {
|
||||
border-radius: var(--mantine-radius-lg) var(--mantine-radius-lg) 0 0;
|
||||
}
|
||||
|
||||
&[data-last="true"] {
|
||||
border-radius: 0 0 var(--mantine-radius-lg) var(--mantine-radius-lg);
|
||||
}
|
||||
|
||||
&[data-active] {
|
||||
transform: scale(1.01);
|
||||
z-index: 1;
|
||||
background-color: var(--mantine-color-body);
|
||||
border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
|
||||
box-shadow: var(--mantine-shadow-md);
|
||||
border-radius: var(--mantine-radius-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.chevron {
|
||||
&[data-rotate] {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import { NoResults } from "~/components/no-results";
|
||||
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
|
||||
import { DeleteIntegrationActionButton } from "./_integration-buttons";
|
||||
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
|
||||
import classes from "./page.module.css";
|
||||
|
||||
interface IntegrationsPageProps {
|
||||
searchParams: Promise<{
|
||||
@@ -133,7 +134,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
return <NoResults icon={IconPlugX} title={t("page.list.noResults.title")} />;
|
||||
}
|
||||
|
||||
const grouppedIntegrations = integrations.reduce(
|
||||
const groupedIntegrations = integrations.reduce(
|
||||
(acc, integration) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!acc[integration.kind]) {
|
||||
@@ -147,11 +148,13 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
{} as Record<IntegrationKind, RouterOutputs["integration"]["all"]>,
|
||||
);
|
||||
|
||||
const entries = objectEntries(groupedIntegrations);
|
||||
|
||||
return (
|
||||
<ActiveTabAccordion defaultValue={activeTab} variant="separated">
|
||||
{objectEntries(grouppedIntegrations).map(([kind, integrations]) => (
|
||||
<AccordionItem key={kind} value={kind}>
|
||||
<AccordionControl icon={<IntegrationAvatar size="sm" kind={kind} />}>
|
||||
<ActiveTabAccordion defaultValue={activeTab} radius="lg" classNames={classes}>
|
||||
{entries.map(([kind, integrations], index) => (
|
||||
<AccordionItem key={kind} value={kind} data-first={index === 0} data-last={index === entries.length - 1}>
|
||||
<AccordionControl icon={<IntegrationAvatar size="sm" kind={kind} radius="sm" />}>
|
||||
<Group>
|
||||
<Text>{getIntegrationName(kind)}</Text>
|
||||
<CountBadge count={integrations.length} />
|
||||
@@ -205,21 +208,20 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
<Stack gap={0}>
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Text>{integration.name}</Text>
|
||||
{hasFullAccess ||
|
||||
(integration.permissions.hasFullAccess && (
|
||||
<ActionIconGroup>
|
||||
<ActionIcon
|
||||
component={Link}
|
||||
href={`/manage/integrations/edit/${integration.id}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
||||
>
|
||||
<IconPencil size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
|
||||
</ActionIconGroup>
|
||||
))}
|
||||
{(hasFullAccess || integration.permissions.hasFullAccess) && (
|
||||
<ActionIconGroup>
|
||||
<ActionIcon
|
||||
component={Link}
|
||||
href={`/manage/integrations/edit/${integration.id}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
||||
>
|
||||
<IconPencil size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
|
||||
</ActionIconGroup>
|
||||
)}
|
||||
</Group>
|
||||
<Anchor href={integration.url} target="_blank" rel="noreferrer" size="sm">
|
||||
{integration.url}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { AppShellMain } from "@mantine/core";
|
||||
import {
|
||||
IconAffiliateFilled,
|
||||
IconBook2,
|
||||
IconBox,
|
||||
IconBrandDiscord,
|
||||
IconBrandDocker,
|
||||
IconBrandGithub,
|
||||
IconBrandTablerFilled,
|
||||
IconCertificate,
|
||||
IconClipboardListFilled,
|
||||
IconDirectionsFilled,
|
||||
IconGitFork,
|
||||
IconHome,
|
||||
IconInfoSmall,
|
||||
IconLayoutDashboard,
|
||||
IconLogs,
|
||||
IconHelpSquareRoundedFilled,
|
||||
IconHomeFilled,
|
||||
IconLayoutDashboardFilled,
|
||||
IconMailForward,
|
||||
IconPhoto,
|
||||
IconPlug,
|
||||
IconQuestionMark,
|
||||
IconReport,
|
||||
IconPhotoFilled,
|
||||
IconPointerFilled,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconTool,
|
||||
IconUser,
|
||||
IconSettingsFilled,
|
||||
IconUserFilled,
|
||||
IconUsers,
|
||||
IconUsersGroup,
|
||||
} from "@tabler/icons-react";
|
||||
@@ -28,9 +28,11 @@ import {
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { isProviderEnabled } from "@homarr/auth/server";
|
||||
import { createDocumentationLink } from "@homarr/definitions";
|
||||
import { env } from "@homarr/docker/env";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { MainHeader } from "~/components/layout/header";
|
||||
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
|
||||
import type { NavigationLink } from "~/components/layout/navigation";
|
||||
import { MainNavigation } from "~/components/layout/navigation";
|
||||
import { ClientShell } from "~/components/layout/shell";
|
||||
@@ -41,11 +43,11 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
const navigationLinks: NavigationLink[] = [
|
||||
{
|
||||
label: t("items.home"),
|
||||
icon: IconHome,
|
||||
icon: IconHomeFilled,
|
||||
href: "/manage",
|
||||
},
|
||||
{
|
||||
icon: IconLayoutDashboard,
|
||||
icon: IconLayoutDashboardFilled,
|
||||
href: "/manage/boards",
|
||||
label: t("items.boards"),
|
||||
},
|
||||
@@ -54,9 +56,12 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
href: "/manage/apps",
|
||||
label: t("items.apps"),
|
||||
hidden: !session,
|
||||
iconProps: {
|
||||
strokeWidth: 2.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: IconPlug,
|
||||
icon: IconAffiliateFilled,
|
||||
href: "/manage/integrations",
|
||||
label: t("items.integrations"),
|
||||
hidden: !session,
|
||||
@@ -66,15 +71,18 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
href: "/manage/search-engines",
|
||||
label: t("items.searchEngies"),
|
||||
hidden: !session,
|
||||
iconProps: {
|
||||
strokeWidth: 2.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: IconPhoto,
|
||||
icon: IconPhotoFilled,
|
||||
href: "/manage/medias",
|
||||
label: t("items.medias"),
|
||||
hidden: !session,
|
||||
},
|
||||
{
|
||||
icon: IconUser,
|
||||
icon: IconUserFilled,
|
||||
label: t("items.users.label"),
|
||||
hidden: !session?.user.permissions.includes("admin"),
|
||||
items: [
|
||||
@@ -98,7 +106,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
},
|
||||
{
|
||||
label: t("items.tools.label"),
|
||||
icon: IconTool,
|
||||
icon: IconPointerFilled,
|
||||
// As permissions always include there children permissions, we can check other-view-logs as admin includes it
|
||||
hidden: !session?.user.permissions.includes("other-view-logs"),
|
||||
items: [
|
||||
@@ -106,17 +114,23 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
label: t("items.tools.items.docker"),
|
||||
icon: IconBrandDocker,
|
||||
href: "/manage/tools/docker",
|
||||
hidden: !session?.user.permissions.includes("admin"),
|
||||
hidden: !(session?.user.permissions.includes("admin") && env.ENABLE_DOCKER),
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.kubernetes"),
|
||||
icon: IconBox,
|
||||
href: "/manage/tools/kubernetes",
|
||||
hidden: !(session?.user.permissions.includes("admin") && env.ENABLE_KUBERNETES),
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.api"),
|
||||
icon: IconPlug,
|
||||
icon: IconDirectionsFilled,
|
||||
href: "/manage/tools/api",
|
||||
hidden: !session?.user.permissions.includes("admin"),
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.logs"),
|
||||
icon: IconLogs,
|
||||
icon: IconBrandTablerFilled,
|
||||
href: "/manage/tools/logs",
|
||||
hidden: !session?.user.permissions.includes("other-view-logs"),
|
||||
},
|
||||
@@ -128,7 +142,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.tasks"),
|
||||
icon: IconReport,
|
||||
icon: IconClipboardListFilled,
|
||||
href: "/manage/tools/tasks",
|
||||
hidden: !session?.user.permissions.includes("admin"),
|
||||
},
|
||||
@@ -137,12 +151,12 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
{
|
||||
label: t("items.settings"),
|
||||
href: "/manage/settings",
|
||||
icon: IconSettings,
|
||||
icon: IconSettingsFilled,
|
||||
hidden: !session?.user.permissions.includes("admin"),
|
||||
},
|
||||
{
|
||||
label: t("items.help.label"),
|
||||
icon: IconQuestionMark,
|
||||
icon: IconHelpSquareRoundedFilled,
|
||||
items: [
|
||||
{
|
||||
label: t("items.help.items.documentation"),
|
||||
@@ -172,7 +186,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
},
|
||||
{
|
||||
label: t("items.about"),
|
||||
icon: IconInfoSmall,
|
||||
icon: homarrLogoPath,
|
||||
href: "/manage/about",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { JSX } from "react";
|
||||
import { Button, FileButton } from "@mantine/core";
|
||||
import { Button } from "@mantine/core";
|
||||
import { IconUpload } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import type { MaybePromise } from "@homarr/common/types";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { UploadMedia } from "@homarr/forms-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { supportedMediaUploadFormats } from "@homarr/validation";
|
||||
|
||||
export const UploadMediaButton = () => {
|
||||
const t = useI18n();
|
||||
@@ -27,45 +23,3 @@ export const UploadMediaButton = () => {
|
||||
</UploadMedia>
|
||||
);
|
||||
};
|
||||
|
||||
interface UploadMediaProps {
|
||||
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
|
||||
onSettled?: () => MaybePromise<void>;
|
||||
onSuccess?: (media: { id: string; url: string }) => MaybePromise<void>;
|
||||
}
|
||||
|
||||
export const UploadMedia = ({ children, onSettled, onSuccess }: UploadMediaProps) => {
|
||||
const t = useI18n();
|
||||
const { mutateAsync, isPending } = clientApi.media.uploadMedia.useMutation();
|
||||
|
||||
const handleFileUploadAsync = async (file: File | null) => {
|
||||
if (!file) return;
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
await mutateAsync(formData, {
|
||||
async onSuccess(mediaId) {
|
||||
showSuccessNotification({
|
||||
message: t("media.action.upload.notification.success.message"),
|
||||
});
|
||||
await onSuccess?.({
|
||||
id: mediaId,
|
||||
url: `/api/user-medias/${mediaId}`,
|
||||
});
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
message: t("media.action.upload.notification.error.message"),
|
||||
});
|
||||
},
|
||||
async onSettled() {
|
||||
await onSettled?.();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FileButton onChange={handleFileUploadAsync} accept={supportedMediaUploadFormats.join(",")}>
|
||||
{({ onClick }) => children({ onClick, loading: isPending })}
|
||||
</FileButton>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Group,
|
||||
Image,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
@@ -61,7 +61,7 @@ export default async function GroupsListPage(props: MediaListPageProps) {
|
||||
const { items: medias, totalCount } = await api.media.getPaginated(searchParams);
|
||||
|
||||
return (
|
||||
<ManageContainer size="xl">
|
||||
<ManageContainer>
|
||||
<DynamicBreadcrumb />
|
||||
<Stack>
|
||||
<Title>{t("media.plural")}</Title>
|
||||
@@ -92,7 +92,8 @@ export default async function GroupsListPage(props: MediaListPageProps) {
|
||||
</TableTbody>
|
||||
</Table>
|
||||
|
||||
<Group justify="end">
|
||||
{/* Added margin to not hide pagination behind affix-button */}
|
||||
<Group justify="end" mb={48}>
|
||||
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -113,11 +114,12 @@ const Row = async ({ media }: RowProps) => {
|
||||
<TableTr>
|
||||
<TableTd w={64}>
|
||||
<Image
|
||||
// Switched to mantine image because next/image doesn't support svgs
|
||||
src={createLocalImageUrl(media.id)}
|
||||
alt={media.name}
|
||||
width={64}
|
||||
height={64}
|
||||
style={{ objectFit: "contain" }}
|
||||
w={64}
|
||||
h={64}
|
||||
fit="contain"
|
||||
/>
|
||||
</TableTd>
|
||||
<TableTd>{media.name}</TableTd>
|
||||
|
||||
@@ -83,7 +83,7 @@ export default async function ManagementPage() {
|
||||
{links.map(
|
||||
(link) =>
|
||||
!link.hidden && (
|
||||
<Card component={Link} href={link.href} key={link.href} withBorder>
|
||||
<Card component={Link} href={link.href} key={link.href} radius="lg">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group wrap="nowrap">
|
||||
<Text size="2.4rem" fw="bolder">
|
||||
|
||||
@@ -9,13 +9,12 @@ import type { z } from "zod";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { searchEngineTypes } from "@homarr/definitions";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { IconPicker } from "@homarr/forms-collection";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||
|
||||
import { IconPicker } from "~/components/icons/picker/icon-picker";
|
||||
|
||||
type FormType = z.infer<typeof validation.searchEngine.manage>;
|
||||
type FormType = z.infer<typeof searchEngineManageSchema>;
|
||||
|
||||
interface SearchEngineFormProps {
|
||||
submitButtonTranslation: (t: TranslationFunction) => string;
|
||||
@@ -31,7 +30,7 @@ export const SearchEngineForm = (props: SearchEngineFormProps) => {
|
||||
|
||||
const [integrationData] = clientApi.integration.allThatSupportSearch.useSuspenseQuery();
|
||||
|
||||
const form = useZodForm(validation.searchEngine.manage, {
|
||||
const form = useZodForm(searchEngineManageSchema, {
|
||||
initialValues: initialValues ?? {
|
||||
name: "",
|
||||
short: "",
|
||||
@@ -58,7 +57,7 @@ export const SearchEngineForm = (props: SearchEngineFormProps) => {
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<IconPicker initialValue={initialValues?.iconUrl} {...form.getInputProps("iconUrl")} />
|
||||
<IconPicker {...form.getInputProps("iconUrl")} />
|
||||
|
||||
<Fieldset legend={t("search.engine.page.edit.configControl")}>
|
||||
<SegmentedControl
|
||||
|
||||
@@ -23,7 +23,9 @@ export const SearchEngineDeleteButton = ({ searchEngine }: SearchEngineDeleteBut
|
||||
const onClick = useCallback(() => {
|
||||
openConfirmModal({
|
||||
title: t("title"),
|
||||
children: t("message", searchEngine),
|
||||
children: t("message", {
|
||||
name: searchEngine.name,
|
||||
}),
|
||||
onConfirm: () => {
|
||||
mutate(
|
||||
{ id: searchEngine.id },
|
||||
|
||||
@@ -10,7 +10,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { validation } from "@homarr/validation";
|
||||
import type { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||
|
||||
import { SearchEngineForm } from "../../_form";
|
||||
|
||||
@@ -41,7 +41,7 @@ export const SearchEngineEditForm = ({ searchEngine }: SearchEngineEditFormProps
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(values: z.infer<typeof validation.searchEngine.manage>) => {
|
||||
(values: z.infer<typeof searchEngineManageSchema>) => {
|
||||
mutate({
|
||||
id: searchEngine.id,
|
||||
...values,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import type { validation } from "@homarr/validation";
|
||||
import type { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||
|
||||
import { SearchEngineForm } from "../_form";
|
||||
|
||||
@@ -35,7 +35,7 @@ export const SearchEngineNewForm = () => {
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(values: z.infer<typeof validation.searchEngine.manage>) => {
|
||||
(values: z.infer<typeof searchEngineManageSchema>) => {
|
||||
mutate(values);
|
||||
},
|
||||
[mutate],
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
|
||||
<Stack>
|
||||
<Title>{tEngine("page.list.title")}</Title>
|
||||
<Group justify="space-between" align="center">
|
||||
<SearchInput placeholder={`${tEngine("search")}...`} defaultValue={searchParams.search} />
|
||||
<SearchInput placeholder={`${tEngine("search")}...`} defaultValue={searchParams.search} flexExpand />
|
||||
{session.user.permissions.includes("search-engine-create") && (
|
||||
<MobileAffixButton component={Link} href="/manage/search-engines/new">
|
||||
{tEngine("page.create.title")}
|
||||
@@ -61,7 +61,8 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Group justify="end">
|
||||
{/* Added margin to not hide pagination behind affix-button */}
|
||||
<Group justify="end" mb={48}>
|
||||
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { Group, Text } from "@mantine/core";
|
||||
import { IconLayoutDashboard } from "@tabler/icons-react";
|
||||
import { Switch, Text } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { SelectWithCustomItems } from "@homarr/ui";
|
||||
|
||||
import { BoardSelect } from "~/components/board/board-select";
|
||||
import { CommonSettingsForm } from "./common-form";
|
||||
|
||||
export const BoardSettingsForm = ({ defaultValues }: { defaultValues: ServerSettings["board"] }) => {
|
||||
@@ -18,44 +17,33 @@ export const BoardSettingsForm = ({ defaultValues }: { defaultValues: ServerSett
|
||||
<CommonSettingsForm settingKey="board" defaultValues={defaultValues}>
|
||||
{(form) => (
|
||||
<>
|
||||
<SelectWithCustomItems
|
||||
<BoardSelect
|
||||
label={tBoard("homeBoard.label")}
|
||||
description={tBoard("homeBoard.description")}
|
||||
data={selectableBoards.map((board) => ({
|
||||
value: board.id,
|
||||
label: board.name,
|
||||
image: board.logoImageUrl,
|
||||
}))}
|
||||
SelectOption={({ label, image }: { value: string; label: string; image: string | null }) => (
|
||||
<Group>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{image ? <img width={16} height={16} src={image} alt={label} /> : <IconLayoutDashboard size={16} />}
|
||||
<Text fz="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
clearable
|
||||
boards={selectableBoards}
|
||||
{...form.getInputProps("homeBoardId")}
|
||||
/>
|
||||
<SelectWithCustomItems
|
||||
|
||||
<BoardSelect
|
||||
label={tBoard("homeBoard.mobileLabel")}
|
||||
description={tBoard("homeBoard.description")}
|
||||
data={selectableBoards.map((board) => ({
|
||||
value: board.id,
|
||||
label: board.name,
|
||||
image: board.logoImageUrl,
|
||||
}))}
|
||||
SelectOption={({ label, image }: { value: string; label: string; image: string | null }) => (
|
||||
<Group>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{image ? <img width={16} height={16} src={image} alt={label} /> : <IconLayoutDashboard size={16} />}
|
||||
<Text fz="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
clearable
|
||||
boards={selectableBoards}
|
||||
{...form.getInputProps("mobileHomeBoardId")}
|
||||
/>
|
||||
|
||||
<Text fw={500}>{tBoard("status.title")}</Text>
|
||||
<Switch
|
||||
{...form.getInputProps("enableStatusByDefault", { type: "checkbox" })}
|
||||
label={tBoard("status.enableStatusByDefault.label")}
|
||||
description={tBoard("status.enableStatusByDefault.description")}
|
||||
/>
|
||||
<Switch
|
||||
{...form.getInputProps("forceDisableStatus", { type: "checkbox" })}
|
||||
label={tBoard("status.forceDisableStatus.label")}
|
||||
description={tBoard("status.forceDisableStatus.description")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CommonSettingsForm>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { Button, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { ActionIcon, Button, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
@@ -20,7 +21,8 @@ interface ApiKeysManagementProps {
|
||||
|
||||
export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
|
||||
const { openModal } = useModalAction(CopyApiKeyModal);
|
||||
const { mutate, isPending } = clientApi.apiKeys.create.useMutation({
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const { mutate: mutateCreate, isPending: isPendingCreate } = clientApi.apiKeys.create.useMutation({
|
||||
async onSuccess(data) {
|
||||
openModal({
|
||||
apiKey: data.apiKey,
|
||||
@@ -28,7 +30,26 @@ export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
|
||||
await revalidatePathActionAsync("/manage/tools/api");
|
||||
},
|
||||
});
|
||||
const { mutateAsync: mutateDeleteAsync, isPending: isPendingDelete } = clientApi.apiKeys.delete.useMutation({
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/manage/tools/api");
|
||||
},
|
||||
});
|
||||
|
||||
const t = useScopedI18n("management.page.tool.api.tab.apiKey");
|
||||
const handleDelete = useCallback(
|
||||
(id: string) => {
|
||||
openConfirmModal({
|
||||
title: t("modal.delete.title"),
|
||||
children: t("modal.delete.text"),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
async onConfirm() {
|
||||
await mutateDeleteAsync({ apiKeyId: id });
|
||||
},
|
||||
});
|
||||
},
|
||||
[t, openConfirmModal, mutateDeleteAsync],
|
||||
);
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<RouterOutputs["apiKeys"]["getAll"][number]>[]>(
|
||||
() => [
|
||||
@@ -46,8 +67,18 @@ export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: t("table.header.actions"),
|
||||
Cell: ({ row }) => (
|
||||
<Group gap="xs">
|
||||
<ActionIcon onClick={() => handleDelete(row.original.id)} loading={isPendingDelete} c="red">
|
||||
<IconTrash size="1rem" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
[t, handleDelete, isPendingDelete],
|
||||
);
|
||||
|
||||
const table = useMantineReactTable({
|
||||
@@ -56,9 +87,9 @@ export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
|
||||
renderTopToolbarCustomActions: () => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
mutate();
|
||||
mutateCreate();
|
||||
}}
|
||||
loading={isPending}
|
||||
loading={isPendingCreate}
|
||||
>
|
||||
{t("button.createApiToken")}
|
||||
</Button>
|
||||
|
||||
@@ -1434,26 +1434,30 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:vertical:start:decrement {
|
||||
background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
|
||||
background:
|
||||
linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
|
||||
linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:vertical:end:increment {
|
||||
background: linear-gradient(310deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%);
|
||||
background:
|
||||
linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(180deg, #696969 40%, transparent 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:horizontal:end:increment {
|
||||
background: linear-gradient(210deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%);
|
||||
background:
|
||||
linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(90deg, #696969 30%, transparent 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:horizontal:start:decrement {
|
||||
background: linear-gradient(30deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%);
|
||||
background:
|
||||
linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%),
|
||||
linear-gradient(270deg, #696969 30%, transparent 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
@@ -1681,28 +1685,32 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:vertical:start:decrement {
|
||||
background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
|
||||
background:
|
||||
linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
|
||||
linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:vertical:end:increment {
|
||||
background: linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
background:
|
||||
linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:horizontal:end:increment {
|
||||
background: linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
background:
|
||||
linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
|
||||
background-color: #b6b6b6;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:horizontal:start:decrement {
|
||||
background: linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
background:
|
||||
linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
|
||||
linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
|
||||
background-color: #b6b6b6;
|
||||
|
||||
@@ -1,82 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { Button, FileInput, Group, Stack } from "@mantine/core";
|
||||
import { IconCertificate } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { Button } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddCertificateModal } from "@homarr/modals-collection";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { superRefineCertificateFile } from "@homarr/validation";
|
||||
|
||||
export const AddCertificateButton = () => {
|
||||
const { openModal } = useModalAction(AddCertificateModal);
|
||||
const t = useI18n();
|
||||
|
||||
const handleClick = () => {
|
||||
openModal({});
|
||||
openModal({
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/manage/tools/certificates");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return <Button onClick={handleClick}>{t("certificate.action.create.label")}</Button>;
|
||||
};
|
||||
|
||||
const AddCertificateModal = createModal(({ actions }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(
|
||||
z.object({
|
||||
file: z.instanceof(File).nullable().superRefine(superRefineCertificateFile),
|
||||
}),
|
||||
{
|
||||
initialValues: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
file: null!,
|
||||
},
|
||||
},
|
||||
);
|
||||
const { mutateAsync } = clientApi.certificates.addCertificate.useMutation();
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
const formData = new FormData();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
formData.set("file", values.file!);
|
||||
await mutateAsync(formData, {
|
||||
async onSuccess() {
|
||||
showSuccessNotification({
|
||||
title: t("certificate.action.create.notification.success.title"),
|
||||
message: t("certificate.action.create.notification.success.message"),
|
||||
});
|
||||
await revalidatePathActionAsync("/manage/tools/certificates");
|
||||
actions.closeModal();
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("certificate.action.create.notification.error.title"),
|
||||
message: t("certificate.action.create.notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<FileInput leftSection={<IconCertificate size={16} />} {...form.getInputProps("file")} />
|
||||
<Group justify="end">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={form.submitting}>
|
||||
{t("common.action.add")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle(t) {
|
||||
return t("certificate.action.create.label");
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { ActionIcon } from "@mantine/core";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
import { useConfirmModal } from "@homarr/modals";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
interface RemoveHostnameActionIconProps {
|
||||
hostname: string;
|
||||
thumbprint: string;
|
||||
}
|
||||
|
||||
export const RemoveHostnameActionIcon = (input: RemoveHostnameActionIconProps) => {
|
||||
const { mutateAsync } = clientApi.certificates.removeTrustedHostname.useMutation();
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const t = useI18n();
|
||||
|
||||
const handleRemove = () => {
|
||||
openConfirmModal({
|
||||
title: t("certificate.action.removeHostname.label"),
|
||||
children: t("certificate.action.removeHostname.confirm"),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
async onConfirm() {
|
||||
await mutateAsync(input, {
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/manage/tools/certificates/hostnames");
|
||||
showSuccessNotification({
|
||||
title: t("certificate.action.removeHostname.notification.success.title"),
|
||||
message: t("certificate.action.removeHostname.notification.success.message"),
|
||||
});
|
||||
},
|
||||
onError() {
|
||||
showErrorNotification({
|
||||
title: t("certificate.action.removeHostname.notification.error.title"),
|
||||
message: t("certificate.action.removeHostname.notification.error.message"),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionIcon color="red" variant="subtle" onClick={handleRemove}>
|
||||
<IconTrash color="red" size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import { X509Certificate } from "node:crypto";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { IconCertificateOff } from "@tabler/icons-react";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { getTrustedCertificateHostnamesAsync } from "@homarr/certificates/server";
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { NoResults } from "~/components/no-results";
|
||||
import { RemoveHostnameActionIcon } from "./_components/remove-hostname";
|
||||
|
||||
export default async function TrustedHostnamesPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user.permissions.includes("admin")) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const t = await getI18n();
|
||||
|
||||
const trustedHostnames = await getTrustedCertificateHostnamesAsync().then((hostnames) => {
|
||||
return hostnames.map((hostname) => {
|
||||
let subject: string | null;
|
||||
try {
|
||||
subject = new X509Certificate(hostname.certificate).subject;
|
||||
} catch {
|
||||
subject = null;
|
||||
}
|
||||
return {
|
||||
...hostname,
|
||||
subject,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DynamicBreadcrumb />
|
||||
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Stack gap={4}>
|
||||
<Title>{t("certificate.page.hostnames.title")}</Title>
|
||||
<Text>{t("certificate.page.hostnames.description")}</Text>
|
||||
</Stack>
|
||||
|
||||
<Button variant="default" component={Link} href="/manage/tools/certificates">
|
||||
{t("certificate.page.hostnames.toCertificates")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{trustedHostnames.length === 0 && (
|
||||
<NoResults icon={IconCertificateOff} title={t("certificate.page.hostnames.noResults.title")} />
|
||||
)}
|
||||
|
||||
{trustedHostnames.length >= 1 && (
|
||||
<Table>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>{t("certificate.field.hostname.label")}</TableTh>
|
||||
<TableTh>{t("certificate.field.subject.label")}</TableTh>
|
||||
<TableTh>{t("certificate.field.fingerprint.label")}</TableTh>
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{trustedHostnames.map(({ hostname, subject, thumbprint }) => (
|
||||
<TableTr key={`${hostname}-${thumbprint}`}>
|
||||
<TableTd>{hostname}</TableTd>
|
||||
<TableTd>{subject}</TableTd>
|
||||
<TableTd>{thumbprint}</TableTd>
|
||||
<TableTd>
|
||||
<Group justify="end">
|
||||
<RemoveHostnameActionIcon hostname={hostname} thumbprint={thumbprint} />
|
||||
</Group>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { X509Certificate } from "node:crypto";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconCertificate, IconCertificateOff } from "@tabler/icons-react";
|
||||
import { Button, Card, Group, SimpleGrid, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconAlertTriangle, IconCertificate, IconCertificateOff } from "@tabler/icons-react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
@@ -31,11 +32,27 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
||||
const t = await getI18n();
|
||||
const certificates = await loadCustomRootCertificatesAsync();
|
||||
const x509Certificates = certificates
|
||||
.map((cert) => ({
|
||||
...cert,
|
||||
x509: new X509Certificate(cert.content),
|
||||
}))
|
||||
.sort((certA, certB) => certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime());
|
||||
.map((cert) => {
|
||||
try {
|
||||
const x509 = new X509Certificate(cert.content);
|
||||
return {
|
||||
...cert,
|
||||
isError: false,
|
||||
x509,
|
||||
} as const;
|
||||
} catch {
|
||||
return {
|
||||
...cert,
|
||||
isError: true,
|
||||
x509: null,
|
||||
} as const;
|
||||
}
|
||||
})
|
||||
.sort((certA, certB) => {
|
||||
if (certA.isError) return -1;
|
||||
if (certB.isError) return 1;
|
||||
return certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime();
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -48,7 +65,12 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
||||
<Text>{t("certificate.page.list.description")}</Text>
|
||||
</Stack>
|
||||
|
||||
<AddCertificateButton />
|
||||
<Group>
|
||||
<Button variant="default" component={Link} href="/manage/tools/certificates/hostnames">
|
||||
{t("certificate.page.list.toHostnames")}
|
||||
</Button>
|
||||
<AddCertificateButton />
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{x509Certificates.length === 0 && (
|
||||
@@ -57,32 +79,47 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
||||
|
||||
<SimpleGrid cols={{ sm: 1, lg: 2, xl: 3 }} spacing="lg">
|
||||
{x509Certificates.map((cert) => (
|
||||
<Card key={cert.x509.fingerprint} withBorder>
|
||||
<Card key={cert.fileName} withBorder>
|
||||
<Group wrap="nowrap">
|
||||
<IconCertificate
|
||||
color={getMantineColor(iconColor(cert.x509.validToDate), 6)}
|
||||
style={{ minWidth: 32 }}
|
||||
size={32}
|
||||
stroke={1.5}
|
||||
/>
|
||||
{cert.isError ? (
|
||||
<IconAlertTriangle
|
||||
color={getMantineColor("red", 6)}
|
||||
style={{ minWidth: 32 }}
|
||||
size={32}
|
||||
stroke={1.5}
|
||||
/>
|
||||
) : (
|
||||
<IconCertificate
|
||||
color={getMantineColor(iconColor(cert.x509.validToDate), 6)}
|
||||
style={{ minWidth: 32 }}
|
||||
size={32}
|
||||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
<Stack flex={1} gap="xs" maw="calc(100% - 48px)">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
|
||||
{cert.x509.subject}
|
||||
{cert.isError ? t("certificate.page.list.invalid.title") : cert.x509.subject}
|
||||
</Text>
|
||||
<Text c="gray.6" ta="end" size="sm">
|
||||
{cert.fileName}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="gray.6" title={cert.x509.validToDate.toISOString()}>
|
||||
{t("certificate.page.list.expires", {
|
||||
when: new Intl.RelativeTimeFormat(locale).format(
|
||||
dayjs(cert.x509.validToDate).diff(dayjs(), "days"),
|
||||
"days",
|
||||
),
|
||||
})}
|
||||
</Text>
|
||||
{cert.isError ? (
|
||||
<Text size="sm" c="gray.6">
|
||||
{t("certificate.page.list.invalid.description")}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="sm" c="gray.6" title={cert.x509.validToDate.toISOString()}>
|
||||
{t("certificate.page.list.expires", {
|
||||
when: new Intl.RelativeTimeFormat(locale).format(
|
||||
dayjs(cert.x509.validToDate).diff(dayjs(), "days"),
|
||||
"days",
|
||||
),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
<RemoveCertificate fileName={cert.fileName} />
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import type { ContainerState } from "@homarr/docker";
|
||||
import { containerStateColorMap } from "@homarr/docker/shared";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { AddDockerAppToHomarr } from "@homarr/modals-collection";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
@@ -35,7 +36,13 @@ const createColumns = (
|
||||
Cell({ renderedCellValue, row }) {
|
||||
return (
|
||||
<Group gap="xs">
|
||||
<Avatar variant="outline" radius="lg" size="md" src={row.original.iconUrl}>
|
||||
<Avatar
|
||||
variant="outline"
|
||||
radius="lg"
|
||||
size="md"
|
||||
styles={{ image: { objectFit: "contain" } }}
|
||||
src={row.original.iconUrl}
|
||||
>
|
||||
{row.original.name.at(0)?.toUpperCase()}
|
||||
</Avatar>
|
||||
<Text>{renderedCellValue}</Text>
|
||||
@@ -98,7 +105,7 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
|
||||
enableBottomToolbar: false,
|
||||
positionGlobalFilter: "right",
|
||||
mantineSearchTextInputProps: {
|
||||
placeholder: tDocker("table.search", { count: data.containers.length }),
|
||||
placeholder: tDocker("table.search", { count: String(data.containers.length) }),
|
||||
style: { minWidth: 300 },
|
||||
autoFocus: true,
|
||||
},
|
||||
@@ -140,8 +147,8 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
|
||||
{groupedAlert}
|
||||
<Text fw={500}>
|
||||
{tDocker("table.selected", {
|
||||
selectCount: table.getSelectedRowModel().rows.length,
|
||||
totalCount: table.getRowCount(),
|
||||
selectCount: String(table.getSelectedRowModel().rows.length),
|
||||
totalCount: String(table.getRowCount()),
|
||||
})}
|
||||
</Text>
|
||||
<ContainerActionBar selectedContainers={dockerContainers} />
|
||||
@@ -238,21 +245,11 @@ const ContainerActionBarButton = (props: ContainerActionBarButtonProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const containerStates = {
|
||||
created: "cyan",
|
||||
running: "green",
|
||||
paused: "yellow",
|
||||
restarting: "orange",
|
||||
exited: "red",
|
||||
removing: "pink",
|
||||
dead: "dark",
|
||||
} satisfies Record<ContainerState, MantineColor>;
|
||||
|
||||
const ContainerStateBadge = ({ state }: { state: ContainerState }) => {
|
||||
const t = useScopedI18n("docker.field.state.option");
|
||||
|
||||
return (
|
||||
<Badge size="lg" radius="sm" variant="light" w={120} color={containerStates[state]}>
|
||||
<Badge size="lg" radius="sm" variant="light" w={120} color={containerStateColorMap[state]}>
|
||||
{t(state)}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Stack, Title } from "@mantine/core";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { env } from "@homarr/docker/env";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
@@ -10,7 +11,7 @@ import { DockerTable } from "./docker-table";
|
||||
|
||||
export default async function DockerPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user || !session.user.permissions.includes("admin")) {
|
||||
if (!(session?.user.permissions.includes("admin") && env.ENABLE_DOCKER)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { SimpleGrid, Skeleton, Stack, Title } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createId } from "@homarr/db/client";
|
||||
import type { KubernetesLabelResourceType } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import KubernetesErrorPage from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/error";
|
||||
import { HeaderCard } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/header-card/header-card";
|
||||
import { ResourceGauge } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-gauge/resource-gauge";
|
||||
import { ResourceTile } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-tile/resource-tile";
|
||||
|
||||
export function ClusterDashboard() {
|
||||
const t = useI18n();
|
||||
|
||||
const {
|
||||
data: clusterData,
|
||||
isLoading: isClusterLoading,
|
||||
isError: isClusterError,
|
||||
} = clientApi.kubernetes.cluster.getCluster.useQuery();
|
||||
|
||||
const {
|
||||
data: resourceCountsData,
|
||||
isLoading: isResourceCountsLoading,
|
||||
isError: isResourceCountsError,
|
||||
} = clientApi.kubernetes.cluster.getClusterResourceCounts.useQuery();
|
||||
|
||||
return (
|
||||
<Stack bg="var(--mantine-color-body)">
|
||||
<Title>{t("kubernetes.cluster.title")}</Title>
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{isClusterError ? (
|
||||
Array.from({ length: 3 }).map(() => <KubernetesErrorPage key={createId()} />)
|
||||
) : isClusterLoading ? (
|
||||
Array.from({ length: 3 }).map(() => <Skeleton key={createId()} height={65} />)
|
||||
) : (
|
||||
<>
|
||||
<HeaderCard headerType={"providers"} value={clusterData ? clusterData.providers : ""} />
|
||||
<HeaderCard headerType={"version"} value={clusterData ? clusterData.kubernetesVersion : ""} />
|
||||
<HeaderCard headerType={"architecture"} value={clusterData ? clusterData.architecture : ""} />
|
||||
</>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
<Title>{t("kubernetes.cluster.capacity.title")}</Title>
|
||||
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{isClusterError
|
||||
? Array.from({ length: 3 }).map(() => <KubernetesErrorPage key={createId()} />)
|
||||
: isClusterLoading
|
||||
? Array.from({ length: 3 }).map(() => <Skeleton key={createId()} height={200} />)
|
||||
: clusterData?.capacity.map((capacity) => (
|
||||
<ResourceGauge kubernetesCapacity={capacity} key={capacity.type} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
<Title>{t("kubernetes.cluster.resources.title")}</Title>
|
||||
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{isResourceCountsError
|
||||
? Array.from({ length: 8 }).map(() => <KubernetesErrorPage key={createId()} />)
|
||||
: isResourceCountsLoading
|
||||
? Array.from({ length: 8 }).map(() => <Skeleton key={createId()} height={100} />)
|
||||
: resourceCountsData?.map((clusterResourceCount) => (
|
||||
<ResourceTile
|
||||
count={clusterResourceCount.count}
|
||||
label={clusterResourceCount.label as KubernetesLabelResourceType}
|
||||
key={clusterResourceCount.label}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Anchor, Center, Stack, Text } from "@mantine/core";
|
||||
import { IconCubeOff } from "@tabler/icons-react";
|
||||
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
export default function KubernetesErrorPage() {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Center>
|
||||
<Stack align="center">
|
||||
<IconCubeOff size={48} stroke={1.5} />
|
||||
<Stack align="center" gap="xs">
|
||||
<Text size="lg" fw={500}>
|
||||
{t("kubernetes.error.internalServerError")}
|
||||
</Text>
|
||||
<Anchor size="sm" component={Link} href="/manage/tools/logs">
|
||||
{t("common.action.checkLogs")}
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.header {
|
||||
direction: inherit;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: var(--mantine-spacing-xs);
|
||||
padding-left: calc(var(--mantine-spacing-xs) * 2);
|
||||
|
||||
@mixin hover {
|
||||
box-shadow: var(--mantine-shadow-md);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
background-image: linear-gradient(0, var(--mantine-color-blue-4), var(--mantine-color-blue-9));
|
||||
}
|
||||
|
||||
@mixin rtl {
|
||||
padding-left: var(--mantine-spacing-xs);
|
||||
padding-right: calc(var(--mantine-spacing-xs) * 2);
|
||||
|
||||
&::before {
|
||||
left: unset;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Card, Flex, Text, ThemeIcon } from "@mantine/core";
|
||||
|
||||
import { isLocaleRTL } from "@homarr/translation";
|
||||
import { useCurrentLocale, useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { HeaderIcon } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/header-card/header-icon";
|
||||
import classes from "./header-card.module.css";
|
||||
|
||||
export type HeaderTypes = "providers" | "version" | "architecture";
|
||||
|
||||
interface HeaderCardProps {
|
||||
headerType: HeaderTypes;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function HeaderCard(props: HeaderCardProps) {
|
||||
const t = useI18n();
|
||||
const currentLocale = useCurrentLocale();
|
||||
const isRTL = isLocaleRTL(currentLocale);
|
||||
|
||||
return (
|
||||
<Card className={classes.header}>
|
||||
<Flex align="center" justify={isRTL ? "space-between" : "flex-start"} gap="md" direction={"row"}>
|
||||
<ThemeIcon
|
||||
size="xl"
|
||||
radius="md"
|
||||
variant="gradient"
|
||||
gradient={{
|
||||
deg: 0,
|
||||
from: "var(--mantine-color-blue-4)",
|
||||
to: "var(--mantine-color-blue-9)",
|
||||
}}
|
||||
>
|
||||
<HeaderIcon type={props.headerType} />
|
||||
</ThemeIcon>
|
||||
<Text size="xl" fw={500} dir={isRTL ? "rtl" : "ltr"}>
|
||||
{isRTL
|
||||
? `${props.value} : ${t(`kubernetes.cluster.${props.headerType}`)}`
|
||||
: `${t(`kubernetes.cluster.${props.headerType}`)} : ${props.value}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IconBrandGit, IconCloudShare, IconGeometry } from "@tabler/icons-react";
|
||||
|
||||
import type { HeaderTypes } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/header-card/header-card";
|
||||
|
||||
interface HeaderIconProps {
|
||||
type: HeaderTypes;
|
||||
}
|
||||
|
||||
export function HeaderIcon({ type }: HeaderIconProps) {
|
||||
switch (type) {
|
||||
case "providers":
|
||||
return <IconCloudShare size={28} stroke={1.5} />;
|
||||
case "version":
|
||||
return <IconBrandGit size={28} stroke={1.5} />;
|
||||
default:
|
||||
return <IconGeometry size={28} stroke={1.5} />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.paper {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
padding: var(--mantine-spacing-xl);
|
||||
padding-top: calc(var(--mantine-spacing-xl) * 1.5 + 20px);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: calc(50% - 30px);
|
||||
border: groove white;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family:
|
||||
Greycliff CF,
|
||||
var(--mantine-font-family);
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from "react";
|
||||
import { Group, Paper, Progress, Text, ThemeIcon } from "@mantine/core";
|
||||
|
||||
import type { KubernetesCapacity } from "@homarr/definitions";
|
||||
import { isLocaleRTL } from "@homarr/translation";
|
||||
import { useCurrentLocale, useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { ResourceIcon } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-gauge/resource-icon";
|
||||
import classes from "./resource-gauge.module.css";
|
||||
|
||||
interface KubernetesResourceGaugeProps {
|
||||
kubernetesCapacity: KubernetesCapacity;
|
||||
}
|
||||
|
||||
export function ResourceGauge(props: KubernetesResourceGaugeProps) {
|
||||
const t = useI18n();
|
||||
const currentLocale = useCurrentLocale();
|
||||
const isRTL = Boolean(isLocaleRTL(currentLocale));
|
||||
|
||||
return (
|
||||
<Paper radius="md" withBorder className={classes.paper} mt={20}>
|
||||
<ThemeIcon className={classes.icon} size={60} radius={60} bg={"#326ce5"}>
|
||||
<ResourceIcon type={props.kubernetesCapacity.type} />
|
||||
</ThemeIcon>
|
||||
|
||||
<Text ta="center" fw={700} className={classes.title}>
|
||||
{props.kubernetesCapacity.type}
|
||||
</Text>
|
||||
|
||||
{props.kubernetesCapacity.resourcesStats.map((stat) => {
|
||||
const isReserved = stat.type === "Reserved";
|
||||
const labelKey = isReserved
|
||||
? "kubernetes.cluster.capacity.resource.reserved"
|
||||
: "kubernetes.cluster.capacity.resource.used";
|
||||
|
||||
return (
|
||||
<div key={stat.percentageValue}>
|
||||
<Group justify={"space-between"} mt="xs">
|
||||
<Text fz="sm" c="dimmed">
|
||||
{isRTL ? (
|
||||
<>
|
||||
{stat.capacityUnit && (
|
||||
<Text component="span" mr={4}>
|
||||
{stat.capacityUnit}
|
||||
</Text>
|
||||
)}
|
||||
<Text component="span">
|
||||
{stat.maxUsedValue} / {stat.usedValue}{" "}
|
||||
</Text>
|
||||
<Text component="span" fw={500}>
|
||||
{t(labelKey)}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text component="span" fw={500}>
|
||||
{t(labelKey)}
|
||||
</Text>
|
||||
<Text component="span">
|
||||
{" "}
|
||||
{stat.usedValue} / {stat.maxUsedValue}{" "}
|
||||
</Text>
|
||||
{stat.capacityUnit && (
|
||||
<Text component="span" ml={4}>
|
||||
{stat.capacityUnit}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{isRTL ? `%${stat.percentageValue}` : `${stat.percentageValue}%`}
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress value={stat.percentageValue} mt={5} color={getProgressBarColor(stat.percentageValue)} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
function getProgressBarColor(value: number): string {
|
||||
if (value > 50 && value < 65) {
|
||||
return "yellow";
|
||||
} else if (value >= 65) {
|
||||
return "red";
|
||||
}
|
||||
return "blue";
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { IconCpu, IconCube, IconDeviceDesktopAnalytics } from "@tabler/icons-react";
|
||||
|
||||
import type { KubernetesCapacityType } from "@homarr/definitions";
|
||||
|
||||
const resourceIcons = {
|
||||
CPU: IconCpu,
|
||||
Memory: IconDeviceDesktopAnalytics,
|
||||
Pods: IconCube,
|
||||
} satisfies Record<KubernetesCapacityType, React.ComponentType<{ size: number; stroke: number }>>;
|
||||
|
||||
interface ResourceIconProps {
|
||||
type: KubernetesCapacityType;
|
||||
size?: number;
|
||||
stroke?: number;
|
||||
}
|
||||
|
||||
export const ResourceIcon: React.FC<ResourceIconProps> = ({ type, size = 32, stroke = 1.5 }) => {
|
||||
const Icon = resourceIcons[type];
|
||||
return <Icon size={size} stroke={stroke} />;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
.cardContainer {
|
||||
transition:
|
||||
box-shadow 150ms ease,
|
||||
transform 100ms ease;
|
||||
|
||||
@mixin hover {
|
||||
box-shadow: var(--mantine-shadow-md);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Card, Group, Text } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
|
||||
import type { KubernetesLabelResourceType } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import classes from "./resource-tile.module.css";
|
||||
|
||||
interface ResourceTileProps {
|
||||
count: number;
|
||||
label: KubernetesLabelResourceType;
|
||||
}
|
||||
|
||||
export function ResourceTile(props: ResourceTileProps) {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<Card
|
||||
withBorder
|
||||
component={Link}
|
||||
href={`/manage/tools/kubernetes/${props.label}`}
|
||||
className={classes.cardContainer}
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Image src={`/images/kubernetes/${props.label}.svg`} alt={props.label} width={64} height={64} />
|
||||
<Group gap="xs">
|
||||
<Text size="xl" fw={700} tt="capitalize">
|
||||
{props.count} {t(`kubernetes.cluster.resources.${props.label}`)}
|
||||
</Text>
|
||||
<IconArrowRight />
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { MantineReactTable } from "mantine-react-table";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { KubernetesBaseResource } from "@homarr/definitions";
|
||||
import type { ScopedTranslationFunction } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
interface ConfigMapsTableComponentProps {
|
||||
initialConfigMaps: RouterOutputs["kubernetes"]["configMaps"]["getConfigMaps"];
|
||||
}
|
||||
|
||||
const createColumns = (
|
||||
t: ScopedTranslationFunction<"kubernetes.configmaps">,
|
||||
): MRT_ColumnDef<KubernetesBaseResource>[] => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: t("field.name.label"),
|
||||
enableClickToCopy: true,
|
||||
},
|
||||
{
|
||||
accessorKey: "namespace",
|
||||
header: t("field.namespace.label"),
|
||||
enableClickToCopy: true,
|
||||
},
|
||||
{
|
||||
accessorKey: "creationTimestamp",
|
||||
header: t("field.creationTimestamp.label"),
|
||||
Cell: ({ row }) => dayjs(row.original.creationTimestamp).fromNow(false),
|
||||
},
|
||||
];
|
||||
|
||||
export function ConfigmapsTable(initialData: ConfigMapsTableComponentProps) {
|
||||
const tConfigMaps = useScopedI18n("kubernetes.configmaps");
|
||||
|
||||
const { data } = clientApi.kubernetes.configMaps.getConfigMaps.useQuery(undefined, {
|
||||
initialData: initialData.initialConfigMaps,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
});
|
||||
|
||||
const table = useTranslatedMantineReactTable({
|
||||
data,
|
||||
enableDensityToggle: false,
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enablePagination: false,
|
||||
enableRowSelection: true,
|
||||
positionToolbarAlertBanner: "top",
|
||||
enableTableFooter: false,
|
||||
enableBottomToolbar: false,
|
||||
positionGlobalFilter: "right",
|
||||
initialState: { density: "xs", showGlobalFilter: true },
|
||||
mantineSearchTextInputProps: {
|
||||
placeholder: tConfigMaps("table.search", { count: String(data.length) }),
|
||||
style: { minWidth: 300 },
|
||||
autoFocus: true,
|
||||
},
|
||||
|
||||
columns: createColumns(tConfigMaps),
|
||||
});
|
||||
|
||||
return <MantineReactTable table={table} />;
|
||||
}
|
||||