Compare commits
118
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0513a8e6f5 | ||
|
|
0b200dc9e2 | ||
|
|
377f4302c0 | ||
|
|
740e9a5082 | ||
|
|
e7652ea4eb | ||
|
|
863f935ef4 | ||
|
|
7388c907a5 | ||
|
|
3d07306e7d | ||
|
|
f37fc0edfe | ||
|
|
c59cdeda47 | ||
|
|
ad444e8fa7 | ||
|
|
fa52b55828 | ||
|
|
1ce1113a8c | ||
|
|
e7a9797484 | ||
|
|
d104aea8b8 | ||
|
|
ab3ee3bac1 | ||
|
|
838066ac75 | ||
|
|
b1a559d801 | ||
|
|
eecf50d461 | ||
|
|
d862ae03e3 | ||
|
|
f6da091577 | ||
|
|
22f0cd885e | ||
|
|
08c6ce9071 | ||
|
|
c3e1a53d09 | ||
|
|
9e86de6e17 | ||
|
|
98c4cd1e39 | ||
|
|
f67702e1d3 | ||
|
|
a9e68e13d5 | ||
|
|
1b3770aafb | ||
|
|
7928f15452 | ||
|
|
80327337ce | ||
|
|
f72a994fb3 | ||
|
|
1910786f5b | ||
|
|
6ec66b1fe2 | ||
|
|
aafd883c32 | ||
|
|
f32242bf79 | ||
|
|
62aeb043b5 | ||
|
|
ffd2f802bb | ||
|
|
3e7bf62500 | ||
|
|
7071d76c52 | ||
|
|
c7a21b1a5f | ||
|
|
6274a66048 | ||
|
|
07f81cee92 | ||
|
|
3ad72e63b1 | ||
|
|
ed3f848e8a | ||
|
|
8484daaf75 | ||
|
|
aeed7dbfd0 | ||
|
|
68881e7ee5 | ||
|
|
61553cfc8e | ||
|
|
cf7336ba87 | ||
|
|
dac82347e8 | ||
|
|
74aa561f47 | ||
|
|
cb77a7dc6d | ||
|
|
81509fd96d | ||
|
|
960dc33af8 | ||
|
|
c051a42791 | ||
|
|
a542ee0798 | ||
|
|
9ac9042564 | ||
|
|
2fe3dcb01b | ||
|
|
a0a0449c70 | ||
|
|
d4afafe0f7 | ||
|
|
cae0cad92f | ||
|
|
df2a9a5329 | ||
|
|
8f6cc6fa60 | ||
|
|
d699aad529 | ||
|
|
0362487156 | ||
|
|
5b354aca20 | ||
|
|
124e8de6c5 | ||
|
|
a0585e3dd7 | ||
|
|
a3deed3b09 | ||
|
|
ae5aebda20 | ||
|
|
362c663060 | ||
|
|
0d1e3a625a | ||
|
|
ccd8a85e0f | ||
|
|
f0ba693077 | ||
|
|
94f176d544 | ||
|
|
a86fc5bcb8 | ||
|
|
9bdbe6f425 | ||
|
|
1b1671e0e6 | ||
|
|
3337cfa3c7 | ||
|
|
161fc86770 | ||
|
|
76ae423fd9 | ||
|
|
1f408685e6 | ||
|
|
b608ffe490 | ||
|
|
f579b283f2 | ||
|
|
9518ebb088 | ||
|
|
9895c1b3ff | ||
|
|
7e5ca418cc | ||
|
|
6b76d55de2 | ||
|
|
09f821b95d | ||
|
|
11149348fc | ||
|
|
c8202ab3c6 | ||
|
|
7a50829b6a | ||
|
|
778cf861cf | ||
|
|
6b8ffdfbc0 | ||
|
|
608280e670 | ||
|
|
5bf20aa353 | ||
|
|
3ba9502b26 | ||
|
|
862d07db68 | ||
|
|
d72a804ad9 | ||
|
|
2320d3c567 | ||
|
|
a0eda74fa4 | ||
|
|
bac4f64257 | ||
|
|
1f9983f16d | ||
|
|
48ce2af4c1 | ||
|
|
635c9a2687 | ||
|
|
c1324eab76 | ||
|
|
bfd7b70618 | ||
|
|
ad5f985645 | ||
|
|
4a184d23f2 | ||
|
|
09b49a242d | ||
|
|
10f720d74c | ||
|
|
9c3a46c071 | ||
|
|
e2e4656001 | ||
|
|
3253b2f138 | ||
|
|
f9c7e7711a | ||
|
|
8b553b80e6 | ||
|
|
75ebf075a5 |
No files matched your search
@@ -31,6 +31,8 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.23.0
|
||||
- 1.22.0
|
||||
- 1.21.0
|
||||
- 1.20.0
|
||||
- 1.19.1
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
|
||||
|
||||
- [ ] Builds without warnings or errors (``pnpm build``, autofix with ``pnpm format:fix``)
|
||||
- [ ] Pull request targets ``dev`` branch
|
||||
- [ ] 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)
|
||||
|
||||
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
|
||||
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
|
||||
+102
@@ -1,3 +1,105 @@
|
||||
## [1.23.0](https://github.com/homarr-labs/homarr/compare/v1.22.0...v1.23.0) (2025-06-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **releases-widget:** add `import from docker` functionality ([#3130](https://github.com/homarr-labs/homarr/issues/3130)) ([7071d76](https://github.com/homarr-labs/homarr/commit/7071d76c526f1b59a955abcc595894975d574218))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Allow nullable on cpuModelName and default to "Unknown CPU" if null in OMV Integration ([#3310](https://github.com/homarr-labs/homarr/issues/3310)) ([3e7bf62](https://github.com/homarr-labs/homarr/commit/3e7bf62500f4ce0a8662104e2a656ea98cf2558f))
|
||||
* csp prevents external stylesheets ([#3337](https://github.com/homarr-labs/homarr/issues/3337)) ([c7a21b1](https://github.com/homarr-labs/homarr/commit/c7a21b1a5f4e7c9d994d81b75a3fd4fa7c013daf))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.34.0 ([#3303](https://github.com/homarr-labs/homarr/issues/3303)) ([5b354ac](https://github.com/homarr-labs/homarr/commit/5b354aca2047e8316b331e327cf6031690dcfef9))
|
||||
* **deps:** update dependency dockerode to ^4.0.7 ([#3331](https://github.com/homarr-labs/homarr/issues/3331)) ([07f81ce](https://github.com/homarr-labs/homarr/commit/07f81cee92627e3b6ee4b2bbf084657f9a83ac34))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.1 ([#3280](https://github.com/homarr-labs/homarr/issues/3280)) ([9895c1b](https://github.com/homarr-labs/homarr/commit/9895c1b3ff028a7ea7273ab2625620c9132f5213))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.2 ([#3315](https://github.com/homarr-labs/homarr/issues/3315)) ([9ac9042](https://github.com/homarr-labs/homarr/commit/9ac90425647bf1f10d3f299f043c5d40067347bc))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.2.4 ([#3336](https://github.com/homarr-labs/homarr/issues/3336)) ([3ad72e6](https://github.com/homarr-labs/homarr/commit/3ad72e63b1adecd18c53897a6023f09d6f5d011c))
|
||||
* **deps:** update dependency node-cron to ^4.1.0 ([#3286](https://github.com/homarr-labs/homarr/issues/3286)) ([1f40868](https://github.com/homarr-labs/homarr/commit/1f408685e69397fa9e3a69bd4331a8240da3713b))
|
||||
* **deps:** update dependency sass to ^1.89.1 ([#3285](https://github.com/homarr-labs/homarr/issues/3285)) ([b608ffe](https://github.com/homarr-labs/homarr/commit/b608ffe4900a5dd97ceb2669464c477404d090e7))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.24.0 ([#3325](https://github.com/homarr-labs/homarr/issues/3325)) ([cf7336b](https://github.com/homarr-labs/homarr/commit/cf7336ba87c617578ab52e2ece94fd84df3fc623))
|
||||
* **deps:** update dependency typescript-eslint to ^8.33.1 ([#3302](https://github.com/homarr-labs/homarr/issues/3302)) ([124e8de](https://github.com/homarr-labs/homarr/commit/124e8de6c584b0efca91d92079f8b3c400bccab2))
|
||||
* **deps:** update dependency video.js to ^8.23.3 ([#3320](https://github.com/homarr-labs/homarr/issues/3320)) ([81509fd](https://github.com/homarr-labs/homarr/commit/81509fd96dfe0561964eb58d3d21c02400db2e8c))
|
||||
* **deps:** update dependency zod to ^3.25.43 ([#3290](https://github.com/homarr-labs/homarr/issues/3290)) ([161fc86](https://github.com/homarr-labs/homarr/commit/161fc867703c4cf634eaaeec1b73f5e3cbfa7fec))
|
||||
* **deps:** update dependency zod to ^3.25.45 ([#3291](https://github.com/homarr-labs/homarr/issues/3291)) ([3337cfa](https://github.com/homarr-labs/homarr/commit/3337cfa3c7e34debcccdbb95ee0bc39f2af98a91))
|
||||
* **deps:** update dependency zod to ^3.25.46 ([#3292](https://github.com/homarr-labs/homarr/issues/3292)) ([1b1671e](https://github.com/homarr-labs/homarr/commit/1b1671e0e6d808469ba7f8185c4590cf2f2f6f44))
|
||||
* **deps:** update dependency zod to ^3.25.47 ([#3296](https://github.com/homarr-labs/homarr/issues/3296)) ([94f176d](https://github.com/homarr-labs/homarr/commit/94f176d544272e0378e65255aba58b05d56019b2))
|
||||
* **deps:** update dependency zod to ^3.25.48 ([#3297](https://github.com/homarr-labs/homarr/issues/3297)) ([ccd8a85](https://github.com/homarr-labs/homarr/commit/ccd8a85e0fcd2cf40dbfb6058787d24a02680b93))
|
||||
* **deps:** update dependency zod to ^3.25.49 ([#3304](https://github.com/homarr-labs/homarr/issues/3304)) ([0362487](https://github.com/homarr-labs/homarr/commit/0362487156457ae825cac501e7d53db58c31ab0b))
|
||||
* **deps:** update dependency zod to ^3.25.50 ([#3312](https://github.com/homarr-labs/homarr/issues/3312)) ([a0a0449](https://github.com/homarr-labs/homarr/commit/a0a0449c704ff57725ab5e0b459eb209fc929446))
|
||||
* **deps:** update dependency zod to ^3.25.51 ([#3316](https://github.com/homarr-labs/homarr/issues/3316)) ([a542ee0](https://github.com/homarr-labs/homarr/commit/a542ee079871c7609ed97c76aa2017305adbfdc9))
|
||||
* **deps:** update dependency zod to ^3.25.55 ([#3332](https://github.com/homarr-labs/homarr/issues/3332)) ([ed3f848](https://github.com/homarr-labs/homarr/commit/ed3f848e8afb57cc1b9249551f0260eb52bbd4a6))
|
||||
* **deps:** update tanstack-query monorepo to ^5.79.1 ([#3300](https://github.com/homarr-labs/homarr/issues/3300)) ([a3deed3](https://github.com/homarr-labs/homarr/commit/a3deed3b090aed0df0f45a9bd04c006640691e05))
|
||||
* **deps:** update tanstack-query monorepo to ^5.79.2 ([#3301](https://github.com/homarr-labs/homarr/issues/3301)) ([a0585e3](https://github.com/homarr-labs/homarr/commit/a0585e3dd7d9cfbccf8ff5535ae2c753a8b92764))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.0 ([#3307](https://github.com/homarr-labs/homarr/issues/3307)) ([df2a9a5](https://github.com/homarr-labs/homarr/commit/df2a9a5329e06847904af56a0d058c0da59f6380))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.2 ([#3308](https://github.com/homarr-labs/homarr/issues/3308)) ([cae0cad](https://github.com/homarr-labs/homarr/commit/cae0cad92f68f716887e077194d4e30402466d57))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.3 ([#3317](https://github.com/homarr-labs/homarr/issues/3317)) ([c051a42](https://github.com/homarr-labs/homarr/commit/c051a42791de4eae4baf20360efe5b0c8925c22b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.5 ([#3321](https://github.com/homarr-labs/homarr/issues/3321)) ([cb77a7d](https://github.com/homarr-labs/homarr/commit/cb77a7dc6d60560250765d129aac9bd236693e2b))
|
||||
* **deps:** update tanstack-query monorepo to ^5.80.6 ([#3326](https://github.com/homarr-labs/homarr/issues/3326)) ([61553cf](https://github.com/homarr-labs/homarr/commit/61553cfc8e48a1179a148007fb970b1f4e6d509c))
|
||||
* **deps:** update testcontainers-node monorepo to v11 (major) ([#3250](https://github.com/homarr-labs/homarr/issues/3250)) ([0d1e3a6](https://github.com/homarr-labs/homarr/commit/0d1e3a625a04abd5f667640ab40e01d28e017488))
|
||||
* **deps:** update tiptap monorepo to v2.13.0 ([#3323](https://github.com/homarr-labs/homarr/issues/3323)) ([dac8234](https://github.com/homarr-labs/homarr/commit/dac82347e86dd7faacb24adbfc61ace9c917b8cd))
|
||||
* **deps:** update tiptap monorepo to v2.14.0 ([#3334](https://github.com/homarr-labs/homarr/issues/3334)) ([6274a66](https://github.com/homarr-labs/homarr/commit/6274a66048163513e3faaf7011d7d6aa6ad9f161))
|
||||
* **deps:** update trpc monorepo to ^11.2.0 ([#3293](https://github.com/homarr-labs/homarr/issues/3293)) ([f0ba693](https://github.com/homarr-labs/homarr/commit/f0ba693077a0c7f7897c81f02ad66b77fce52f1c))
|
||||
* **deps:** update trpc monorepo to ^11.3.0 ([#3322](https://github.com/homarr-labs/homarr/issues/3322)) ([74aa561](https://github.com/homarr-labs/homarr/commit/74aa561f470f0f76421f356998caa91dc8bb65a1))
|
||||
* **deps:** update trpc monorepo to ^11.3.1 ([#3329](https://github.com/homarr-labs/homarr/issues/3329)) ([8484daa](https://github.com/homarr-labs/homarr/commit/8484daaf7516ad72dd9e7de3117b5e0d50415dbb))
|
||||
* **nextcloud:** correct calendar event date mapping and support showU… ([#3330](https://github.com/homarr-labs/homarr/issues/3330)) ([ffd2f80](https://github.com/homarr-labs/homarr/commit/ffd2f802bb777f13eac1ee45e1b5f0ca2b596672))
|
||||
|
||||
## [1.22.0](https://github.com/homarr-labs/homarr/compare/v1.21.0...v1.22.0) (2025-05-30)
|
||||
|
||||
### Features
|
||||
|
||||
* allow custom protocols for app href ([#2575](https://github.com/homarr-labs/homarr/issues/2575)) ([09af0a7](https://github.com/homarr-labs/homarr/commit/09af0a76f64c2fec512ba30726827208d5c3ad95))
|
||||
* **cluster-health:** add visibility options ([#3210](https://github.com/homarr-labs/homarr/issues/3210)) ([939df8f](https://github.com/homarr-labs/homarr/commit/939df8f6d11bb36f71854425e4fdc4ca0724da31))
|
||||
* **downloads:** add option to limit amount of items ([#3205](https://github.com/homarr-labs/homarr/issues/3205)) ([2dc871e](https://github.com/homarr-labs/homarr/commit/2dc871e531b58aee25db26ab7c5e94626517db36))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **boards:** app select modal images rendering issue ([#3218](https://github.com/homarr-labs/homarr/issues/3218)) ([85e98d2](https://github.com/homarr-labs/homarr/commit/85e98d2b603abe18b37fa74bf50836422b72c9e1))
|
||||
* **calendar:** load events for all visible days of calendar month ([#3216](https://github.com/homarr-labs/homarr/issues/3216)) ([437ecd1](https://github.com/homarr-labs/homarr/commit/437ecd10239c70e5a8c4d34119076ab155a2159e))
|
||||
* **calendar:** remove scrollbar for 2x2 ([#3227](https://github.com/homarr-labs/homarr/issues/3227)) ([1d27da2](https://github.com/homarr-labs/homarr/commit/1d27da22303b2133bb1a7c59102b7c292bc34833))
|
||||
* **cluster-health:** storage items multiply ([#3206](https://github.com/homarr-labs/homarr/issues/3206)) ([1114934](https://github.com/homarr-labs/homarr/commit/11149348fcbe9002bba33763e0b03aed50d20f71))
|
||||
* csp prevents fonts to load ([#3225](https://github.com/homarr-labs/homarr/issues/3225)) ([05e159f](https://github.com/homarr-labs/homarr/commit/05e159f5662f04dd298eb13816fe1768d7ef9312))
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.4.2 ([#3258](https://github.com/homarr-labs/homarr/issues/3258)) ([4a184d2](https://github.com/homarr-labs/homarr/commit/4a184d23f25a8a06b36cd8c6d9b2a65118399d9f))
|
||||
* **deps:** update dependency @kubernetes/client-node to ^1.3.0 ([#3261](https://github.com/homarr-labs/homarr/issues/3261)) ([635c9a2](https://github.com/homarr-labs/homarr/commit/635c9a2687cf2b9a09a3093eb638ef772eddfb03))
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.6 ([#3244](https://github.com/homarr-labs/homarr/issues/3244)) ([8966e2a](https://github.com/homarr-labs/homarr/commit/8966e2ad8a2025dfc91934ccf825bd76c08dc333))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.0 ([#3256](https://github.com/homarr-labs/homarr/issues/3256)) ([09b49a2](https://github.com/homarr-labs/homarr/commit/09b49a242dd5d3d34bab758f190ab948b6fc7566))
|
||||
* **deps:** update dependency flag-icons to ^7.4.0 ([#3265](https://github.com/homarr-labs/homarr/issues/3265)) ([a0eda74](https://github.com/homarr-labs/homarr/commit/a0eda74fa472446c5b93c1b65829e65670620ab6))
|
||||
* **deps:** update dependency flag-icons to ^7.5.0 ([#3268](https://github.com/homarr-labs/homarr/issues/3268)) ([2320d3c](https://github.com/homarr-labs/homarr/commit/2320d3c5674413e18e9bb42fd004ada016c5e605))
|
||||
* **deps:** update dependency jotai to ^2.12.5 ([#3243](https://github.com/homarr-labs/homarr/issues/3243)) ([9c32385](https://github.com/homarr-labs/homarr/commit/9c323850bc49df5f6fc3437678faab0c7eab2e53))
|
||||
* **deps:** update dependency ldapts to v8.0.1 ([#3264](https://github.com/homarr-labs/homarr/issues/3264)) ([bac4f64](https://github.com/homarr-labs/homarr/commit/bac4f642574caf5f170d20353c036396698e5963))
|
||||
* **deps:** update dependency node-cron to v4 ([#3075](https://github.com/homarr-labs/homarr/issues/3075)) ([109f9e7](https://github.com/homarr-labs/homarr/commit/109f9e79793b36b46ab1987a56f7c206e21d32e4))
|
||||
* **deps:** update dependency octokit to ^5.0.3 ([#3249](https://github.com/homarr-labs/homarr/issues/3249)) ([3253b2f](https://github.com/homarr-labs/homarr/commit/3253b2f138a9e911b50c952e37fb4afb1f71446d))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.3.1 ([#3046](https://github.com/homarr-labs/homarr/issues/3046)) ([640bf7b](https://github.com/homarr-labs/homarr/commit/640bf7b939b201d9905f5119af0dc0da6120b98c))
|
||||
* **deps:** update dependency typescript-eslint to ^8.33.0 ([#3247](https://github.com/homarr-labs/homarr/issues/3247)) ([8b553b8](https://github.com/homarr-labs/homarr/commit/8b553b80e61ab608d01d890d26a8ea5bae3f38b8))
|
||||
* **deps:** update dependency zod to ^3.25.24 ([#3195](https://github.com/homarr-labs/homarr/issues/3195)) ([0b224e8](https://github.com/homarr-labs/homarr/commit/0b224e8835d4fc5a292a624c8af8d874216b703a))
|
||||
* **deps:** update dependency zod to ^3.25.28 ([#3201](https://github.com/homarr-labs/homarr/issues/3201)) ([e0a9e4f](https://github.com/homarr-labs/homarr/commit/e0a9e4f97c9dddb95fbc0b2a65acfd802dac9c19))
|
||||
* **deps:** update dependency zod to ^3.25.29 ([#3237](https://github.com/homarr-labs/homarr/issues/3237)) ([81ca6ef](https://github.com/homarr-labs/homarr/commit/81ca6ef7b2767fb0bc285ffa53998e2c586225ec))
|
||||
* **deps:** update dependency zod to ^3.25.30 ([#3239](https://github.com/homarr-labs/homarr/issues/3239)) ([3062948](https://github.com/homarr-labs/homarr/commit/30629489396668125dec7d93fcb0e733099c35fe))
|
||||
* **deps:** update dependency zod to ^3.25.31 ([#3254](https://github.com/homarr-labs/homarr/issues/3254)) ([e2e4656](https://github.com/homarr-labs/homarr/commit/e2e46560010a94603852ea90a0fc63fabd473198))
|
||||
* **deps:** update dependency zod to ^3.25.32 ([#3255](https://github.com/homarr-labs/homarr/issues/3255)) ([9c3a46c](https://github.com/homarr-labs/homarr/commit/9c3a46c071e169459ec7b8c9ba1647b4b354cbea))
|
||||
* **deps:** update dependency zod to ^3.25.34 ([#3259](https://github.com/homarr-labs/homarr/issues/3259)) ([c1324ea](https://github.com/homarr-labs/homarr/commit/c1324eab76795dabc0a41315191f9ab6a73ba501))
|
||||
* **deps:** update dependency zod to ^3.25.36 ([#3262](https://github.com/homarr-labs/homarr/issues/3262)) ([48ce2af](https://github.com/homarr-labs/homarr/commit/48ce2af4c182985a7721b65535bcf1b41f1cdcba))
|
||||
* **deps:** update dependency zod to ^3.25.39 ([#3270](https://github.com/homarr-labs/homarr/issues/3270)) ([862d07d](https://github.com/homarr-labs/homarr/commit/862d07db68e7624342cb81241ef9924e6407fda8))
|
||||
* **deps:** update dependency zod to ^3.25.41 ([#3272](https://github.com/homarr-labs/homarr/issues/3272)) ([5bf20aa](https://github.com/homarr-labs/homarr/commit/5bf20aa3538503de1d4f4d04f9de3e3b92442d75))
|
||||
* **deps:** update dependency zod to ^3.25.42 ([#3274](https://github.com/homarr-labs/homarr/issues/3274)) ([608280e](https://github.com/homarr-labs/homarr/commit/608280e670432098ce466cfb8fa2b315bf35ead2))
|
||||
* **deps:** update mantine monorepo to ^8.0.2 ([#3245](https://github.com/homarr-labs/homarr/issues/3245)) ([816544a](https://github.com/homarr-labs/homarr/commit/816544a996656ccf7cbacf76aff83d013ba21ad1))
|
||||
* **deps:** update nextjs monorepo to v15.3.3 ([#3269](https://github.com/homarr-labs/homarr/issues/3269)) ([d72a804](https://github.com/homarr-labs/homarr/commit/d72a804ad9feb24a2b067712128a6566930990a6))
|
||||
* **deps:** update tanstack-query monorepo to ^5.77.0 ([#3204](https://github.com/homarr-labs/homarr/issues/3204)) ([ec42f26](https://github.com/homarr-labs/homarr/commit/ec42f265a04f0f96105d6b1318becd5cc94aa8fb))
|
||||
* **deps:** update tanstack-query monorepo to ^5.77.1 ([#3229](https://github.com/homarr-labs/homarr/issues/3229)) ([5ea7a20](https://github.com/homarr-labs/homarr/commit/5ea7a206772924faf1f842c80033954c853b3a02))
|
||||
* **deps:** update tanstack-query monorepo to ^5.77.2 ([#3232](https://github.com/homarr-labs/homarr/issues/3232)) ([7118690](https://github.com/homarr-labs/homarr/commit/71186908b96f717c6f8c528b0f983e1792a7a885))
|
||||
* **deps:** update tanstack-query monorepo to ^5.79.0 ([#3263](https://github.com/homarr-labs/homarr/issues/3263)) ([1f9983f](https://github.com/homarr-labs/homarr/commit/1f9983f16db8677b8c244b63306ea4afd2195d94))
|
||||
* **deps:** update trpc monorepo to ^11.1.3 ([#3240](https://github.com/homarr-labs/homarr/issues/3240)) ([78f98ed](https://github.com/homarr-labs/homarr/commit/78f98edc4145e27e2daa269121173905c642c139))
|
||||
* **deps:** update trpc monorepo to ^11.1.4 ([#3260](https://github.com/homarr-labs/homarr/issues/3260)) ([bfd7b70](https://github.com/homarr-labs/homarr/commit/bfd7b706182340eaa1db70a3c6cdcc935aee8df4))
|
||||
* **deps:** update turbo monorepo to ^2.5.4 ([#3279](https://github.com/homarr-labs/homarr/issues/3279)) ([c8202ab](https://github.com/homarr-labs/homarr/commit/c8202ab3c676bf796c6e7aff2ebfef224a0124c8))
|
||||
* **downloads:** upspeed font size wrong ([#3214](https://github.com/homarr-labs/homarr/issues/3214)) ([c67916f](https://github.com/homarr-labs/homarr/commit/c67916f72eded64133fd458184a599f32d8fc63c))
|
||||
* **media-request-stats:** users squeezed together ([#3213](https://github.com/homarr-labs/homarr/issues/3213)) ([25bd387](https://github.com/homarr-labs/homarr/commit/25bd3879db9260bbd24a1338cc5f26652ed78d72))
|
||||
* navigating back results in navigation back to first page opened ([#3224](https://github.com/homarr-labs/homarr/issues/3224)) ([cbbf0c8](https://github.com/homarr-labs/homarr/commit/cbbf0c8dcf685e31e70bb62f04b1065dab90e18d))
|
||||
* reduce logging ([#3200](https://github.com/homarr-labs/homarr/issues/3200)) ([be8b332](https://github.com/homarr-labs/homarr/commit/be8b332f942beb3af181ee6f39558882ed640a0e))
|
||||
* **search:** removing server search engine causes loading error ([#3252](https://github.com/homarr-labs/homarr/issues/3252)) ([10f720d](https://github.com/homarr-labs/homarr/commit/10f720d74c6cc70cbf217e8daf8cc7878ac6d712))
|
||||
* **visual:** check for creator board permission is wrong ([#3212](https://github.com/homarr-labs/homarr/issues/3212)) ([f7e5e82](https://github.com/homarr-labs/homarr/commit/f7e5e823d5dee36ea11f243a0da7d84a1f363419))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* cache middleware onboarding and language checks ([#3197](https://github.com/homarr-labs/homarr/issues/3197)) ([2afa9b6](https://github.com/homarr-labs/homarr/commit/2afa9b67274edd79b52598dc0d22437048a67e2c))
|
||||
|
||||
## [1.21.0](https://github.com/homarr-labs/homarr/compare/v1.20.0...v1.21.0) (2025-05-23)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -17,31 +17,16 @@ const withNextIntl = createNextIntlPlugin({
|
||||
requestConfig: "../../packages/translation/src/request.ts",
|
||||
});
|
||||
|
||||
interface WebpackConfig {
|
||||
module: {
|
||||
rules: {
|
||||
test: RegExp;
|
||||
loader: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
/** We already do linting and typechecking as separate tasks in CI */
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
webpack: (config: WebpackConfig, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.module.rules.push({
|
||||
test: /\.node$/,
|
||||
loader: "node-loader",
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
/**
|
||||
* dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612
|
||||
*/
|
||||
serverExternalPackages: ["dockerode"],
|
||||
experimental: {
|
||||
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
||||
},
|
||||
@@ -62,12 +47,13 @@ const nextConfig: NextConfig = {
|
||||
script-src * 'unsafe-inline' 'unsafe-eval';
|
||||
base-uri 'self';
|
||||
connect-src *;
|
||||
style-src 'self' 'unsafe-inline';
|
||||
style-src * 'unsafe-inline';
|
||||
frame-ancestors *;
|
||||
frame-src *;
|
||||
form-action 'self';
|
||||
img-src * data:;
|
||||
font-src * data:;
|
||||
media-src * data:;
|
||||
`
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim(),
|
||||
|
||||
+23
-23
@@ -48,21 +48,21 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.0.2",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/dropzone": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/modals": "^8.0.2",
|
||||
"@mantine/tiptap": "^8.0.2",
|
||||
"@mantine/colors-generator": "^8.1.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/dropzone": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@mantine/modals": "^8.1.0",
|
||||
"@mantine/tiptap": "^8.1.0",
|
||||
"@million/lint": "1.0.14",
|
||||
"@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",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@tanstack/react-query-devtools": "^5.80.6",
|
||||
"@tanstack/react-query-next-experimental": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/next": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -70,35 +70,35 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.5.0",
|
||||
"flag-icons": "^7.3.2",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^11.0.2",
|
||||
"jotai": "^2.12.5",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.89.0",
|
||||
"sass": "^1.89.2",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.22.0",
|
||||
"swagger-ui-react": "^5.24.1",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/node": "^22.15.31",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.6",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import Link from "next/link";
|
||||
import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core";
|
||||
import { Card, Group, SimpleGrid, Space, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { isProviderEnabled } from "@homarr/auth/server";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { catchTrpcNotFound } from "~/errors/trpc-catch-error";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { HeroBanner } from "./_components/hero-banner";
|
||||
|
||||
@@ -30,6 +33,8 @@ export async function generateMetadata() {
|
||||
export default async function ManagementPage() {
|
||||
const statistics = await api.home.getStats();
|
||||
const session = await auth();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
const suggestions = isAdmin ? await api.integration.suggestedIntegrations().catch(catchTrpcNotFound) : [];
|
||||
const t = await getScopedI18n("management.page.home");
|
||||
|
||||
const links: LinkProps[] = [
|
||||
@@ -102,6 +107,37 @@ export default async function ManagementPage() {
|
||||
),
|
||||
)}
|
||||
</SimpleGrid>
|
||||
{suggestions.length >= 1 && (
|
||||
<>
|
||||
<Space h="md" />
|
||||
<Stack gap="sm">
|
||||
<Stack gap={0}>
|
||||
<Title order={3}>Suggested integrations</Title>
|
||||
<Text c="dimmed">Based on your Docker containers</Text>
|
||||
</Stack>
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{suggestions.slice(0, 6).map((suggestion) => (
|
||||
<Card key={suggestion.kind} component={Link} href="#" radius="lg">
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<IntegrationAvatar kind={suggestion.kind} size="sm" />
|
||||
<Stack gap={0}>
|
||||
<Text size="md" fw={500}>
|
||||
{getIntegrationName(suggestion.kind)}
|
||||
</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
Show active media streams
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<IconArrowRight />
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -44,12 +44,12 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/node": "^22.15.31",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.21.0",
|
||||
"version": "1.23.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -38,22 +38,22 @@
|
||||
"@semantic-release/github": "^11.0.3",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.5.3",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"@vitest/ui": "^3.1.4",
|
||||
"@turbo/gen": "^2.5.4",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@vitest/coverage-v8": "^3.2.3",
|
||||
"@vitest/ui": "^3.2.3",
|
||||
"conventional-changelog-conventionalcommits": "^9.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"semantic-release": "^24.2.5",
|
||||
"testcontainers": "^10.28.0",
|
||||
"turbo": "^2.5.3",
|
||||
"testcontainers": "^11.0.3",
|
||||
"turbo": "^2.5.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.4"
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"packageManager": "pnpm@10.12.1",
|
||||
"engines": {
|
||||
"node": ">=22.16.0"
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -40,25 +40,25 @@
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.77.2",
|
||||
"@trpc/client": "^11.1.3",
|
||||
"@trpc/react-query": "^11.1.3",
|
||||
"@trpc/server": "^11.1.3",
|
||||
"@trpc/tanstack-react-query": "^11.1.3",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.80.6",
|
||||
"@trpc/client": "^11.3.1",
|
||||
"@trpc/react-query": "^11.3.1",
|
||||
"@trpc/server": "^11.3.1",
|
||||
"@trpc/tanstack-react-query": "^11.3.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.3.1",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -14,15 +14,19 @@ import {
|
||||
integrationUserPermissions,
|
||||
searchEngines,
|
||||
} from "@homarr/db/schema";
|
||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||
import {
|
||||
getIconUrl,
|
||||
getIntegrationKindsByCategory,
|
||||
getIntegrationName,
|
||||
getPermissionsWithParents,
|
||||
integrationDefs,
|
||||
integrationKinds,
|
||||
integrationSecretKindObject,
|
||||
} from "@homarr/definitions";
|
||||
import type { ContainerInfo, Port } from "@homarr/docker";
|
||||
import { DockerSingleton } from "@homarr/docker";
|
||||
import { env } from "@homarr/docker/env";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { logger } from "@homarr/log";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
@@ -527,8 +531,169 @@ export const integrationRouter = createTRPCRouter({
|
||||
const integrationInstance = await createIntegrationAsync(ctx.integration);
|
||||
return await integrationInstance.searchAsync(encodeURI(input.query));
|
||||
}),
|
||||
suggestedIntegrations: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
||||
if (!env.ENABLE_DOCKER)
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Docker is not enabled",
|
||||
});
|
||||
|
||||
const dockerInstances = DockerSingleton.getInstances();
|
||||
if (dockerInstances.length === 0) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "No Docker instances found",
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove those that were already added
|
||||
const containers = await Promise.all(
|
||||
dockerInstances.map(({ instance, host, isSocket }) =>
|
||||
instance.listContainers().then((containers) =>
|
||||
containers.map((containerInfo) => ({
|
||||
info: containerInfo,
|
||||
host,
|
||||
isSocket,
|
||||
})),
|
||||
),
|
||||
),
|
||||
).then((results) => results.flat());
|
||||
|
||||
// Default docker hostname consist out of first 12 characters of the container id
|
||||
const homarrContainer = containers.find((container) => container.info.Id.slice(0, 12) === process.env.HOSTNAME);
|
||||
|
||||
const suggestions = containers
|
||||
.map((container) => getSuggestionFromContainer(container, homarrContainer))
|
||||
.filter((suggestion) => suggestion !== null);
|
||||
|
||||
const groupedSuggestions = suggestions.reduce(
|
||||
(acc, suggestion) => {
|
||||
acc[suggestion.kind] ??= [];
|
||||
// is defined above
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
acc[suggestion.kind]!.push(suggestion);
|
||||
return acc;
|
||||
},
|
||||
{} as Partial<Record<IntegrationKind, IntegrationSuggestion[]>>,
|
||||
);
|
||||
|
||||
return Object.entries(groupedSuggestions)
|
||||
.map(([kind, suggestions]) => ({
|
||||
kind: kind as IntegrationKind,
|
||||
suggestions,
|
||||
}))
|
||||
.sort((itemA, itemB) => {
|
||||
if (itemA.suggestions.length !== itemB.suggestions.length)
|
||||
return itemB.suggestions.length - itemA.suggestions.length;
|
||||
|
||||
const createdAtA = Math.max(...itemA.suggestions.map((suggestion) => suggestion.createdAt?.getTime() ?? 0));
|
||||
const createdAtB = Math.max(...itemB.suggestions.map((suggestion) => suggestion.createdAt?.getTime() ?? 0));
|
||||
return createdAtB - createdAtA;
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
interface IntegrationSuggestion {
|
||||
containerId: string;
|
||||
name: string;
|
||||
internalUrl: string | null;
|
||||
externalUrl: string | null;
|
||||
kind: IntegrationKind;
|
||||
createdAt?: Date;
|
||||
}
|
||||
|
||||
interface ContainerInfoWithHost {
|
||||
info: ContainerInfo;
|
||||
host: string;
|
||||
isSocket: boolean | undefined;
|
||||
}
|
||||
|
||||
const getSuggestionFromContainer = (
|
||||
container: ContainerInfoWithHost,
|
||||
homarrContainer: ContainerInfoWithHost | undefined,
|
||||
): IntegrationSuggestion | null => {
|
||||
const integrationKind = getSuggestionKind(container.info);
|
||||
|
||||
if (!integrationKind) return null;
|
||||
|
||||
return {
|
||||
containerId: container.info.Id,
|
||||
name: getIntegrationName(integrationKind),
|
||||
kind: integrationKind,
|
||||
internalUrl: getInternalUrl(container, homarrContainer),
|
||||
externalUrl: getExternalUrl(container),
|
||||
createdAt: container.info.Created ? new Date(container.info.Created * 1000) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const getSuggestionKind = (container: ContainerInfo): IntegrationKind | null => {
|
||||
const [image, _tag] = container.Image.split(":");
|
||||
if (!image) return null;
|
||||
return (
|
||||
objectEntries(integrationDefs).find(
|
||||
([, integration]) =>
|
||||
"containerImages" in integration &&
|
||||
integration.containerImages.some((containerImage) => containerImage.toLowerCase() === image.toLowerCase()),
|
||||
)?.[0] ?? null
|
||||
);
|
||||
};
|
||||
|
||||
const calculatePortScore = (port: Port): number => {
|
||||
let score = 0;
|
||||
|
||||
if (port.PublicPort) score += 10; // Public ports are more likely to be used
|
||||
if (port.PrivatePort % 1000 === 80) score += 5; // Default http ports are most likely suffixed with 80 like 8080 or 80
|
||||
if (port.PrivatePort % 1000 === 443) score += 10; // Default https ports are most likely suffixed with 443 like 8443 or 443
|
||||
return score;
|
||||
};
|
||||
|
||||
const getPort = (container: ContainerInfoWithHost): Port | null => {
|
||||
const ports = container.info.Ports.filter((port) => port.Type === "tcp");
|
||||
return (
|
||||
ports
|
||||
.map((port) => ({ port, score: calculatePortScore(port) }))
|
||||
.sort((portA, portB) => portB.score - portA.score)
|
||||
.at(0)?.port ?? null
|
||||
);
|
||||
};
|
||||
|
||||
const getInternalUrl = (
|
||||
container: ContainerInfoWithHost,
|
||||
homarrContainer: ContainerInfoWithHost | undefined,
|
||||
): string | null => {
|
||||
const port = getPort(container);
|
||||
if (!port) return null;
|
||||
const protocol = port.PrivatePort % 1000 === 443 ? "https" : "http";
|
||||
|
||||
if (homarrContainer) {
|
||||
const matchingNetwork = Object.entries(homarrContainer.info.NetworkSettings.Networks).find(
|
||||
([_key, homarrNetwork]) =>
|
||||
Object.entries(container.info.NetworkSettings.Networks).some(
|
||||
([_name, network]) => homarrNetwork.NetworkID === network.NetworkID,
|
||||
),
|
||||
)?.[1];
|
||||
if (matchingNetwork) return `${protocol}://${matchingNetwork.IPAddress}:${port.PrivatePort}`;
|
||||
}
|
||||
|
||||
// TODO: Let user specify the internal ip-address for integrations --> use as default before falling back to host.docker.internal
|
||||
if (container.host === homarrContainer?.host && container.isSocket === homarrContainer.isSocket) {
|
||||
return `${protocol}://host.docker.internal:${port.PublicPort}`;
|
||||
}
|
||||
|
||||
// TODO: Change this
|
||||
return `${protocol}://localhost:${port.PublicPort}`;
|
||||
};
|
||||
|
||||
const getExternalUrl = (container: ContainerInfoWithHost): string | null => {
|
||||
const port = getPort(container);
|
||||
if (!port) return null;
|
||||
const protocol = port.PrivatePort % 1000 === 443 ? "https" : "http";
|
||||
|
||||
// TODO: Use some labels like "traefik.host" to determine the external url
|
||||
// For now we just use localhost as external url
|
||||
return `${protocol}://localhost:${port.PublicPort}`;
|
||||
};
|
||||
|
||||
interface UpdateSecretInput {
|
||||
integrationId: string;
|
||||
value: string;
|
||||
|
||||
@@ -2,9 +2,10 @@ import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { asc, createId, eq, like } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { searchEngines, users } from "@homarr/db/schema";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { logger } from "@homarr/log";
|
||||
import { byIdSchema, paginatedSchema, searchSchema } from "@homarr/validation/common";
|
||||
import { searchEngineEditSchema, searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||
import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/validation/widgets/media-request";
|
||||
@@ -96,23 +97,35 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const serverDefaultId = await getServerSettingByKeyAsync(ctx.db, "search").then(
|
||||
(setting) => setting.defaultSearchEngineId,
|
||||
);
|
||||
const searchSettings = await getServerSettingByKeyAsync(ctx.db, "search");
|
||||
|
||||
if (serverDefaultId) {
|
||||
return await ctx.db.query.searchEngines.findFirst({
|
||||
where: eq(searchEngines.id, serverDefaultId),
|
||||
with: {
|
||||
integration: {
|
||||
columns: {
|
||||
kind: true,
|
||||
url: true,
|
||||
id: true,
|
||||
},
|
||||
if (!searchSettings.defaultSearchEngineId) return null;
|
||||
|
||||
const serverDefault = await ctx.db.query.searchEngines.findFirst({
|
||||
where: eq(searchEngines.id, searchSettings.defaultSearchEngineId),
|
||||
with: {
|
||||
integration: {
|
||||
columns: {
|
||||
kind: true,
|
||||
url: true,
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (serverDefault) return serverDefault;
|
||||
|
||||
// Remove the default search engine ID from settings if it does not longer exist
|
||||
try {
|
||||
await updateServerSettingByKeyAsync(ctx.db, "search", {
|
||||
...searchSettings,
|
||||
defaultSearchEngineId: null,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
new Error("Failed to update search settings after default search engine not found", { cause: error }),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -34,20 +34,20 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.0",
|
||||
"next": "15.3.2",
|
||||
"ldapts": "8.0.1",
|
||||
"next": "15.3.3",
|
||||
"next-auth": "5.0.0-beta.28",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookies": "0.9.0",
|
||||
"eslint": "^9.27.0",
|
||||
"@types/cookies": "0.9.1",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,18 +30,18 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"undici": "7.10.0",
|
||||
"zod": "^3.25.30",
|
||||
"zod": "^3.25.62",
|
||||
"zod-validation-error": "^3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"node-cron": "^4.0.7"
|
||||
"node-cron": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EVERY_5_SECONDS } from "@homarr/cron-jobs-core/expressions";
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { dnsHoleRequestHandler } from "@homarr/request-handler/dns-hole";
|
||||
import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler";
|
||||
|
||||
import { createCronJob } from "../../lib";
|
||||
|
||||
export const dnsHoleJob = createCronJob("dnsHole", EVERY_5_SECONDS).withCallback(
|
||||
export const dnsHoleJob = createCronJob("dnsHole", EVERY_MINUTE).withCallback(
|
||||
createRequestIntegrationJobHandler(dnsHoleRequestHandler.handler, {
|
||||
widgetKinds: ["dnsHoleSummary", "dnsHoleControls"],
|
||||
getInput: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
@@ -10,7 +10,7 @@ import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||
import type { WidgetComponentProps } from "../../../widgets";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const rssFeedsJob = createCronJob("rssFeeds", EVERY_5_MINUTES).withCallback(async () => {
|
||||
export const rssFeedsJob = createCronJob("rssFeeds", EVERY_10_MINUTES).withCallback(async () => {
|
||||
const rssItems = await db.query.items.findMany({
|
||||
where: eq(items.kind, "rssFeed"),
|
||||
});
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^10.28.0",
|
||||
"@testcontainers/mysql": "^11.0.3",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"mysql2": "3.14.1"
|
||||
},
|
||||
@@ -61,9 +61,9 @@
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import * as mysqlSchema from "../schema/mysql";
|
||||
|
||||
describe("Mysql Migration", () => {
|
||||
test("should add all tables and keys specified in migration files", async () => {
|
||||
const mysqlContainer = await new MySqlContainer().start();
|
||||
const mysqlContainer = await new MySqlContainer("mysql:latest").start();
|
||||
|
||||
const connection = mysql.createConnection({
|
||||
host: mysqlContainer.getHost(),
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.2.3",
|
||||
"zod": "^3.25.30"
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"tsx": "4.19.4",
|
||||
"eslint": "^9.28.0",
|
||||
"tsx": "4.20.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ interface integrationDefinition {
|
||||
iconUrl: string;
|
||||
secretKinds: AtLeastOneOf<IntegrationSecretKind[]>; // at least one secret kind set is required
|
||||
category: AtLeastOneOf<IntegrationCategory>;
|
||||
containerImages?: string[]; // Optional, used for container images to suggest in the UI
|
||||
}
|
||||
|
||||
export const integrationDefs = {
|
||||
@@ -24,30 +25,45 @@ export const integrationDefs = {
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
containerImages: ["linuxserver/sabnzbd", "lscr.io/linuxserver/sabnzbd", "ghcr.io/linuxserver/sabnzbd"],
|
||||
},
|
||||
nzbGet: {
|
||||
name: "NZBGet",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
containerImages: [
|
||||
"nzbgetcom/nzbget",
|
||||
"ghcr.io/nzbgetcom/nzbget",
|
||||
"linuxserver/nzbget",
|
||||
"lscr.io/linuxserver/nzbget",
|
||||
"ghcr.io/linuxserver/nzbget",
|
||||
],
|
||||
},
|
||||
deluge: {
|
||||
name: "Deluge",
|
||||
secretKinds: [["password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/deluge.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: ["linuxserver/deluge", "lscr.io/linuxserver/deluge", "ghcr.io/linuxserver/deluge"],
|
||||
},
|
||||
transmission: {
|
||||
name: "Transmission",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/transmission.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: [
|
||||
"linuxserver/transmission",
|
||||
"lscr.io/linuxserver/transmission",
|
||||
"ghcr.io/linuxserver/transmission",
|
||||
],
|
||||
},
|
||||
qBittorrent: {
|
||||
name: "qBittorrent",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
containerImages: ["linuxserver/qbittorrent", "lscr.io/linuxserver/qbittorrent", "ghcr.io/linuxserver/qbittorrent"],
|
||||
},
|
||||
aria2: {
|
||||
name: "Aria2",
|
||||
@@ -60,78 +76,144 @@ export const integrationDefs = {
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/sonarr",
|
||||
"ghcr.io/hotio/sonarr",
|
||||
"linuxserver/sonarr",
|
||||
"lscr.io/linuxserver/sonarr",
|
||||
"ghcr.io/linuxserver/sonarr",
|
||||
],
|
||||
},
|
||||
radarr: {
|
||||
name: "Radarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/radarr",
|
||||
"ghcr.io/hotio/radarr",
|
||||
"linuxserver/radarr",
|
||||
"lscr.io/linuxserver/radarr",
|
||||
"ghcr.io/linuxserver/radarr",
|
||||
],
|
||||
},
|
||||
lidarr: {
|
||||
name: "Lidarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/lidarr",
|
||||
"ghcr.io/hotio/lidarr",
|
||||
"linuxserver/lidarr",
|
||||
"lscr.io/linuxserver/lidarr",
|
||||
"ghcr.io/linuxserver/lidarr",
|
||||
],
|
||||
},
|
||||
readarr: {
|
||||
name: "Readarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/readarr.svg",
|
||||
category: ["calendar"],
|
||||
containerImages: [
|
||||
"hotio/readarr",
|
||||
"ghcr.io/hotio/readarr",
|
||||
"linuxserver/readarr",
|
||||
"lscr.io/linuxserver/readarr",
|
||||
"ghcr.io/linuxserver/readarr",
|
||||
],
|
||||
},
|
||||
prowlarr: {
|
||||
name: "Prowlarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg",
|
||||
category: ["indexerManager"],
|
||||
containerImages: [
|
||||
"hotio/prowlarr",
|
||||
"ghcr.io/hotio/prowlarr",
|
||||
"linuxserver/prowlarr",
|
||||
"lscr.io/linuxserver/prowlarr",
|
||||
"ghcr.io/linuxserver/prowlarr",
|
||||
],
|
||||
},
|
||||
jellyfin: {
|
||||
name: "Jellyfin",
|
||||
secretKinds: [["username", "password"], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: [
|
||||
"jellyfin/jellyfin",
|
||||
"ghcr.io/jellyfin/jellyfin",
|
||||
"linuxserver/jellyfin",
|
||||
"lscr.io/linuxserver/jellyfin",
|
||||
"ghcr.io/linuxserver/jellyfin",
|
||||
],
|
||||
},
|
||||
emby: {
|
||||
name: "Emby",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: [
|
||||
"emby/embyserver",
|
||||
"emby/embyserver_arm64v8",
|
||||
"emby/embyserver_arm32v7",
|
||||
"linuxserver/emby",
|
||||
"lscr.io/linuxserver/emby",
|
||||
"ghcr.io/linuxserver/emby",
|
||||
],
|
||||
},
|
||||
plex: {
|
||||
name: "Plex",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg",
|
||||
category: ["mediaService"],
|
||||
containerImages: ["plexinc/pms-docker", "linuxserver/plex", "lscr.io/linuxserver/plex", "ghcr.io/linuxserver/plex"],
|
||||
},
|
||||
jellyseerr: {
|
||||
name: "Jellyseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
containerImages: ["fallenbagel/jellyseerr", "ghcr.io/fallenbagel/jellyseerr"],
|
||||
},
|
||||
overseerr: {
|
||||
name: "Overseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
containerImages: [
|
||||
"sct/overseerr",
|
||||
"linuxserver/overseerr",
|
||||
"lscr.io/linuxserver/overseerr",
|
||||
"ghcr.io/linuxserver/overseerr",
|
||||
],
|
||||
},
|
||||
piHole: {
|
||||
name: "Pi-hole",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg",
|
||||
category: ["dnsHole"],
|
||||
containerImages: ["pihole/pihole"],
|
||||
},
|
||||
adGuardHome: {
|
||||
name: "AdGuard Home",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/adguard-home.svg",
|
||||
category: ["dnsHole"],
|
||||
containerImages: ["adguard/adguardhome"],
|
||||
},
|
||||
homeAssistant: {
|
||||
name: "Home Assistant",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
|
||||
category: ["smartHomeServer"],
|
||||
containerImages: [
|
||||
"homeassistant/home-assistant",
|
||||
"linuxserver/home-assistant",
|
||||
"lscr.io/linuxserver/home-assistant",
|
||||
"ghcr.io/linuxserver/home-assistant",
|
||||
],
|
||||
},
|
||||
openmediavault: {
|
||||
name: "OpenMediaVault",
|
||||
@@ -144,12 +226,14 @@ export const integrationDefs = {
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/dashdot.png",
|
||||
category: ["healthMonitoring"],
|
||||
containerImages: ["mauricenino/dashdot", "ghcr.io/mauricenino/dashdot"],
|
||||
},
|
||||
tdarr: {
|
||||
name: "Tdarr",
|
||||
secretKinds: [[], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png",
|
||||
category: ["mediaTranscoding"],
|
||||
containerImages: ["haveagitgat/tdarr", "ghcr.io/haveagitgat/tdarr"],
|
||||
},
|
||||
proxmox: {
|
||||
name: "Proxmox",
|
||||
@@ -168,6 +252,16 @@ export const integrationDefs = {
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png",
|
||||
category: ["networkController"],
|
||||
containerImages: [
|
||||
"jacobalberty/unifi",
|
||||
"ghcr.io/jacobalberty/unifi",
|
||||
"linuxserver/unifi-controller",
|
||||
"lscr.io/linuxserver/unifi-controller",
|
||||
"ghcr.io/linuxserver/unifi-controller",
|
||||
"linuxserver/unifi-network-application",
|
||||
"lscr.io/linuxserver/unifi-network-application",
|
||||
"ghcr.io/linuxserver/unifi-network-application",
|
||||
],
|
||||
},
|
||||
} as const satisfies Record<string, integrationDefinition>;
|
||||
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"dockerode": "^4.0.6"
|
||||
"dockerode": "^4.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.39",
|
||||
"eslint": "^9.27.0",
|
||||
"@types/dockerode": "^3.3.40",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { env } from "./env";
|
||||
export interface DockerInstance {
|
||||
host: string;
|
||||
instance: Docker;
|
||||
isSocket?: boolean;
|
||||
}
|
||||
|
||||
export class DockerSingleton {
|
||||
@@ -14,7 +15,7 @@ export class DockerSingleton {
|
||||
const hostVariable = env.DOCKER_HOSTNAMES;
|
||||
const portVariable = env.DOCKER_PORTS;
|
||||
if (hostVariable === undefined || portVariable === undefined) {
|
||||
return [{ host: "socket", instance: new Docker() }];
|
||||
return [{ host: "socket", instance: new Docker(), isSocket: true }];
|
||||
}
|
||||
const hostnames = hostVariable.split(",");
|
||||
const ports = portVariable.split(",");
|
||||
|
||||
Vendored
+3
-3
@@ -23,14 +23,14 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.6",
|
||||
"zod": "^3.25.30"
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,14 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^8.0.2",
|
||||
"zod": "^3.25.30"
|
||||
"@mantine/form": "^8.1.0",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,15 @@
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"react": "19.1.0",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
"tsdav": "^2.1.4",
|
||||
"undici": "7.10.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-unifi": "^2.5.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
import dayjs from "dayjs";
|
||||
import objectSupport from "dayjs/plugin/objectSupport";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import * as ical from "node-ical";
|
||||
import { DAVClient } from "tsdav";
|
||||
import type { Dispatcher, RequestInit as UndiciFetchRequestInit } from "undici";
|
||||
@@ -15,9 +12,6 @@ import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { CalendarEvent } from "../calendar-types";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(objectSupport);
|
||||
|
||||
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
||||
export class NextcloudIntegration extends Integration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
@@ -27,7 +21,7 @@ export class NextcloudIntegration extends Integration {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||
public async getCalendarEventsAsync(start: Date, end: Date, _showUnmonitored?: boolean): Promise<CalendarEvent[]> {
|
||||
const client = await this.createCalendarClientAsync();
|
||||
await client.login();
|
||||
|
||||
@@ -57,14 +51,7 @@ export class NextcloudIntegration extends Integration {
|
||||
|
||||
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
|
||||
|
||||
const date = dayjs.utc({
|
||||
days: veventObject.start.getDay(),
|
||||
month: veventObject.start.getMonth(),
|
||||
year: veventObject.start.getFullYear(),
|
||||
hours: veventObject.start.getHours(),
|
||||
minutes: veventObject.start.getMinutes(),
|
||||
seconds: veventObject.start.getSeconds(),
|
||||
});
|
||||
const date = veventObject.start;
|
||||
|
||||
const eventUrlWithoutHost = new URL(event.url).pathname;
|
||||
const dateInMillis = veventObject.start.valueOf();
|
||||
@@ -75,7 +62,7 @@ export class NextcloudIntegration extends Integration {
|
||||
|
||||
return {
|
||||
name: veventObject.summary,
|
||||
date: date.toDate(),
|
||||
date,
|
||||
subName: "",
|
||||
description: veventObject.description,
|
||||
links: [
|
||||
|
||||
@@ -66,7 +66,7 @@ export class OpenMediaVaultIntegration extends Integration {
|
||||
|
||||
return {
|
||||
version: systemResult.data.response.version,
|
||||
cpuModelName: systemResult.data.response.cpuModelName,
|
||||
cpuModelName: systemResult.data.response.cpuModelName ?? "Unknown CPU",
|
||||
cpuUtilization: systemResult.data.response.cpuUtilization,
|
||||
memUsed: systemResult.data.response.memUsed,
|
||||
memAvailable: systemResult.data.response.memAvailable,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
||||
export const systemInformationSchema = z.object({
|
||||
response: z.object({
|
||||
version: z.string(),
|
||||
cpuModelName: z.string(),
|
||||
cpuModelName: z.string().nullable(),
|
||||
cpuUtilization: z.number(),
|
||||
memUsed: z.string(),
|
||||
memAvailable: z.string(),
|
||||
|
||||
@@ -77,6 +77,7 @@ export class PiHoleIntegrationV6 extends Integration implements DnsHoleSummaryIn
|
||||
protected async testingAsync({ fetchAsync }: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const sessionId = await this.getSessionAsync(fetchAsync);
|
||||
await this.clearSessionAsync(sessionId, fetchAsync);
|
||||
await this.sessionStore.clearAsync();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ const mapResource = (resource: Proxmox.clusterResourcesResources): Resource | nu
|
||||
|
||||
const mapComputeResource = (resource: Proxmox.clusterResourcesResources): Omit<ComputeResourceBase<string>, "type"> => {
|
||||
return {
|
||||
id: resource.id,
|
||||
cpu: {
|
||||
utilization: resource.cpu ?? 0,
|
||||
cores: resource.maxcpu ?? 0,
|
||||
@@ -114,6 +115,7 @@ const mapVmResource = (resource: Proxmox.clusterResourcesResources): LxcResource
|
||||
|
||||
const mapStorageResource = (resource: Proxmox.clusterResourcesResources): StorageResource => {
|
||||
return {
|
||||
id: resource.id,
|
||||
type: "storage",
|
||||
name: resource.storage ?? "",
|
||||
node: resource.node ?? "",
|
||||
|
||||
@@ -7,6 +7,7 @@ interface ResourceBase<TType extends string> {
|
||||
}
|
||||
|
||||
export interface ComputeResourceBase<TType extends string> extends ResourceBase<TType> {
|
||||
id: string;
|
||||
cpu: {
|
||||
utilization: number; // previously cpu (0-1)
|
||||
cores: number; // previously cpuCores
|
||||
@@ -40,6 +41,7 @@ export interface QemuResource extends ComputeResourceBase<"qemu"> {
|
||||
}
|
||||
|
||||
export interface StorageResource extends ResourceBase<"storage"> {
|
||||
id: string;
|
||||
storagePlugin: string;
|
||||
used: number; // previously disk
|
||||
total: number; // previously maxDisk
|
||||
|
||||
@@ -64,26 +64,29 @@ describe("Home Assistant integration", () => {
|
||||
const prepareHomeAssistantContainerAsync = async () => {
|
||||
const homeAssistantContainer = createHomeAssistantContainer();
|
||||
const startedContainer = await homeAssistantContainer.start();
|
||||
|
||||
await startedContainer.exec(["unzip", "-o", "/tmp/config.zip", "-d", "/config"]);
|
||||
await startedContainer.restart();
|
||||
return startedContainer;
|
||||
};
|
||||
|
||||
const createHomeAssistantContainer = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/home-assistant-config.zip"),
|
||||
target: "/tmp/config.zip",
|
||||
},
|
||||
])
|
||||
.withPrivilegedMode()
|
||||
.withExposedPorts(8123)
|
||||
.withWaitStrategy(Wait.forHttp("/", 8123));
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/home-assistant-config.zip"),
|
||||
target: "/tmp/config.zip",
|
||||
},
|
||||
])
|
||||
.withPrivilegedMode()
|
||||
.withExposedPorts(8123)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/onboarding.html", 8123))
|
||||
);
|
||||
};
|
||||
|
||||
const createHomeAssistantIntegration = (container: StartedTestContainer, apiKeyOverride?: string) => {
|
||||
console.log("Creating Home Assistant integration...");
|
||||
return new HomeAssistantIntegration({
|
||||
id: "1",
|
||||
decryptedSecrets: [
|
||||
|
||||
@@ -145,8 +145,8 @@ describe("Nzbget integration", () => {
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).resolves.not.toThrow();
|
||||
// NzbGet is slow and we wait for a second before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
// NzbGet is slow and we wait for a few seconds before querying the items. Test was flaky without this.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const result = await nzbGetIntegration.getClientJobsAndStatusAsync({ limit: 99 });
|
||||
expect(result.items).toHaveLength(0);
|
||||
|
||||
|
||||
@@ -211,12 +211,15 @@ const createPiHoleIntegrationV5 = (container: StartedTestContainer, apiKey: stri
|
||||
};
|
||||
|
||||
const createPiHoleV6Container = (password: string) => {
|
||||
return new GenericContainer("pihole/pihole:latest")
|
||||
.withEnvironment({
|
||||
FTLCONF_webserver_api_password: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
.withWaitStrategy(Wait.forHttp("/admin", 80));
|
||||
return (
|
||||
new GenericContainer("pihole/pihole:latest")
|
||||
.withEnvironment({
|
||||
FTLCONF_webserver_api_password: password,
|
||||
})
|
||||
.withExposedPorts(80)
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/admin/login", 80))
|
||||
);
|
||||
};
|
||||
|
||||
const createPiHoleIntegrationV6 = (container: StartedTestContainer, apiKey: string) => {
|
||||
|
||||
@@ -192,16 +192,19 @@ describe("Sabnzbd integration", () => {
|
||||
});
|
||||
|
||||
const createSabnzbdContainer = () => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/sabnzbd.ini"),
|
||||
target: "/config/sabnzbd.ini",
|
||||
},
|
||||
])
|
||||
.withExposedPorts(1212)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
.withWaitStrategy(Wait.forHttp("/", 1212));
|
||||
return (
|
||||
new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer([
|
||||
{
|
||||
source: join(__dirname, "/volumes/usenet/sabnzbd.ini"),
|
||||
target: "/config/sabnzbd.ini",
|
||||
},
|
||||
])
|
||||
.withExposedPorts(1212)
|
||||
.withEnvironment({ PUID: "0", PGID: "0" })
|
||||
// This has to be a page that is not redirected (or a status code has to be defined withStatusCode(statusCode))
|
||||
.withWaitStrategy(Wait.forHttp("/sabnzbd/wizard/", 1212))
|
||||
);
|
||||
};
|
||||
|
||||
const createSabnzbdIntegration = (container: StartedTestContainer, apiKey: string) => {
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
"ioredis": "5.6.1",
|
||||
"superjson": "2.2.2",
|
||||
"winston": "3.17.0",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,19 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,15 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"react": "19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0"
|
||||
"@mantine/notifications": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,14 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^3.25.30",
|
||||
"zod": "^3.25.62",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/adm-zip": "0.5.7",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,13 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"zod": "^3.25.30"
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,14 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"octokit": "^5.0.2",
|
||||
"octokit": "^5.0.3",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.0.2",
|
||||
"next": "15.3.2",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,12 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@mantine/spotlight": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@mantine/spotlight": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"jotai": "^2.12.5",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
@@ -47,7 +47,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"next-intl": "4.1.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
@@ -41,7 +41,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -263,14 +263,14 @@
|
||||
}
|
||||
},
|
||||
"removeImage": {
|
||||
"label": "删除图像",
|
||||
"label": "删除头像",
|
||||
"confirm": "确定要移除这张图片吗?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": "图像删除成功"
|
||||
},
|
||||
"error": {
|
||||
"message": "无法删除图像"
|
||||
"message": "无法删除头像"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,17 +612,17 @@
|
||||
"select": {
|
||||
"label": "选择应用",
|
||||
"notFound": "未找到应用",
|
||||
"search": "",
|
||||
"noResults": "",
|
||||
"action": "",
|
||||
"title": ""
|
||||
"search": "搜索应用",
|
||||
"noResults": "没有结果",
|
||||
"action": "选择 {app}",
|
||||
"title": "选择一个应用程序添加到此面板"
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"action": ""
|
||||
"title": "创建应用",
|
||||
"description": "创建应用 ",
|
||||
"action": "打开应用创建"
|
||||
},
|
||||
"add": ""
|
||||
"add": "添加应用"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -705,125 +705,125 @@
|
||||
"error": {
|
||||
"common": {
|
||||
"cause": {
|
||||
"title": ""
|
||||
"title": "更多详情"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "未知错误",
|
||||
"description": "发生未知错误,打开下面的原因以查看更多详情"
|
||||
},
|
||||
"parse": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "解析错误",
|
||||
"description": "无法解析该响应。请确认URL指向服务的基本URL。"
|
||||
},
|
||||
"authorization": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "授权错误",
|
||||
"description": "请求未被授权。请验证凭据是否正确,并且您拥有足够的权限。"
|
||||
},
|
||||
"statusCode": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"otherDescription": "",
|
||||
"title": "响应错误",
|
||||
"description": "收到来自<url></url>的意外{statusCode} ({reason})响应。请验证URL是否指向集成的基本URL。",
|
||||
"otherDescription": "从<url></url>收到意外的{statusCode}响应。请验证URL是否指向集成的基本URL。",
|
||||
"reason": {
|
||||
"badRequest": "",
|
||||
"notFound": "",
|
||||
"tooManyRequests": "",
|
||||
"internalServerError": "",
|
||||
"serviceUnavailable": "",
|
||||
"gatewayTimeout": ""
|
||||
"badRequest": "错误请求",
|
||||
"notFound": "未找到",
|
||||
"tooManyRequests": "请求过于频繁",
|
||||
"internalServerError": "服务器内部错误",
|
||||
"serviceUnavailable": "服务暂时不可用",
|
||||
"gatewayTimeout": "网关超时"
|
||||
}
|
||||
},
|
||||
"certificate": {
|
||||
"title": "",
|
||||
"title": "证书错误",
|
||||
"description": {
|
||||
"expired": "",
|
||||
"notYetValid": "",
|
||||
"untrusted": "",
|
||||
"hostnameMismatch": ""
|
||||
"expired": "此证书已经过期。",
|
||||
"notYetValid": "此证书尚未生效。",
|
||||
"untrusted": "此证书不受信任。",
|
||||
"hostnameMismatch": "证书主机名与 URL 不匹配。"
|
||||
},
|
||||
"alert": {
|
||||
"permission": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "权限不足",
|
||||
"message": "您无权信任或上传证书。请与您的管理员联系上传必要的根证书。"
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名不匹配",
|
||||
"message": "证书中的主机名与您连接的主机名不匹配。 这可能会显示安全风险,但您仍然可以选择信任此证书。"
|
||||
},
|
||||
"extract": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "CA证书提取失败",
|
||||
"message": "只有自签署证书链可以自动获取。如果使用的是自签名证书,请确保手动上传CA证书。您可以找到有关如何执行此操作的说明<docsLink></docsLink>。"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"retry": {
|
||||
"label": ""
|
||||
"label": "重试创建"
|
||||
},
|
||||
"trust": {
|
||||
"label": ""
|
||||
"label": "信任证书"
|
||||
},
|
||||
"upload": {
|
||||
"label": ""
|
||||
"label": "上传证书"
|
||||
}
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任主机名不匹配",
|
||||
"message": "您确定要信任主机名不匹配的证书吗?"
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "可信证书",
|
||||
"message": "将主机名添加到受信任证书列表中"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任证书失败",
|
||||
"message": "主机名不匹配的证书不可信"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selfSigned": {
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任自签名证书",
|
||||
"message": "您确定要信任此自签名证书吗?"
|
||||
},
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "可信证书",
|
||||
"message": "已添加证书到可信证书列表"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "信任证书失败",
|
||||
"message": "向受信任证书列表添加证书失败"
|
||||
}
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "详情",
|
||||
"description": "在决定信任证书信息之前,请检查该证书信息。",
|
||||
"content": {
|
||||
"action": "",
|
||||
"title": ""
|
||||
"action": "显示内容",
|
||||
"title": "PEM 证书"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"title": "",
|
||||
"title": "错误请求",
|
||||
"description": {
|
||||
"connection": {
|
||||
"hostUnreachable": "",
|
||||
"networkUnreachable": "",
|
||||
"refused": "",
|
||||
"reset": ""
|
||||
"hostUnreachable": "无法连接到服务器。这通常意味着主机离线或无法从您的网络访问。",
|
||||
"networkUnreachable": "网络不可达。请检查网络连接或网络配置。",
|
||||
"refused": "服务器拒绝连接。它可能没有运行或正在拒绝指定端口上的请求。",
|
||||
"reset": "服务器意外关闭了连接。如果服务器不稳定或重新启动,就会发生这种情况。"
|
||||
},
|
||||
"dns": {
|
||||
"notFound": "",
|
||||
"timeout": "",
|
||||
"noAnswer": ""
|
||||
"notFound": "找不到服务器地址。请检查URL是否有拼写错误或无效的域名。",
|
||||
"timeout": "DNS查找超时。这可能是暂时的问题,请稍后再试。",
|
||||
"noAnswer": "DNS服务器没有返回有效的响应。域可能存在,但没有有效记录。"
|
||||
},
|
||||
"timeout": {
|
||||
"aborted": "",
|
||||
"timeout": ""
|
||||
"aborted": "请求在完成之前被中止。这可能是由于用户操作或系统超时造成的。",
|
||||
"timeout": "请求耗时太长。请检查您的网络或稍后再试。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -895,8 +895,8 @@
|
||||
"message": "该路径可能不正确"
|
||||
},
|
||||
"tooManyRequests": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "指定时间内请求过多",
|
||||
"message": "请求太多了。您可能会受到速率限制或被目标系统拒绝"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1004,7 +1004,7 @@
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"discard": "Discard",
|
||||
"close": "",
|
||||
"close": "关闭",
|
||||
"confirm": "确认",
|
||||
"continue": "继续",
|
||||
"previous": "上一步",
|
||||
@@ -1220,7 +1220,7 @@
|
||||
"label": "集成"
|
||||
},
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "标题"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": "自定义 CSS 类"
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"label": "显示内存信息"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
"label": "显示运行时间"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "显示文件系统信息"
|
||||
@@ -1767,7 +1767,7 @@
|
||||
"label": "默认标签"
|
||||
},
|
||||
"visibleClusterSections": {
|
||||
"label": ""
|
||||
"label": "可见集群部分"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "部分指标要求"
|
||||
@@ -1839,11 +1839,11 @@
|
||||
}
|
||||
},
|
||||
"dockerContainers": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Docker 状态",
|
||||
"description": "您的容器状态 (这个小部件只能用管理员权限添加)",
|
||||
"option": {},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "获取容器统计信息失败"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -1912,8 +1912,8 @@
|
||||
"description": "显示媒体服务器上的当前流",
|
||||
"option": {
|
||||
"showOnlyPlaying": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只显示当前正在播放",
|
||||
"description": "禁用此功能对plex不起作用"
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
@@ -1946,7 +1946,7 @@
|
||||
"label": "显示标记为已完成的 Torrent 条目"
|
||||
},
|
||||
"showCompletedHttp": {
|
||||
"label": ""
|
||||
"label": "显示标记为已完成的杂项"
|
||||
},
|
||||
"activeTorrentThreshold": {
|
||||
"label": "隐藏在此阈值下已完成的 Torrent (kiB/s)"
|
||||
@@ -1961,8 +1961,8 @@
|
||||
"label": "使用过滤器来计算比率"
|
||||
},
|
||||
"limitPerIntegration": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "限制每个集成的项目",
|
||||
"description": "这将限制每个集成显示的项目数量,而不是全局项目"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -2095,7 +2095,7 @@
|
||||
"approved": "已批准",
|
||||
"declined": "已拒绝",
|
||||
"failed": "失败",
|
||||
"completed": ""
|
||||
"completed": "已完成"
|
||||
},
|
||||
"toBeDetermined": "待定"
|
||||
},
|
||||
@@ -2199,94 +2199,106 @@
|
||||
}
|
||||
},
|
||||
"releases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "发布",
|
||||
"description": "显示特定版本正则表达式中当前版本的列表。",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "内部新版本",
|
||||
"description": "使用示例:1w(1周),10M(10个月)。支持单位类型h(小时),d(天),w(周),M(月),y(年)。留空不突出显示新版本。"
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "内部过期发布",
|
||||
"description": "使用示例:1w(1周),10M(10个月)。支持单位类型h(小时),d(天),w(周),M(月),y(年)。留空不突出显示过时的版本。"
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只显示高亮显示",
|
||||
"description": "只显示新的或过时的版本。如上所述。"
|
||||
},
|
||||
"showDetails": {
|
||||
"label": ""
|
||||
"label": "展开详情"
|
||||
},
|
||||
"topReleases": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "热门发布",
|
||||
"description": "要显示的最新版本的最大数量。零表示没有限制。"
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"label": ""
|
||||
"label": "仓库",
|
||||
"addRepository": {
|
||||
"label": "添加仓库"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "从 Docker 导入",
|
||||
"loading": "正在加载 docker 映像",
|
||||
"noImagesFound": "没有找到 docker 映像",
|
||||
"listFoundImages": "找到的映像列表",
|
||||
"listAlreadyImportedImages": "已导入的映像列表",
|
||||
"allImagesAlreadyImported": "所有映像已导入",
|
||||
"onlyAdminCanImport": "只有管理员才能从 docker 导入"
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
"label": "提供者"
|
||||
},
|
||||
"identifier": {
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
"label": "标识符",
|
||||
"placeholder": "姓名或所有者名称"
|
||||
},
|
||||
"name": {
|
||||
"label": ""
|
||||
"label": "名称"
|
||||
},
|
||||
"versionFilter": {
|
||||
"label": "",
|
||||
"label": "版本过滤器",
|
||||
"prefix": {
|
||||
"label": ""
|
||||
"label": "前缀"
|
||||
},
|
||||
"precision": {
|
||||
"label": "",
|
||||
"label": "精度",
|
||||
"options": {
|
||||
"none": ""
|
||||
"none": "空"
|
||||
}
|
||||
},
|
||||
"suffix": {
|
||||
"label": ""
|
||||
"label": "后缀"
|
||||
},
|
||||
"regex": {
|
||||
"label": ""
|
||||
"label": "正则表达式"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"label": ""
|
||||
"label": "编辑"
|
||||
},
|
||||
"editForm": {
|
||||
"title": "",
|
||||
"title": "编辑仓库",
|
||||
"cancel": {
|
||||
"label": ""
|
||||
"label": "取消"
|
||||
},
|
||||
"confirm": {
|
||||
"label": ""
|
||||
"label": "确认"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
"importForm": {
|
||||
"title": "从 Docker 导入"
|
||||
},
|
||||
"invalid": ""
|
||||
"example": {
|
||||
"label": "示例"
|
||||
},
|
||||
"invalid": "仓库定义无效,请检查值"
|
||||
}
|
||||
},
|
||||
"not-found": "",
|
||||
"pre-release": "",
|
||||
"archived": "",
|
||||
"forked": "",
|
||||
"starsCount": "",
|
||||
"forksCount": "",
|
||||
"issuesCount": "",
|
||||
"openProjectPage": "",
|
||||
"openReleasePage": "",
|
||||
"releaseDescription": "",
|
||||
"created": "",
|
||||
"not-found": "未找到",
|
||||
"pre-release": "预发行",
|
||||
"archived": "已归档",
|
||||
"forked": "复刻成功",
|
||||
"starsCount": "星",
|
||||
"forksCount": "复刻",
|
||||
"issuesCount": "打开问题",
|
||||
"openProjectPage": "打开项目页面",
|
||||
"openReleasePage": "打开发布页面",
|
||||
"releaseDescription": "发布说明",
|
||||
"created": "已创建",
|
||||
"error": {
|
||||
"label": "",
|
||||
"label": "错误",
|
||||
"options": {
|
||||
"noMatchingVersion": ""
|
||||
"noMatchingVersion": "没有找到匹配的版本"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2294,56 +2306,56 @@
|
||||
"option": {},
|
||||
"card": {
|
||||
"vpn": {
|
||||
"countConnected": ""
|
||||
"countConnected": "{count} 已连接"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "没有可用的数据,所有集成已断开",
|
||||
"unknownContentOption": "网络控制器摘要小部件的未知内容选项: "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "网络控制器概述",
|
||||
"description": "显示网络控制器摘要(如Unifi Controller)"
|
||||
},
|
||||
"networkControllerStatus": {
|
||||
"card": {
|
||||
"variants": {
|
||||
"wired": {
|
||||
"name": ""
|
||||
"name": "有线"
|
||||
},
|
||||
"wifi": {
|
||||
"name": ""
|
||||
"name": "Wi-Fi"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": ""
|
||||
"label": "用户数"
|
||||
},
|
||||
"guests": {
|
||||
"label": ""
|
||||
"label": "游客"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"content": {
|
||||
"option": {
|
||||
"wifi": {
|
||||
"label": ""
|
||||
"label": "Wi-Fi"
|
||||
},
|
||||
"wired": {
|
||||
"label": ""
|
||||
"label": "有线"
|
||||
}
|
||||
},
|
||||
"label": ""
|
||||
"label": "小部件内容"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"integrationsDisconnected": "",
|
||||
"unknownContentOption": ""
|
||||
"integrationsDisconnected": "没有可用的数据,所有集成已断开",
|
||||
"unknownContentOption": "网络状态小部件的未知内容选项: "
|
||||
},
|
||||
"name": "",
|
||||
"description": ""
|
||||
"name": "网络状态",
|
||||
"description": "在网络上显示已连接的设备"
|
||||
},
|
||||
"networkController": {
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "获取网络控制器概述失败"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2466,10 +2478,10 @@
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "背景图片 URL",
|
||||
"placeholder": "",
|
||||
"placeholder": "开始输入以搜索本地映像",
|
||||
"group": {
|
||||
"your": "你的图像",
|
||||
"other": ""
|
||||
"your": "你的映像",
|
||||
"other": "其他映像"
|
||||
}
|
||||
},
|
||||
"backgroundImageAttachment": {
|
||||
@@ -3111,7 +3123,7 @@
|
||||
"label": "网络控制器"
|
||||
},
|
||||
"dockerContainers": {
|
||||
"label": ""
|
||||
"label": "Docker 容器"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3177,7 +3189,7 @@
|
||||
"updated": "已更新 {when}",
|
||||
"search": "搜索 {count} 容器",
|
||||
"selected": "{totalCount} 容器的 {selectCount}",
|
||||
"footer": ""
|
||||
"footer": "总计 {count} 容器"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -3197,10 +3209,10 @@
|
||||
},
|
||||
"stats": {
|
||||
"cpu": {
|
||||
"label": ""
|
||||
"label": "CPU"
|
||||
},
|
||||
"memory": {
|
||||
"label": ""
|
||||
"label": "内存"
|
||||
}
|
||||
},
|
||||
"containerImage": {
|
||||
@@ -3211,7 +3223,7 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"title": "",
|
||||
"title": "操作",
|
||||
"start": {
|
||||
"label": "开始",
|
||||
"notification": {
|
||||
@@ -3656,7 +3668,7 @@
|
||||
"certificates": {
|
||||
"label": "证书",
|
||||
"hostnames": {
|
||||
"label": ""
|
||||
"label": "主机名"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4057,25 +4069,25 @@
|
||||
"certificate": {
|
||||
"field": {
|
||||
"hostname": {
|
||||
"label": ""
|
||||
"label": "主机名"
|
||||
},
|
||||
"subject": {
|
||||
"label": ""
|
||||
"label": "主题"
|
||||
},
|
||||
"issuer": {
|
||||
"label": ""
|
||||
"label": "发行者"
|
||||
},
|
||||
"validFrom": {
|
||||
"label": ""
|
||||
"label": "有效期自"
|
||||
},
|
||||
"validTo": {
|
||||
"label": ""
|
||||
"label": "有效期至"
|
||||
},
|
||||
"serialNumber": {
|
||||
"label": ""
|
||||
"label": "序号"
|
||||
},
|
||||
"fingerprint": {
|
||||
"label": ""
|
||||
"label": "指纹"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
@@ -4086,19 +4098,19 @@
|
||||
"title": "还没有证书"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "无效证书",
|
||||
"description": "解析证书失败"
|
||||
},
|
||||
"expires": "到期时间 {when}",
|
||||
"toHostnames": ""
|
||||
"toHostnames": "信任的主机名"
|
||||
},
|
||||
"hostnames": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "信任的证书主机名",
|
||||
"description": "某些证书不允许指定域名使用 Homarr 请求他们, 由于这个原因,所有信任的主机名及其证书缩略图都被用来绕过这些限制。",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "还没有主机名"
|
||||
},
|
||||
"toCertificates": ""
|
||||
"toCertificates": "证书"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
@@ -4130,16 +4142,16 @@
|
||||
}
|
||||
},
|
||||
"removeHostname": {
|
||||
"label": "",
|
||||
"confirm": "",
|
||||
"label": "删除受信任的主机名",
|
||||
"confirm": "您确定要删除此受信任的主机名吗?这可能会导致一些集成停止工作。",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名已删除",
|
||||
"message": "主机名已成功删除"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "主机名未删除",
|
||||
"message": "无法删除主机名"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Tilføj repository"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "Udbyder"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Bekræft"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "Eksempel"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Repository hinzufügen"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "Anbieter"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Bestätigen"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "Beispiel"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Repositories",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Add repository"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "Import from docker",
|
||||
"loading": "Loading docker images",
|
||||
"noImagesFound": "No docker images found",
|
||||
"listFoundImages": "List of found images",
|
||||
"listAlreadyImportedImages": "List of already imported images",
|
||||
"allImagesAlreadyImported": "All images already imported",
|
||||
"onlyAdminCanImport": "Only administrators can import from docker"
|
||||
},
|
||||
"provider": {
|
||||
"label": "Provider"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Confirm"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": "Import from Docker"
|
||||
},
|
||||
"example": {
|
||||
"label": "Example"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -747,7 +747,7 @@
|
||||
"message": ""
|
||||
},
|
||||
"hostnameMismatch": {
|
||||
"title": "",
|
||||
"title": "Nom d'hôte incohérent",
|
||||
"message": ""
|
||||
},
|
||||
"extract": {
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"label": "הצג מידע זיכרון"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
"label": "הצג זמן פעולה"
|
||||
},
|
||||
"fileSystem": {
|
||||
"label": "הצג מידע על מערכת הקבצים"
|
||||
@@ -1767,7 +1767,7 @@
|
||||
"label": "כרטיסיית ברירת מחדל"
|
||||
},
|
||||
"visibleClusterSections": {
|
||||
"label": ""
|
||||
"label": "מקטעי אשכול גלויים"
|
||||
},
|
||||
"sectionIndicatorRequirement": {
|
||||
"label": "דרישת מציין מקטע"
|
||||
@@ -1961,8 +1961,8 @@
|
||||
"label": "השתמש במסנן כדי לחשב יחס"
|
||||
},
|
||||
"limitPerIntegration": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "הגבלת פריטים לכל אינטגרציה",
|
||||
"description": "פעולה זו תגביל את מספר הפריטים המוצגים בכל אינטגרציה, לא באופן גלובלי"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "מאגרים",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "הוסף מאגר"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "ספק"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "אשר"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "דוגמא"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "Depolar",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "Depo Ekle"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "Docker'dan içe aktar",
|
||||
"loading": "Docker imajları yükleniyor",
|
||||
"noImagesFound": "Hiç docker imajı bulunamadı",
|
||||
"listFoundImages": "Bulunan imajların listesi",
|
||||
"listAlreadyImportedImages": "Daha önce içe aktarılan imajların listesi",
|
||||
"allImagesAlreadyImported": "Tüm imajlar daha önce içe aktarıldı",
|
||||
"onlyAdminCanImport": "Sadece yöneticiler docker'dan içe aktarabilir"
|
||||
},
|
||||
"provider": {
|
||||
"label": "Sağlayıcı"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "Onayla"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": "Docker'dan içe aktar"
|
||||
},
|
||||
"example": {
|
||||
"label": "Örnek"
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": ""
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
},
|
||||
|
||||
@@ -2223,9 +2223,18 @@
|
||||
},
|
||||
"repositories": {
|
||||
"label": "儲存庫",
|
||||
"addRRepository": {
|
||||
"addRepository": {
|
||||
"label": "新增儲存庫"
|
||||
},
|
||||
"importRepositories": {
|
||||
"label": "",
|
||||
"loading": "",
|
||||
"noImagesFound": "",
|
||||
"listFoundImages": "",
|
||||
"listAlreadyImportedImages": "",
|
||||
"allImagesAlreadyImported": "",
|
||||
"onlyAdminCanImport": ""
|
||||
},
|
||||
"provider": {
|
||||
"label": "提供者"
|
||||
},
|
||||
@@ -2266,6 +2275,9 @@
|
||||
"label": "確認"
|
||||
}
|
||||
},
|
||||
"importForm": {
|
||||
"title": ""
|
||||
},
|
||||
"example": {
|
||||
"label": "範例"
|
||||
},
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/dates": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/dates": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
@@ -43,7 +43,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/css-modules": "^1.0.5",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^3.25.30",
|
||||
"zod": "^3.25.62",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -48,42 +48,42 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^8.0.2",
|
||||
"@mantine/core": "^8.0.2",
|
||||
"@mantine/hooks": "^8.0.2",
|
||||
"@tabler/icons-react": "^3.33.0",
|
||||
"@tiptap/extension-color": "2.12.0",
|
||||
"@tiptap/extension-highlight": "2.12.0",
|
||||
"@tiptap/extension-image": "2.12.0",
|
||||
"@tiptap/extension-link": "^2.12.0",
|
||||
"@tiptap/extension-table": "2.12.0",
|
||||
"@tiptap/extension-table-cell": "2.12.0",
|
||||
"@tiptap/extension-table-header": "2.12.0",
|
||||
"@tiptap/extension-table-row": "2.12.0",
|
||||
"@tiptap/extension-task-item": "2.12.0",
|
||||
"@tiptap/extension-task-list": "2.12.0",
|
||||
"@tiptap/extension-text-align": "2.12.0",
|
||||
"@tiptap/extension-text-style": "2.12.0",
|
||||
"@tiptap/extension-underline": "2.12.0",
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"@mantine/charts": "^8.1.0",
|
||||
"@mantine/core": "^8.1.0",
|
||||
"@mantine/hooks": "^8.1.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tiptap/extension-color": "2.14.0",
|
||||
"@tiptap/extension-highlight": "2.14.0",
|
||||
"@tiptap/extension-image": "2.14.0",
|
||||
"@tiptap/extension-link": "^2.14.0",
|
||||
"@tiptap/extension-table": "2.14.0",
|
||||
"@tiptap/extension-table-cell": "2.14.0",
|
||||
"@tiptap/extension-table-header": "2.14.0",
|
||||
"@tiptap/extension-table-row": "2.14.0",
|
||||
"@tiptap/extension-task-item": "2.14.0",
|
||||
"@tiptap/extension-task-list": "2.14.0",
|
||||
"@tiptap/extension-text-align": "2.14.0",
|
||||
"@tiptap/extension-text-style": "2.14.0",
|
||||
"@tiptap/extension-underline": "2.14.0",
|
||||
"@tiptap/react": "^2.14.0",
|
||||
"@tiptap/starter-kit": "^2.14.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.2",
|
||||
"next": "15.3.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.3",
|
||||
"video.js": "^8.22.0",
|
||||
"zod": "^3.25.30"
|
||||
"video.js": "^8.23.3",
|
||||
"zod": "^3.25.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ActionIcon, Button, Divider, Fieldset, Group, Select, Stack, Text, TextInput } from "@mantine/core";
|
||||
import {
|
||||
Accordion,
|
||||
ActionIcon,
|
||||
Button,
|
||||
Checkbox,
|
||||
Code,
|
||||
Divider,
|
||||
Fieldset,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import type { CheckboxProps } from "@mantine/core";
|
||||
import type { FormErrors } from "@mantine/form";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { IconEdit, IconTrash, IconTriangleFilled } from "@tabler/icons-react";
|
||||
import {
|
||||
IconBrandDocker,
|
||||
IconEdit,
|
||||
IconPlus,
|
||||
IconSquare,
|
||||
IconSquareCheck,
|
||||
IconTrash,
|
||||
IconTriangleFilled,
|
||||
} from "@tabler/icons-react";
|
||||
import { escapeForRegEx } from "@tiptap/react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { findBestIconMatch, IconPicker } from "@homarr/forms-collection";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
import { MaskedImage } from "@homarr/ui";
|
||||
|
||||
import { Providers } from "../releases/releases-providers";
|
||||
import { isProviderKey, Providers } from "../releases/releases-providers";
|
||||
import type { ReleasesRepository, ReleasesVersionFilter } from "../releases/releases-repository";
|
||||
import type { CommonWidgetInputProps } from "./common";
|
||||
import { useWidgetInputTranslation } from "./common";
|
||||
@@ -32,11 +59,14 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const form = useFormContext();
|
||||
const repositories = form.values.options[property] as ReleasesRepository[];
|
||||
const { openModal } = useModalAction(ReleaseEditModal);
|
||||
const { openModal: openEditModal } = useModalAction(RepositoryEditModal);
|
||||
const { openModal: openImportModal } = useModalAction(RepositoryImportModal);
|
||||
const versionFilterPrecisionOptions = useMemo(
|
||||
() => [tRepository("versionFilter.precision.options.none"), "#", "#.#", "#.#.#", "#.#.#.#", "#.#.#.#.#"],
|
||||
[tRepository],
|
||||
);
|
||||
const { data: session } = useSession();
|
||||
const isAdmin = session?.user.permissions.includes("admin") ?? false;
|
||||
|
||||
const onRepositorySave = useCallback(
|
||||
(repository: ReleasesRepository, index: number): FormValidation => {
|
||||
@@ -62,8 +92,8 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
[form, property],
|
||||
);
|
||||
|
||||
const addNewItem = () => {
|
||||
const item = {
|
||||
const addNewRepository = () => {
|
||||
const repository: ReleasesRepository = {
|
||||
providerKey: "DockerHub",
|
||||
identifier: "",
|
||||
};
|
||||
@@ -74,16 +104,16 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
...previous,
|
||||
options: {
|
||||
...previous.options,
|
||||
[property]: [...previousValues, item],
|
||||
[property]: [...previousValues, repository],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const index = repositories.length;
|
||||
|
||||
openModal({
|
||||
openEditModal({
|
||||
fieldPath: `options.${property}.${index}`,
|
||||
repository: item,
|
||||
repository,
|
||||
onRepositorySave: (saved) => onRepositorySave(saved, index),
|
||||
onRepositoryCancel: () => onRepositoryRemove(index),
|
||||
versionFilterPrecisionOptions,
|
||||
@@ -106,24 +136,56 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
return (
|
||||
<Fieldset legend={t("label")}>
|
||||
<Stack gap="5">
|
||||
<Button onClick={addNewItem}>{tRepository("addRRepository.label")}</Button>
|
||||
<Group grow>
|
||||
<Button leftSection={<IconPlus />} onClick={addNewRepository}>
|
||||
{tRepository("addRepository.label")}
|
||||
</Button>
|
||||
<Tooltip label={tRepository("importRepositories.onlyAdminCanImport")} disabled={isAdmin} withArrow>
|
||||
<Button
|
||||
disabled={!isAdmin}
|
||||
leftSection={<IconBrandDocker stroke={1.25} />}
|
||||
onClick={() =>
|
||||
openImportModal({
|
||||
repositories,
|
||||
versionFilterPrecisionOptions,
|
||||
onConfirm: (selectedRepositories) => {
|
||||
if (!selectedRepositories.length) return;
|
||||
|
||||
form.setValues((previous) => {
|
||||
const previousValues = previous.options?.[property] as ReleasesRepository[];
|
||||
return {
|
||||
...previous,
|
||||
options: {
|
||||
...previous.options,
|
||||
[property]: [...previousValues, ...selectedRepositories],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
isAdmin,
|
||||
})
|
||||
}
|
||||
>
|
||||
{tRepository("importRepositories.label")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
|
||||
{repositories.map((repository, index) => {
|
||||
return (
|
||||
<Stack key={`${repository.providerKey}.${repository.identifier}`} gap={5}>
|
||||
<Group align="center" gap="xs">
|
||||
<MaskedOrNormalImage
|
||||
hasColor={false}
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey]?.iconUrl}
|
||||
<Image
|
||||
src={repository.iconUrl ?? Providers[repository.providerKey].iconUrl}
|
||||
style={{
|
||||
height: "1em",
|
||||
width: "1em",
|
||||
height: "1.2em",
|
||||
width: "1.2em",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text c="dimmed" fw={100} size="xs">
|
||||
{Providers[repository.providerKey]?.name}
|
||||
{Providers[repository.providerKey].name}
|
||||
</Text>
|
||||
|
||||
<Group justify="space-between" align="center" style={{ flex: 1 }} gap={5}>
|
||||
@@ -135,7 +197,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
|
||||
<Button
|
||||
onClick={() =>
|
||||
openModal({
|
||||
openEditModal({
|
||||
fieldPath: `options.${property}.${index}`,
|
||||
repository,
|
||||
onRepositorySave: (saved) => onRepositorySave(saved, index),
|
||||
@@ -185,7 +247,7 @@ const formatIdentifierName = (identifier: string) => {
|
||||
return unformattedName?.replace(/[-_]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (char) => char.toUpperCase()) ?? "";
|
||||
};
|
||||
|
||||
interface ReleaseEditProps {
|
||||
interface RepositoryEditProps {
|
||||
fieldPath: string;
|
||||
repository: ReleasesRepository;
|
||||
onRepositorySave: (repository: ReleasesRepository) => FormValidation;
|
||||
@@ -193,7 +255,7 @@ interface ReleaseEditProps {
|
||||
versionFilterPrecisionOptions: string[];
|
||||
}
|
||||
|
||||
const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions }) => {
|
||||
const RepositoryEditModal = createModal<RepositoryEditProps>(({ innerProps, actions }) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository }));
|
||||
@@ -262,7 +324,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
value={tempRepository.providerKey}
|
||||
error={formErrors[`${innerProps.fieldPath}.providerKey`]}
|
||||
onChange={(value) => {
|
||||
if (value && Providers[value]) {
|
||||
if (value && isProviderKey(value)) {
|
||||
handleChange({ providerKey: value });
|
||||
}
|
||||
}}
|
||||
@@ -386,7 +448,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
{tRepository("editForm.cancel.label")}
|
||||
</Button>
|
||||
|
||||
<Button data-autofocus onClick={handleConfirm} color="red.9" loading={loading}>
|
||||
<Button data-autofocus onClick={handleConfirm} loading={loading}>
|
||||
{tRepository("editForm.confirm.label")}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -398,3 +460,247 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
||||
},
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
interface ReleasesRepositoryImport extends ReleasesRepository {
|
||||
alreadyImported: boolean;
|
||||
}
|
||||
|
||||
interface ContainerImageSelectorProps {
|
||||
containerImage: ReleasesRepositoryImport;
|
||||
versionFilterPrecisionOptions: string[];
|
||||
onImageSelectionChanged?: (isSelected: boolean) => void;
|
||||
}
|
||||
|
||||
const ContainerImageSelector = ({
|
||||
containerImage,
|
||||
versionFilterPrecisionOptions,
|
||||
onImageSelectionChanged,
|
||||
}: ContainerImageSelectorProps) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const checkBoxProps: CheckboxProps = !onImageSelectionChanged
|
||||
? {
|
||||
disabled: true,
|
||||
checked: true,
|
||||
}
|
||||
: {
|
||||
onChange: (event) => onImageSelectionChanged(event.currentTarget.checked),
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
key={`${Providers[containerImage.providerKey].name}/${containerImage.identifier}`}
|
||||
gap="xl"
|
||||
justify="space-between"
|
||||
>
|
||||
<Group gap="md">
|
||||
<Checkbox
|
||||
label={
|
||||
<Group>
|
||||
<Image
|
||||
src={containerImage.iconUrl}
|
||||
style={{
|
||||
height: "1.2em",
|
||||
width: "1.2em",
|
||||
}}
|
||||
/>
|
||||
<Text>{containerImage.identifier}</Text>
|
||||
</Group>
|
||||
}
|
||||
{...checkBoxProps}
|
||||
/>
|
||||
|
||||
{containerImage.versionFilter && (
|
||||
<Group gap={5}>
|
||||
<Text c="dimmed" size="xs">
|
||||
{tRepository("versionFilter.label")}:
|
||||
</Text>
|
||||
|
||||
<Code>{containerImage.versionFilter.prefix && containerImage.versionFilter.prefix}</Code>
|
||||
<Code color="var(--mantine-primary-color-light)" fw={700}>
|
||||
{versionFilterPrecisionOptions[containerImage.versionFilter.precision]}
|
||||
</Code>
|
||||
<Code>{containerImage.versionFilter.suffix && containerImage.versionFilter.suffix}</Code>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Group>
|
||||
<MaskedImage
|
||||
color="dimmed"
|
||||
imageUrl={Providers[containerImage.providerKey].iconUrl}
|
||||
style={{
|
||||
height: "1em",
|
||||
width: "1em",
|
||||
}}
|
||||
/>
|
||||
<Text ff="monospace" c="dimmed" size="sm">
|
||||
{Providers[containerImage.providerKey].name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
interface RepositoryImportProps {
|
||||
repositories: ReleasesRepository[];
|
||||
versionFilterPrecisionOptions: string[];
|
||||
onConfirm: (selectedRepositories: ReleasesRepositoryImport[]) => void;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps, actions }) => {
|
||||
const tRepository = useScopedI18n("widget.releases.option.repositories");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedImages, setSelectedImages] = useState([] as ReleasesRepositoryImport[]);
|
||||
|
||||
const docker = clientApi.docker.getContainers.useQuery(undefined, {
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: innerProps.isAdmin,
|
||||
});
|
||||
|
||||
const containersImages: ReleasesRepositoryImport[] = useMemo(
|
||||
() =>
|
||||
docker.data?.containers.reduce<ReleasesRepositoryImport[]>((acc, containerImage) => {
|
||||
const providerKey = containerImage.image.startsWith("ghcr.io/") ? "Github" : "DockerHub";
|
||||
const [identifier, version] = containerImage.image.replace(/^(ghcr\.io\/|docker\.io\/)/, "").split(":");
|
||||
|
||||
if (!identifier) return acc;
|
||||
|
||||
if (acc.some((item) => item.providerKey === providerKey && item.identifier === identifier)) return acc;
|
||||
|
||||
acc.push({
|
||||
providerKey,
|
||||
identifier,
|
||||
iconUrl: containerImage.iconUrl ?? undefined,
|
||||
name: formatIdentifierName(identifier),
|
||||
versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined,
|
||||
alreadyImported: innerProps.repositories.some(
|
||||
(item) => item.providerKey === providerKey && item.identifier === identifier,
|
||||
),
|
||||
});
|
||||
return acc;
|
||||
}, []) ?? [],
|
||||
[docker.data, innerProps.repositories],
|
||||
);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
setLoading(true);
|
||||
|
||||
innerProps.onConfirm(selectedImages);
|
||||
|
||||
setLoading(false);
|
||||
actions.closeModal();
|
||||
}, [innerProps, selectedImages, actions]);
|
||||
|
||||
const allImagesImported = useMemo(
|
||||
() => containersImages.every((containerImage) => containerImage.alreadyImported),
|
||||
[containersImages],
|
||||
);
|
||||
|
||||
const anyImagesImported = useMemo(
|
||||
() => containersImages.some((containerImage) => containerImage.alreadyImported),
|
||||
[containersImages],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{docker.isPending ? (
|
||||
<Stack justify="center" align="center">
|
||||
<Loader size="xl" />
|
||||
<Title order={3}>{tRepository("importRepositories.loading")}</Title>
|
||||
</Stack>
|
||||
) : containersImages.length === 0 ? (
|
||||
<Stack justify="center" align="center">
|
||||
<IconBrandDocker stroke={1} size={128} />
|
||||
<Title order={3}>{tRepository("importRepositories.noImagesFound")}</Title>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack>
|
||||
<Accordion defaultValue={!allImagesImported ? "foundImages" : anyImagesImported ? "alreadyImported" : ""}>
|
||||
<Accordion.Item value="foundImages">
|
||||
<Accordion.Control disabled={allImagesImported} icon={<IconSquare stroke={1.25} />}>
|
||||
<Group>
|
||||
{tRepository("importRepositories.listFoundImages")}
|
||||
{allImagesImported && (
|
||||
<Text c="dimmed" size="sm">
|
||||
{tRepository("importRepositories.allImagesAlreadyImported")}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{!allImagesImported &&
|
||||
containersImages
|
||||
.filter((containerImage) => !containerImage.alreadyImported)
|
||||
.map((containerImage) => {
|
||||
return (
|
||||
<ContainerImageSelector
|
||||
key={`${containerImage.providerKey}/${containerImage.identifier}`}
|
||||
containerImage={containerImage}
|
||||
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
|
||||
onImageSelectionChanged={(isSelected) =>
|
||||
isSelected
|
||||
? setSelectedImages([...selectedImages, containerImage])
|
||||
: setSelectedImages(selectedImages.filter((img) => img !== containerImage))
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="alreadyImported">
|
||||
<Accordion.Control disabled={!anyImagesImported} icon={<IconSquareCheck stroke={1.25} />}>
|
||||
{tRepository("importRepositories.listAlreadyImportedImages")}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{anyImagesImported &&
|
||||
containersImages
|
||||
.filter((containerImage) => containerImage.alreadyImported)
|
||||
.map((containerImage) => {
|
||||
return (
|
||||
<ContainerImageSelector
|
||||
key={`${containerImage.providerKey}/${containerImage.identifier}`}
|
||||
containerImage={containerImage}
|
||||
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={actions.closeModal} color="gray.5">
|
||||
{tRepository("editForm.cancel.label")}
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleConfirm} loading={loading} disabled={selectedImages.length === 0}>
|
||||
{tRepository("editForm.confirm.label")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle(t) {
|
||||
return t("widget.releases.option.repositories.importForm.title");
|
||||
},
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
const parseImageVersionToVersionFilter = (imageVersion: string): ReleasesVersionFilter | undefined => {
|
||||
const version = /(?<=\D|^)\d+(?:\.\d+)*(?![\d.])/.exec(imageVersion)?.[0];
|
||||
|
||||
if (!version) return undefined;
|
||||
|
||||
const [prefix, suffix] = imageVersion.split(version);
|
||||
|
||||
return {
|
||||
prefix,
|
||||
precision: version.split(".").length,
|
||||
suffix,
|
||||
};
|
||||
};
|
||||
@@ -38,34 +38,40 @@ export const ResourceTable = ({ type, data, isTiny }: ResourceTableProps) => {
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ResourcePopover key={item.name} item={item}>
|
||||
<Popover.Target>
|
||||
<TableTr fz={isTiny ? "8px" : "xs"}>
|
||||
<td>
|
||||
<Group wrap="nowrap" gap={isTiny ? 8 : "xs"}>
|
||||
<Indicator size={isTiny ? 4 : 8} children={null} color={item.isRunning ? "green" : "yellow"} />
|
||||
<Text lineClamp={1} fz={isTiny ? "8px" : "xs"}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Group>
|
||||
</td>
|
||||
{item.type === "storage" ? (
|
||||
<td style={{ WebkitLineClamp: "1" }}>{item.node}</td>
|
||||
) : (
|
||||
<>
|
||||
<td style={{ whiteSpace: "nowrap" }}>{(item.cpu.utilization * 100).toFixed(1)}%</td>
|
||||
<td style={{ whiteSpace: "nowrap" }}>
|
||||
{(item.memory.total ? (item.memory.used / item.memory.total) * 100 : 0).toFixed(1)}%
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</TableTr>
|
||||
</Popover.Target>
|
||||
</ResourcePopover>
|
||||
);
|
||||
})}
|
||||
{data
|
||||
.sort((itemA, itemB) => {
|
||||
const nodeResult = itemA.node.localeCompare(itemB.node);
|
||||
if (nodeResult !== 0) return nodeResult;
|
||||
return itemA.name.localeCompare(itemB.name);
|
||||
})
|
||||
.map((item) => {
|
||||
return (
|
||||
<ResourcePopover key={item.id} item={item}>
|
||||
<Popover.Target>
|
||||
<TableTr fz={isTiny ? "8px" : "xs"}>
|
||||
<td>
|
||||
<Group wrap="nowrap" gap={isTiny ? 8 : "xs"}>
|
||||
<Indicator size={isTiny ? 4 : 8} children={null} color={item.isRunning ? "green" : "yellow"} />
|
||||
<Text lineClamp={1} fz={isTiny ? "8px" : "xs"}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Group>
|
||||
</td>
|
||||
{item.type === "storage" ? (
|
||||
<td style={{ WebkitLineClamp: "1" }}>{item.node}</td>
|
||||
) : (
|
||||
<>
|
||||
<td style={{ whiteSpace: "nowrap" }}>{(item.cpu.utilization * 100).toFixed(1)}%</td>
|
||||
<td style={{ whiteSpace: "nowrap" }}>
|
||||
{(item.memory.total ? (item.memory.used / item.memory.total) * 100 : 0).toFixed(1)}%
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</TableTr>
|
||||
</Popover.Target>
|
||||
</ResourcePopover>
|
||||
);
|
||||
})}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
>
|
||||
<MaskedOrNormalImage
|
||||
className="releases-repository-header-icon"
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey]?.iconUrl}
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey].iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
style={{
|
||||
width: "1em",
|
||||
@@ -474,7 +474,7 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
||||
<Group className="releases-repository-expanded-header-provider-wrapper" gap={5} align="center">
|
||||
<MaskedOrNormalImage
|
||||
className="releases-repository-expanded-header-provider-icon"
|
||||
imageUrl={Providers[repository.providerKey]?.iconUrl}
|
||||
imageUrl={Providers[repository.providerKey].iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
style={{
|
||||
width: "1em",
|
||||
@@ -482,7 +482,7 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
||||
}}
|
||||
/>
|
||||
<Text className="releases-repository-expanded-header-provider-name" size="xs" c="iconColor" ff="monospace">
|
||||
{Providers[repository.providerKey]?.name}
|
||||
{Providers[repository.providerKey].name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
@@ -3,16 +3,7 @@ export interface ReleasesProvider {
|
||||
iconUrl: string;
|
||||
}
|
||||
|
||||
interface ProvidersProps {
|
||||
[key: string]: ReleasesProvider;
|
||||
DockerHub: ReleasesProvider;
|
||||
Github: ReleasesProvider;
|
||||
Gitlab: ReleasesProvider;
|
||||
Npm: ReleasesProvider;
|
||||
Codeberg: ReleasesProvider;
|
||||
}
|
||||
|
||||
export const Providers: ProvidersProps = {
|
||||
export const Providers = {
|
||||
DockerHub: {
|
||||
name: "Docker Hub",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/docker.svg",
|
||||
@@ -33,4 +24,10 @@ export const Providers: ProvidersProps = {
|
||||
name: "Codeberg",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/codeberg.svg",
|
||||
},
|
||||
} as const satisfies Record<string, ReleasesProvider>;
|
||||
|
||||
export type ProviderKey = keyof typeof Providers;
|
||||
|
||||
export const isProviderKey = (key: string): key is ProviderKey => {
|
||||
return key in Providers;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ProviderKey } from "./releases-providers";
|
||||
|
||||
export interface ReleasesVersionFilter {
|
||||
prefix?: string;
|
||||
precision: number;
|
||||
@@ -5,7 +7,7 @@ export interface ReleasesVersionFilter {
|
||||
}
|
||||
|
||||
export interface ReleasesRepository {
|
||||
providerKey: string;
|
||||
providerKey: ProviderKey;
|
||||
identifier: string;
|
||||
name?: string;
|
||||
versionFilter?: ReleasesVersionFilter;
|
||||
|
||||
Generated
+1688
-1579
File diff suppressed because it is too large.
Load diff
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user