Compare commits
63
Commits
No files matched your search
@@ -20,7 +20,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prebuilt debian dependencies
|
||||
uses: ./.github/actions/prebuilt-debian@dev
|
||||
uses: homarr-labs/homarr/.github/actions/prebuilt-debian@dev
|
||||
id: prebuilt-debian
|
||||
with:
|
||||
architecture: ${{ inputs.architecture }}
|
||||
|
||||
@@ -1,3 +1,96 @@
|
||||
## [1.49.0](https://github.com/homarr-labs/homarr/compare/v1.48.0...v1.49.0) (2026-01-02)
|
||||
|
||||
### Features
|
||||
|
||||
* unraid integration ([#4439](https://github.com/homarr-labs/homarr/issues/4439)) ([9b0dd6d](https://github.com/homarr-labs/homarr/commit/9b0dd6d6cebda4a541c8859506d89e9e9f782fa4))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add orphaned status for containers with invalid stats ([#4441](https://github.com/homarr-labs/homarr/issues/4441)) ([f04f537](https://github.com/homarr-labs/homarr/commit/f04f53759b6890d5d2dd683a9bf124d8ff12780b))
|
||||
* **deps:** update dependency @xterm/addon-fit to v0.11.0 ([#4756](https://github.com/homarr-labs/homarr/issues/4756)) ([1c727e5](https://github.com/homarr-labs/homarr/commit/1c727e59af4041e5e41da854633ccd3c030c1d18))
|
||||
* **deps:** update dependency @xterm/xterm to v6 ([#4757](https://github.com/homarr-labs/homarr/issues/4757)) ([fad478c](https://github.com/homarr-labs/homarr/commit/fad478c549cdbc616f921d9b444bff48b59e77a5))
|
||||
* **deps:** update dependency isomorphic-dompurify to ^2.35.0 ([#4779](https://github.com/homarr-labs/homarr/issues/4779)) ([236fa35](https://github.com/homarr-labs/homarr/commit/236fa35f7e0943be33bba153e7030ce4e3bb204e))
|
||||
* **deps:** update dependency jotai to ^2.16.1 ([#4772](https://github.com/homarr-labs/homarr/issues/4772)) ([d002700](https://github.com/homarr-labs/homarr/commit/d00270046a041a10c1c540cfa79c1eee477f3303))
|
||||
* **deps:** update dependency ldapts to v8.0.31 ([#4755](https://github.com/homarr-labs/homarr/issues/4755)) ([8b09693](https://github.com/homarr-labs/homarr/commit/8b096939e4e1da8e98791a8e05f108a136fe2810))
|
||||
* **deps:** update dependency ldapts to v8.0.32 ([#4762](https://github.com/homarr-labs/homarr/issues/4762)) ([49afb82](https://github.com/homarr-labs/homarr/commit/49afb8244615f67af0586aaacf77a509231ac6dc))
|
||||
* **deps:** update dependency ldapts to v8.0.34 ([#4765](https://github.com/homarr-labs/homarr/issues/4765)) ([809bf55](https://github.com/homarr-labs/homarr/commit/809bf559fe1b96df9a903dc790d138cad7368adb))
|
||||
* **deps:** update dependency ldapts to v8.0.35 ([#4768](https://github.com/homarr-labs/homarr/issues/4768)) ([e574dd7](https://github.com/homarr-labs/homarr/commit/e574dd70f1421e9254a289a0f02fd0b4d43e26ad))
|
||||
* **deps:** update dependency typescript-eslint to ^8.50.1 ([#4758](https://github.com/homarr-labs/homarr/issues/4758)) ([adaa803](https://github.com/homarr-labs/homarr/commit/adaa8039dd545109246ccd9fa15570739f755f62))
|
||||
* **deps:** update nextjs monorepo to v16.1.1 ([#4759](https://github.com/homarr-labs/homarr/issues/4759)) ([7712ab4](https://github.com/homarr-labs/homarr/commit/7712ab410b5094e5d84df75461e07efdfccc285b))
|
||||
* **deps:** update tanstack-query monorepo ([#4782](https://github.com/homarr-labs/homarr/issues/4782)) ([1e5d38f](https://github.com/homarr-labs/homarr/commit/1e5d38fd3ca4c68fa4c61b02757a3cff39839fdb))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.11.0 ([#4781](https://github.com/homarr-labs/homarr/issues/4781)) ([e40a116](https://github.com/homarr-labs/homarr/commit/e40a11608cb21a75e6910028abf97e14b5275f56))
|
||||
* **deps:** update turbo monorepo to ^2.7.2 ([#4764](https://github.com/homarr-labs/homarr/issues/4764)) ([9191430](https://github.com/homarr-labs/homarr/commit/919143022969350f6ee7be2f53be2fe09a89bba3))
|
||||
|
||||
## [1.48.0](https://github.com/homarr-labs/homarr/compare/v1.47.0...v1.48.0) (2025-12-26)
|
||||
|
||||
### Features
|
||||
|
||||
* enable dns caching by default ([#4706](https://github.com/homarr-labs/homarr/issues/4706)) ([fbb5298](https://github.com/homarr-labs/homarr/commit/fbb5298cb05b12eb2f19c121a1bc103caeb3e576))
|
||||
* **http:** add proxy support ([#4721](https://github.com/homarr-labs/homarr/issues/4721)) ([5a57115](https://github.com/homarr-labs/homarr/commit/5a57115ca073aea1c75a000fda6a23648c5488e4))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.10 ([#4710](https://github.com/homarr-labs/homarr/issues/4710)) ([689523a](https://github.com/homarr-labs/homarr/commit/689523a194ab48bdc072b1cdf65f07ade6cbc584))
|
||||
* **deps:** update dependency ldapts to v8.0.24 ([#4711](https://github.com/homarr-labs/homarr/issues/4711)) ([648f7e2](https://github.com/homarr-labs/homarr/commit/648f7e2c7743af81f4bced36b7e1e80c21e851e5))
|
||||
* **deps:** update dependency ldapts to v8.0.25 ([#4724](https://github.com/homarr-labs/homarr/issues/4724)) ([ef12b2b](https://github.com/homarr-labs/homarr/commit/ef12b2b5c2e1131138937fb6da61ce4c167fe0eb))
|
||||
* **deps:** update dependency ldapts to v8.0.27 ([#4737](https://github.com/homarr-labs/homarr/issues/4737)) ([8891ac4](https://github.com/homarr-labs/homarr/commit/8891ac42399e47fda6efc657b8ff2fe58aebca9e))
|
||||
* **deps:** update dependency ldapts to v8.0.28 ([#4743](https://github.com/homarr-labs/homarr/issues/4743)) ([75c5453](https://github.com/homarr-labs/homarr/commit/75c545344aae39550917331458c01a894b6a942f))
|
||||
* **deps:** update dependency ldapts to v8.0.29 ([#4747](https://github.com/homarr-labs/homarr/issues/4747)) ([5dd7400](https://github.com/homarr-labs/homarr/commit/5dd74006843ae72f63e631b14f0fd50c911d610b))
|
||||
* **deps:** update dependency ldapts to v8.0.30 ([#4749](https://github.com/homarr-labs/homarr/issues/4749)) ([96c2c0d](https://github.com/homarr-labs/homarr/commit/96c2c0d604737c64722fa21bf79965c806bf4da3))
|
||||
* **deps:** update dependency mysql2 to v3.16.0 ([#4731](https://github.com/homarr-labs/homarr/issues/4731)) ([f34bdce](https://github.com/homarr-labs/homarr/commit/f34bdce6446d4e62b54b96f63e91cfdddd51e901))
|
||||
* **deps:** update dependency next-intl to v4.6.1 ([#4730](https://github.com/homarr-labs/homarr/issues/4730)) ([f4d782a](https://github.com/homarr-labs/homarr/commit/f4d782abefb78e085ca3fc66034c0ca54fb21120))
|
||||
* **deps:** update dependency sass to ^1.97.0 ([#4726](https://github.com/homarr-labs/homarr/issues/4726)) ([88baad8](https://github.com/homarr-labs/homarr/commit/88baad8143f4ca830e50644d6bd1d1980ef1da82))
|
||||
* **deps:** update dependency sass to ^1.97.1 ([#4746](https://github.com/homarr-labs/homarr/issues/4746)) ([6b774f4](https://github.com/homarr-labs/homarr/commit/6b774f47d0a8bf731e7e1837a9d47e0dc53a1ff0))
|
||||
* **deps:** update dependency typescript-eslint to ^8.50.0 ([#4720](https://github.com/homarr-labs/homarr/issues/4720)) ([2dc8fea](https://github.com/homarr-labs/homarr/commit/2dc8feae7ea6174a1d826d8f0151ddc2c04c6aa6))
|
||||
* **deps:** update dependency zod to ^4.2.0 ([#4712](https://github.com/homarr-labs/homarr/issues/4712)) ([6e39524](https://github.com/homarr-labs/homarr/commit/6e39524f9d28d201c13738212d238726e1685832))
|
||||
* **deps:** update dependency zod to ^4.2.1 ([#4722](https://github.com/homarr-labs/homarr/issues/4722)) ([68dc684](https://github.com/homarr-labs/homarr/commit/68dc6844fdee53aca55b44dd7d19bf9d3a35c057))
|
||||
* **deps:** update nextjs monorepo to v16.1.0 ([#4739](https://github.com/homarr-labs/homarr/issues/4739)) ([99f2842](https://github.com/homarr-labs/homarr/commit/99f2842a5a102056c76ddc68cb5083791f324c3c))
|
||||
* **deps:** update tiptap monorepo to v3.14.0 ([#4744](https://github.com/homarr-labs/homarr/issues/4744)) ([adbe707](https://github.com/homarr-labs/homarr/commit/adbe707c73f462771c3d21cbbcc21e8245eea6f3))
|
||||
* **deps:** update trpc monorepo to ^11.8.1 ([#4750](https://github.com/homarr-labs/homarr/issues/4750)) ([93bf9ab](https://github.com/homarr-labs/homarr/commit/93bf9ab84b5387eefb8cc0b03057a4ccf0b13aa9))
|
||||
* **deps:** update turbo monorepo to ^2.7.0 ([#4745](https://github.com/homarr-labs/homarr/issues/4745)) ([5d659a3](https://github.com/homarr-labs/homarr/commit/5d659a3ebbf79266158cb57236de90e7883e7a4e))
|
||||
* **deps:** update turbo monorepo to ^2.7.1 ([#4751](https://github.com/homarr-labs/homarr/issues/4751)) ([d8db016](https://github.com/homarr-labs/homarr/commit/d8db0169a142c2ec302806fe946cb17431ccae6b))
|
||||
|
||||
## [1.47.0](https://github.com/homarr-labs/homarr/compare/v1.46.0...v1.47.0) (2025-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* [#4452](https://github.com/homarr-labs/homarr/issues/4452) improve calendar design ([#4461](https://github.com/homarr-labs/homarr/issues/4461)) ([08e9f7d](https://github.com/homarr-labs/homarr/commit/08e9f7d8d85475788534b46e6cceb40a7231c09d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **boards:** selection menu not shown to users without change access ([#4689](https://github.com/homarr-labs/homarr/issues/4689)) ([a0a11e3](https://github.com/homarr-labs/homarr/commit/a0a11e35707513fee45551e09c26ff633f636c32))
|
||||
* **deps:** update dependency dns-caching to ^0.2.9 ([#4648](https://github.com/homarr-labs/homarr/issues/4648)) ([6deab2e](https://github.com/homarr-labs/homarr/commit/6deab2ecec2dc4f5192b53213cb755a9853eb7c0))
|
||||
* **deps:** update dependency drizzle-orm to ^0.45.1 ([#4663](https://github.com/homarr-labs/homarr/issues/4663)) ([f635009](https://github.com/homarr-labs/homarr/commit/f63500961f11589a972a41dde9e4117b1f25141c))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.3 ([#4683](https://github.com/homarr-labs/homarr/issues/4683)) ([611b65e](https://github.com/homarr-labs/homarr/commit/611b65e8bd420aaad580bf35071b651093623c95))
|
||||
* **deps:** update dependency isomorphic-dompurify to ^2.34.0 ([#4650](https://github.com/homarr-labs/homarr/issues/4650)) ([5d5c18b](https://github.com/homarr-labs/homarr/commit/5d5c18b3649d5fa1b2b933d443ccd5abcbb84e3a))
|
||||
* **deps:** update dependency jotai to ^2.16.0 ([#4655](https://github.com/homarr-labs/homarr/issues/4655)) ([4aef85b](https://github.com/homarr-labs/homarr/commit/4aef85ba60f4978da5a0b13c08e1799949fe4497))
|
||||
* **deps:** update dependency ldapts to v8.0.16 ([#4640](https://github.com/homarr-labs/homarr/issues/4640)) ([ada5597](https://github.com/homarr-labs/homarr/commit/ada5597cf3ab0bafb04e9176b431d257b36d1dc8))
|
||||
* **deps:** update dependency ldapts to v8.0.17 ([#4642](https://github.com/homarr-labs/homarr/issues/4642)) ([c09da85](https://github.com/homarr-labs/homarr/commit/c09da85f3a8450a70991ff0656228a9aa1364f89))
|
||||
* **deps:** update dependency ldapts to v8.0.18 ([#4653](https://github.com/homarr-labs/homarr/issues/4653)) ([4bd142e](https://github.com/homarr-labs/homarr/commit/4bd142e09c666cc84c67c93e2202e571565b766c))
|
||||
* **deps:** update dependency ldapts to v8.0.19 ([#4669](https://github.com/homarr-labs/homarr/issues/4669)) ([8ed771d](https://github.com/homarr-labs/homarr/commit/8ed771d30073809b08a6ace2c29b3de2675e37d1))
|
||||
* **deps:** update dependency ldapts to v8.0.20 ([#4684](https://github.com/homarr-labs/homarr/issues/4684)) ([703c8cc](https://github.com/homarr-labs/homarr/commit/703c8ccc060bb1775a72f405da1e169926b1ce5d))
|
||||
* **deps:** update dependency ldapts to v8.0.21 ([#4695](https://github.com/homarr-labs/homarr/issues/4695)) ([7f6eaa0](https://github.com/homarr-labs/homarr/commit/7f6eaa0b1f397a37b6acd15e0ccfea75ee2e0161))
|
||||
* **deps:** update dependency ldapts to v8.0.22 ([#4700](https://github.com/homarr-labs/homarr/issues/4700)) ([391d725](https://github.com/homarr-labs/homarr/commit/391d725191563ba5048593c14baf077a924cea44))
|
||||
* **deps:** update dependency ldapts to v8.0.23 ([#4701](https://github.com/homarr-labs/homarr/issues/4701)) ([db337b2](https://github.com/homarr-labs/homarr/commit/db337b2525762c96acd815bd4fdacfaabff5f0cf))
|
||||
* **deps:** update dependency next-intl to v4.6.0 ([#4691](https://github.com/homarr-labs/homarr/issues/4691)) ([1ff6fac](https://github.com/homarr-labs/homarr/commit/1ff6fac71bb9187d0eb703a7b6543daf8861efe8))
|
||||
* **deps:** update dependency sass to ^1.94.3 ([#4646](https://github.com/homarr-labs/homarr/issues/4646)) ([7e91d12](https://github.com/homarr-labs/homarr/commit/7e91d12439826eee76f118448c583294c3c46b42))
|
||||
* **deps:** update dependency sass to ^1.95.0 ([#4647](https://github.com/homarr-labs/homarr/issues/4647)) ([eb760da](https://github.com/homarr-labs/homarr/commit/eb760da8b30583575fb638995fc2f2acbd5b2961))
|
||||
* **deps:** update dependency sass to ^1.95.1 ([#4661](https://github.com/homarr-labs/homarr/issues/4661)) ([e327c66](https://github.com/homarr-labs/homarr/commit/e327c660b239840c4929f536eff3096342cc3433))
|
||||
* **deps:** update dependency sass to ^1.96.0 ([#4668](https://github.com/homarr-labs/homarr/issues/4668)) ([bdfc150](https://github.com/homarr-labs/homarr/commit/bdfc1501152450a1546962331037a6fc1c782e4e))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.31.0 ([#4672](https://github.com/homarr-labs/homarr/issues/4672)) ([4d593f5](https://github.com/homarr-labs/homarr/commit/4d593f5b9d1f47ae2368f42131e8ef8d32f73fc9))
|
||||
* **deps:** update dependency typescript-eslint to ^8.49.0 ([#4645](https://github.com/homarr-labs/homarr/issues/4645)) ([220cb66](https://github.com/homarr-labs/homarr/commit/220cb669a13fead953ff42040b39e06d1dcc1fe2))
|
||||
* **deps:** update mantine monorepo to ^8.3.10 ([#4651](https://github.com/homarr-labs/homarr/issues/4651)) ([d0652f7](https://github.com/homarr-labs/homarr/commit/d0652f7cf5ac0e63a61adcfe43e53c4ed130e8fc))
|
||||
* **deps:** update react monorepo to v19.2.2 ([#4676](https://github.com/homarr-labs/homarr/issues/4676)) ([82e79f2](https://github.com/homarr-labs/homarr/commit/82e79f26274a289e1ce4f9331b6ba16f00f5a86c))
|
||||
* **deps:** update react monorepo to v19.2.3 ([#4680](https://github.com/homarr-labs/homarr/issues/4680)) ([4c71856](https://github.com/homarr-labs/homarr/commit/4c7185646d9d061d1aebf9194f9ef2f810fc3384))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.10.0 ([#4659](https://github.com/homarr-labs/homarr/issues/4659)) ([c880ce9](https://github.com/homarr-labs/homarr/commit/c880ce9bd99a7056f20d450275fcbc37f9372f63))
|
||||
* **deps:** update trpc monorepo to ^11.8.0 ([#4697](https://github.com/homarr-labs/homarr/issues/4697)) ([1f44845](https://github.com/homarr-labs/homarr/commit/1f44845df489bb33bcfe943c6a05c0f157037ed6))
|
||||
* **downloads:** improve progress to only show 100 percent if fully completed ([#4621](https://github.com/homarr-labs/homarr/issues/4621)) ([1062745](https://github.com/homarr-labs/homarr/commit/10627453d5b5a17991bbe9b8d4bc23981b366c39))
|
||||
* **iframe-widget:** permission selection not applying to any origin ([#4616](https://github.com/homarr-labs/homarr/issues/4616)) ([479f083](https://github.com/homarr-labs/homarr/commit/479f083ab8ec405d1b8296b32a20262f6192a1f9))
|
||||
* **invites:** creation with custom expiration not working ([#4677](https://github.com/homarr-labs/homarr/issues/4677)) ([423da9e](https://github.com/homarr-labs/homarr/commit/423da9eeb88510a123bdd2447dc97b54c5f1dec4))
|
||||
* reference to prebuilt-debian action does not work ([47d7458](https://github.com/homarr-labs/homarr/commit/47d7458143d8801947cae1985745fa827b247dd1))
|
||||
* **stock-price:** use previous-close for change calculation ([#4674](https://github.com/homarr-labs/homarr/issues/4674)) ([17fc7a0](https://github.com/homarr-labs/homarr/commit/17fc7a043b4adbed9f14408e23c95ec5fba0c8b8))
|
||||
* **tdarr:** array parse error ([#4678](https://github.com/homarr-labs/homarr/issues/4678)) ([d86af07](https://github.com/homarr-labs/homarr/commit/d86af072bf136d76b02f3efa2c15520a4f63974e))
|
||||
|
||||
## [1.46.0](https://github.com/homarr-labs/homarr/compare/v1.45.3...v1.46.0) (2025-12-12)
|
||||
|
||||
### Features
|
||||
|
||||
+15
-14
@@ -55,18 +55,17 @@
|
||||
"@mantine/modals": "^8.3.10",
|
||||
"@mantine/tiptap": "^8.3.10",
|
||||
"@million/lint": "1.0.14",
|
||||
"@scalar/api-reference-react": "^0.8.11",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-query-devtools": "^5.91.1",
|
||||
"@tanstack/react-query": "^5.90.14",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||
"@trpc/client": "^11.8.0",
|
||||
"@trpc/next": "^11.8.0",
|
||||
"@trpc/react-query": "^11.8.0",
|
||||
"@trpc/server": "^11.8.0",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/next": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"chroma-js": "^3.2.0",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -74,20 +73,21 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^13.0.0",
|
||||
"isomorphic-dompurify": "^2.34.0",
|
||||
"jotai": "^2.16.0",
|
||||
"isomorphic-dompurify": "^2.35.0",
|
||||
"jotai": "^2.16.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.96.0",
|
||||
"sass": "^1.97.1",
|
||||
"superjson": "2.2.6",
|
||||
"swagger-ui-react": "^5.31.0",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -98,6 +98,7 @@
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.39.2",
|
||||
"node-loader": "^2.1.0",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import type { OpenAPIObject } from "openapi3-ts/oas31";
|
||||
import SwaggerUI from "swagger-ui-react";
|
||||
|
||||
// workaround for CSS that cannot be processed by next.js, https://github.com/swagger-api/swagger-ui/issues/10045
|
||||
import "../swagger-ui-dark.css";
|
||||
import "../swagger-ui-overrides.css";
|
||||
import "../swagger-ui.css";
|
||||
|
||||
interface SwaggerUIClientProps {
|
||||
document: OpenAPIObject;
|
||||
}
|
||||
|
||||
export const SwaggerUIClient = ({ document }: SwaggerUIClientProps) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const requestInterceptor = (req: Record<string, any>) => {
|
||||
req.credentials = "omit";
|
||||
return req;
|
||||
};
|
||||
|
||||
return <SwaggerUI requestInterceptor={requestInterceptor} spec={document} />;
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { headers } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from "@mantine/core";
|
||||
import { ApiReferenceReact } from "@scalar/api-reference-react";
|
||||
|
||||
import { openApiDocument } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -9,14 +8,11 @@ import { auth } from "@homarr/auth/next";
|
||||
import { extractBaseUrlFromHeaders } from "@homarr/common";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { SwaggerUIClient } from "~/app/[locale]/manage/tools/api/components/swagger-ui";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { ApiKeysManagement } from "./components/api-keys";
|
||||
|
||||
import "@scalar/api-reference-react/style.css";
|
||||
|
||||
import { Metadata } from "next";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
export async function generateMetadata() {
|
||||
const session = await auth();
|
||||
if (!session?.user || !session.user.permissions.includes("admin")) {
|
||||
return {};
|
||||
@@ -49,21 +45,7 @@ export default async function ApiPage() {
|
||||
<ApiKeysManagement apiKeys={apiKeys} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value={"documentation"}>
|
||||
<ApiReferenceReact
|
||||
configuration={{
|
||||
content: document,
|
||||
layout: "classic",
|
||||
showSidebar: false,
|
||||
hideDarkModeToggle: true,
|
||||
showDeveloperTools: "never",
|
||||
hiddenClients: true,
|
||||
darkMode: session.user.colorScheme === "dark",
|
||||
authentication: {
|
||||
preferredSecurityScheme: "apikey",
|
||||
},
|
||||
hideSearch: true,
|
||||
}}
|
||||
/>
|
||||
<SwaggerUIClient document={document} />
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
.swagger-ui .info {
|
||||
margin: 0 !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -76,6 +76,7 @@ const createColumns = (
|
||||
accessorKey: "ports",
|
||||
header: t("docker.field.ports.label"),
|
||||
Cell({ cell }) {
|
||||
if (!cell.row.original.ports.length) return null;
|
||||
return (
|
||||
<OverflowBadge overflowCount={1} data={cell.row.original.ports.map((port) => port.PrivatePort.toString())} />
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^24.10.4",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.7.4",
|
||||
"tsx": "4.20.4",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.7.4",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.5.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.25.0",
|
||||
"packageManager": "pnpm@10.26.2",
|
||||
"engines": {
|
||||
"node": ">=24.12.0",
|
||||
"pnpm": ">=10.25.0"
|
||||
"pnpm": ">=10.26.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
|
||||
@@ -314,6 +314,13 @@
|
||||
<br/>
|
||||
<p align="center">Unifi<br/>Controller</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/unraid" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg" alt="Unraid" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Unraid</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+16
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.46.0",
|
||||
"version": "1.49.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -42,27 +42,27 @@
|
||||
"@semantic-release/github": "^12.0.2",
|
||||
"@semantic-release/npm": "^13.1.3",
|
||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||
"@testcontainers/redis": "^11.10.0",
|
||||
"@turbo/gen": "^2.6.3",
|
||||
"@testcontainers/redis": "^11.11.0",
|
||||
"@turbo/gen": "^2.7.2",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"@vitest/coverage-v8": "^4.0.15",
|
||||
"@vitest/ui": "^4.0.15",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"@vitest/ui": "^4.0.16",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"jsdom": "^27.3.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"json5": "^2.2.3",
|
||||
"prettier": "^3.7.4",
|
||||
"semantic-release": "^25.0.2",
|
||||
"testcontainers": "^11.10.0",
|
||||
"turbo": "^2.6.3",
|
||||
"testcontainers": "^11.11.0",
|
||||
"turbo": "^2.7.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^4.0.15"
|
||||
"vite-tsconfig-paths": "^6.0.3",
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"packageManager": "pnpm@10.25.0",
|
||||
"packageManager": "pnpm@10.26.2",
|
||||
"engines": {
|
||||
"node": ">=24.12.0",
|
||||
"pnpm": ">=10.25.0"
|
||||
"pnpm": ">=10.26.2"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -82,20 +82,20 @@
|
||||
"axios@>=1.0.0 <1.8.2": ">=1.13.2",
|
||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
|
||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||
"esbuild@<=0.24.2": ">=0.27.1",
|
||||
"esbuild@<=0.24.2": ">=0.27.2",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||
"hono@<4.6.5": ">=4.11.0",
|
||||
"hono@<4.6.5": ">=4.11.3",
|
||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
||||
"prismjs@<1.30.0": ">=1.30.0",
|
||||
"proxmox-api>undici": "7.16.0",
|
||||
"react-is": "^19.2.3",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.53.3",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.54.0",
|
||||
"sha.js@<=2.4.11": ">=2.4.12",
|
||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
|
||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
|
||||
"tmp@<=0.2.3": ">=0.2.5",
|
||||
"vite@>=5.0.0 <=5.4.18": ">=7.2.7"
|
||||
"vite@>=5.0.0 <=5.4.18": ">=7.3.0"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||
|
||||
@@ -41,18 +41,18 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@trpc/client": "^11.8.0",
|
||||
"@trpc/react-query": "^11.8.0",
|
||||
"@trpc/server": "^11.8.0",
|
||||
"@trpc/tanstack-react-query": "^11.8.0",
|
||||
"@tanstack/react-query": "^5.90.14",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@trpc/tanstack-react-query": "^11.8.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"superjson": "2.2.6",
|
||||
"trpc-to-openapi": "^3.1.0",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -11,7 +11,6 @@ import { importRouter } from "./router/import/import-router";
|
||||
import { infoRouter } from "./router/info";
|
||||
import { integrationRouter } from "./router/integration/integration-router";
|
||||
import { inviteRouter } from "./router/invite";
|
||||
import { itemsRouter } from "./router/item/item-router";
|
||||
import { kubernetesRouter } from "./router/kubernetes/router/kubernetes-router";
|
||||
import { locationRouter } from "./router/location";
|
||||
import { logRouter } from "./router/log";
|
||||
@@ -50,7 +49,6 @@ export const appRouter = createTRPCRouter({
|
||||
updateChecker: updateCheckerRouter,
|
||||
certificates: certificateRouter,
|
||||
info: infoRouter,
|
||||
item: itemsRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -17,7 +17,6 @@ export const throwIfActionForbiddenAsync = async (
|
||||
ctx: { db: Database; session: Session | null },
|
||||
boardWhere: SQL<unknown>,
|
||||
permission: BoardPermission,
|
||||
error?: TRPCError,
|
||||
) => {
|
||||
const { db, session } = ctx;
|
||||
const groupsOfCurrentUser = await db.query.groupMembers.findMany({
|
||||
@@ -41,7 +40,7 @@ export const throwIfActionForbiddenAsync = async (
|
||||
});
|
||||
|
||||
if (!board) {
|
||||
notAllowed(error);
|
||||
notAllowed();
|
||||
}
|
||||
|
||||
const { hasViewAccess, hasChangeAccess, hasFullAccess } = constructBoardPermissions(board, session);
|
||||
@@ -58,18 +57,14 @@ export const throwIfActionForbiddenAsync = async (
|
||||
return; // As view access is required and user has view access, allow
|
||||
}
|
||||
|
||||
notAllowed(error);
|
||||
notAllowed();
|
||||
};
|
||||
|
||||
/**
|
||||
* This method returns NOT_FOUND to prevent snooping on board existence
|
||||
* A function is used to use the method without return statement
|
||||
*/
|
||||
function notAllowed(error?: TRPCError): never {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
function notAllowed(): never {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Board not found",
|
||||
|
||||
@@ -6,8 +6,7 @@ import type { Database } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db";
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
|
||||
import { selectGroupSchema, selectUserSchema } from "@homarr/db/validationSchemas";
|
||||
import { everyoneGroup, groupPermissionKeys } from "@homarr/definitions";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
import {
|
||||
groupCreateSchema,
|
||||
@@ -23,65 +22,33 @@ import { throwIfCredentialsDisabled } from "./invite/checks";
|
||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||
|
||||
export const groupRouter = createTRPCRouter({
|
||||
getAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(z.void())
|
||||
.output(
|
||||
z.array(
|
||||
selectGroupSchema.and(
|
||||
z.object({ members: z.array(selectUserSchema.pick({ id: true, name: true, email: true, image: true })) }),
|
||||
),
|
||||
),
|
||||
)
|
||||
.meta({
|
||||
openapi: { method: "GET", path: "/api/groups", tags: ["groups"], protect: true, summary: "Retrieve all groups" },
|
||||
})
|
||||
.query(async ({ ctx }) => {
|
||||
const dbGroups = await ctx.db.query.groups.findMany({
|
||||
with: {
|
||||
members: {
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
},
|
||||
getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
||||
const dbGroups = await ctx.db.query.groups.findMany({
|
||||
with: {
|
||||
members: {
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return dbGroups.map((group) => ({
|
||||
...group,
|
||||
members: group.members.map((member) => member.user),
|
||||
}));
|
||||
}),
|
||||
return dbGroups.map((group) => ({
|
||||
...group,
|
||||
members: group.members.map((member) => member.user),
|
||||
}));
|
||||
}),
|
||||
|
||||
getPaginated: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(paginatedSchema)
|
||||
.output(
|
||||
z.object({
|
||||
items: z.array(
|
||||
selectGroupSchema.and(
|
||||
z.object({ members: z.array(selectUserSchema.pick({ id: true, name: true, email: true, image: true })) }),
|
||||
),
|
||||
),
|
||||
totalCount: z.number(),
|
||||
}),
|
||||
)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/api/groups/paginated",
|
||||
tags: ["groups"],
|
||||
protect: true,
|
||||
summary: "Retrieve groups with pagination",
|
||||
},
|
||||
})
|
||||
.query(async ({ input, ctx }) => {
|
||||
const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined;
|
||||
const groupCount = await ctx.db.$count(groups, whereQuery);
|
||||
@@ -117,24 +84,6 @@ export const groupRouter = createTRPCRouter({
|
||||
getById: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(byIdSchema)
|
||||
.output(
|
||||
selectGroupSchema.and(
|
||||
z.object({
|
||||
owner: selectUserSchema.pick({ id: true, name: true, image: true, email: true }).nullable(),
|
||||
members: z.array(selectUserSchema.pick({ id: true, name: true, email: true, image: true, provider: true })),
|
||||
permissions: z.array(z.enum(groupPermissionKeys)),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/api/groups/{id}",
|
||||
tags: ["groups"],
|
||||
protect: true,
|
||||
summary: "Retrieve group details",
|
||||
},
|
||||
})
|
||||
.query(async ({ input, ctx }) => {
|
||||
const group = await ctx.db.query.groups.findFirst({
|
||||
where: eq(groups.id, input.id),
|
||||
@@ -252,10 +201,6 @@ export const groupRouter = createTRPCRouter({
|
||||
createGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(groupCreateSchema)
|
||||
.output(z.string())
|
||||
.meta({
|
||||
openapi: { method: "POST", path: "/api/groups", tags: ["groups"], protect: true, summary: "Create group" },
|
||||
})
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
||||
|
||||
@@ -274,10 +219,6 @@ export const groupRouter = createTRPCRouter({
|
||||
updateGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(groupUpdateSchema)
|
||||
.output(z.void())
|
||||
.meta({
|
||||
openapi: { method: "PUT", path: "/api/groups/{id}", tags: ["groups"], protect: true, summary: "Update group" },
|
||||
})
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||
@@ -362,10 +303,6 @@ export const groupRouter = createTRPCRouter({
|
||||
deleteGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(byIdSchema)
|
||||
.output(z.void())
|
||||
.meta({
|
||||
openapi: { method: "DELETE", path: "/api/groups/{id}", tags: ["groups"], protect: true, summary: "Delete group" },
|
||||
})
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||
@@ -375,16 +312,6 @@ export const groupRouter = createTRPCRouter({
|
||||
addMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(groupUserSchema)
|
||||
.output(z.void())
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "POST",
|
||||
path: "/api/groups/{groupId}/members/{userId}",
|
||||
tags: ["groups"],
|
||||
protect: true,
|
||||
summary: "Add member to group",
|
||||
},
|
||||
})
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
@@ -401,8 +328,6 @@ export const groupRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Create another pr to only allow adding users with provider=credentials
|
||||
|
||||
await ctx.db.insert(groupMembers).values({
|
||||
groupId: input.groupId,
|
||||
userId: input.userId,
|
||||
@@ -411,16 +336,6 @@ export const groupRouter = createTRPCRouter({
|
||||
removeMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(groupUserSchema)
|
||||
.output(z.void())
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "DELETE",
|
||||
path: "/api/groups/{groupId}/members/{userId}",
|
||||
tags: ["groups"],
|
||||
protect: true,
|
||||
summary: "Remove member from group",
|
||||
},
|
||||
})
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import SuperJSON from "superjson";
|
||||
import z from "zod";
|
||||
|
||||
import { createId, objectEntries } from "@homarr/common";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { and, eq, handleTransactionsAsync } from "@homarr/db";
|
||||
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
|
||||
import { boards, itemLayouts, items } from "@homarr/db/schema";
|
||||
import { selectItemLayoutSchema, selectitemSchema } from "@homarr/db/validationSchemas";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { widgetKinds } from "@homarr/definitions";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||
import { itemAdvancedOptionsSchema } from "@homarr/validation/shared";
|
||||
|
||||
import { reduceWidgetOptionsWithDefaultValues, widgetImports } from "../../../../widgets/src";
|
||||
import type { WidgetOptionDefinition } from "../../../../widgets/src/options";
|
||||
import { convertIntersectionToZodObject } from "../../schema-merger";
|
||||
import { createTRPCRouter, protectedProcedure } from "../../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "../board/board-access";
|
||||
|
||||
const createItemOptionsSchema = (kind: WidgetKind): z.ZodObject =>
|
||||
z.object(
|
||||
objectEntries(
|
||||
widgetImports[kind].definition.createOptions({
|
||||
enableStatusByDefault: true,
|
||||
firstDayOfWeek: 1,
|
||||
forceDisableStatus: false,
|
||||
}),
|
||||
).reduce(
|
||||
(previous, [key, value]: [string, WidgetOptionDefinition]) => {
|
||||
previous[key] = value.validate;
|
||||
return previous;
|
||||
},
|
||||
{} as Record<string, z.ZodType>,
|
||||
),
|
||||
);
|
||||
|
||||
const outputItemSchema = z.union([
|
||||
...widgetKinds.map((kind) =>
|
||||
z
|
||||
.object({
|
||||
kind: z.literal(kind),
|
||||
options: createItemOptionsSchema(kind),
|
||||
advancedOptions: itemAdvancedOptionsSchema,
|
||||
layouts: z.array(selectItemLayoutSchema.omit({ itemId: true })),
|
||||
})
|
||||
.and(
|
||||
selectitemSchema.pick({
|
||||
id: true,
|
||||
boardId: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
const manageItemSchema = z.object({
|
||||
boardId: z.string(),
|
||||
kind: zodEnumFromArray(widgetKinds),
|
||||
options: z.record(z.string(), z.unknown().optional()),
|
||||
advancedOptions: itemAdvancedOptionsSchema,
|
||||
layouts: z.array(selectItemLayoutSchema.omit({ itemId: true })),
|
||||
});
|
||||
|
||||
export const itemsRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
boardId: z.string(),
|
||||
}),
|
||||
)
|
||||
.output(z.array(outputItemSchema))
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/api/boards/{boardId}/items",
|
||||
tags: ["items"],
|
||||
protect: true,
|
||||
summary: "Retrieve all items for board",
|
||||
},
|
||||
})
|
||||
.query(async ({ ctx, input }) => {
|
||||
const board = await ctx.db.query.boards.findFirst({
|
||||
where: (fields, { eq }) => eq(fields.id, input.boardId),
|
||||
with: {
|
||||
items: {
|
||||
with: {
|
||||
layouts: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const error = new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Board not found",
|
||||
});
|
||||
|
||||
if (!board) throw error;
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.boardId), "view", error);
|
||||
|
||||
return board.items.map((item) => ({
|
||||
...item,
|
||||
options: reduceWidgetOptionsWithDefaultValues(
|
||||
item.kind,
|
||||
{
|
||||
enableStatusByDefault: true,
|
||||
firstDayOfWeek: 1,
|
||||
forceDisableStatus: false,
|
||||
},
|
||||
SuperJSON.parse(item.options),
|
||||
),
|
||||
advancedOptions: itemAdvancedOptionsSchema.parse(SuperJSON.parse(item.advancedOptions)),
|
||||
layouts: item.layouts.map(({ itemId: _, ...layout }) => layout),
|
||||
}));
|
||||
}),
|
||||
createItem: protectedProcedure
|
||||
.input(manageItemSchema)
|
||||
.output(outputItemSchema)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "POST",
|
||||
path: "/api/items",
|
||||
tags: ["items"],
|
||||
protect: true,
|
||||
summary: "Create item on board",
|
||||
description: "Options available can be viewed in response options object. Input options are optional",
|
||||
},
|
||||
})
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const board = await ctx.db.query.boards.findFirst({
|
||||
where: (fields, { eq }) => eq(fields.id, input.boardId),
|
||||
});
|
||||
|
||||
const error = new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Board not found",
|
||||
});
|
||||
|
||||
if (!board) throw error;
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, board.id), "modify", error);
|
||||
|
||||
const itemOptionsSchema = createItemOptionsSchema(input.kind);
|
||||
const fullOptions = reduceWidgetOptionsWithDefaultValues(
|
||||
input.kind,
|
||||
{ enableStatusByDefault: true, firstDayOfWeek: 1, forceDisableStatus: false },
|
||||
input.options,
|
||||
);
|
||||
const result = await itemOptionsSchema.safeParseAsync(fullOptions);
|
||||
if (!result.success) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Failed to parse item options",
|
||||
cause: result.error,
|
||||
});
|
||||
}
|
||||
|
||||
const item = {
|
||||
id: createId(),
|
||||
boardId: input.boardId,
|
||||
kind: input.kind,
|
||||
advancedOptions: input.advancedOptions,
|
||||
options: result.data,
|
||||
};
|
||||
|
||||
const itemInsert: InferInsertModel<typeof items> = {
|
||||
...item,
|
||||
advancedOptions: SuperJSON.stringify(item.advancedOptions),
|
||||
options: SuperJSON.stringify(item.options),
|
||||
};
|
||||
const layoutInserts: InferInsertModel<typeof itemLayouts>[] = input.layouts.map((layout) => ({
|
||||
...layout,
|
||||
itemId: item.id,
|
||||
}));
|
||||
|
||||
const insertCollection = createDbInsertCollectionWithoutTransaction(["items", "itemLayouts"]);
|
||||
insertCollection.items.push(itemInsert);
|
||||
insertCollection.itemLayouts.push(...layoutInserts);
|
||||
await insertCollection.insertAllAsync(ctx.db);
|
||||
|
||||
return {
|
||||
...item,
|
||||
layouts: layoutInserts,
|
||||
};
|
||||
}),
|
||||
getItem: protectedProcedure
|
||||
.input(byIdSchema)
|
||||
.output(outputItemSchema)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/api/items/{id}",
|
||||
tags: ["items"],
|
||||
protect: true,
|
||||
summary: "Retrieve item from a board",
|
||||
},
|
||||
})
|
||||
.query(async ({ ctx, input }) => {
|
||||
const item = await ctx.db.query.items.findFirst({
|
||||
where: (fields, { eq }) => eq(fields.id, input.id),
|
||||
with: {
|
||||
layouts: true,
|
||||
},
|
||||
});
|
||||
|
||||
const error = new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Item not found",
|
||||
});
|
||||
|
||||
if (!item) throw error;
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "view", error);
|
||||
|
||||
return {
|
||||
...item,
|
||||
options: reduceWidgetOptionsWithDefaultValues(
|
||||
item.kind,
|
||||
{
|
||||
enableStatusByDefault: true,
|
||||
firstDayOfWeek: 1,
|
||||
forceDisableStatus: false,
|
||||
},
|
||||
SuperJSON.parse(item.options),
|
||||
),
|
||||
advancedOptions: itemAdvancedOptionsSchema.parse(SuperJSON.parse(item.advancedOptions)),
|
||||
layouts: item.layouts.map(({ itemId: _, ...layout }) => layout),
|
||||
};
|
||||
}),
|
||||
updateItem: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(manageItemSchema.omit({ boardId: true, kind: true }).and(byIdSchema)))
|
||||
.output(outputItemSchema)
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "PUT",
|
||||
path: "/api/items/{id}",
|
||||
tags: ["items"],
|
||||
protect: true,
|
||||
summary: "Update an item on the board",
|
||||
},
|
||||
})
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const item = await ctx.db.query.items.findFirst({
|
||||
where: (fields, { eq }) => eq(fields.id, input.id),
|
||||
with: {
|
||||
layouts: true,
|
||||
},
|
||||
});
|
||||
|
||||
const error = new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Item not found",
|
||||
});
|
||||
if (!item) throw error;
|
||||
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "modify", error);
|
||||
|
||||
const previousOptions = SuperJSON.parse<Record<string, unknown>>(item.options);
|
||||
const combinedOptions = {
|
||||
...previousOptions,
|
||||
...input.options,
|
||||
};
|
||||
const itemOptionsSchema = createItemOptionsSchema(item.kind);
|
||||
const fullOptions = reduceWidgetOptionsWithDefaultValues(
|
||||
item.kind,
|
||||
{ enableStatusByDefault: true, firstDayOfWeek: 1, forceDisableStatus: false },
|
||||
combinedOptions,
|
||||
);
|
||||
const optionsResult = await itemOptionsSchema.safeParseAsync(fullOptions);
|
||||
if (!optionsResult.success) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Failed to parse item options",
|
||||
cause: optionsResult.error,
|
||||
});
|
||||
}
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
await transaction
|
||||
.update(schema.items)
|
||||
.set({
|
||||
advancedOptions: SuperJSON.stringify(input.advancedOptions),
|
||||
options: SuperJSON.stringify(optionsResult),
|
||||
})
|
||||
.where(eq(schema.items.id, input.id));
|
||||
|
||||
for (const itemLayout of input.layouts) {
|
||||
await transaction
|
||||
.update(schema.itemLayouts)
|
||||
.set({
|
||||
height: itemLayout.height,
|
||||
width: itemLayout.width,
|
||||
sectionId: itemLayout.sectionId,
|
||||
xOffset: itemLayout.xOffset,
|
||||
yOffset: itemLayout.yOffset,
|
||||
})
|
||||
.where(
|
||||
and(eq(schema.itemLayouts.itemId, input.id), eq(schema.itemLayouts.layoutId, itemLayout.layoutId)),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((transaction) => {
|
||||
transaction
|
||||
.update(items)
|
||||
.set({
|
||||
advancedOptions: SuperJSON.stringify(input.advancedOptions),
|
||||
options: SuperJSON.stringify(optionsResult),
|
||||
})
|
||||
.where(eq(items.id, input.id));
|
||||
|
||||
for (const itemLayout of input.layouts) {
|
||||
transaction
|
||||
.update(itemLayouts)
|
||||
.set({
|
||||
height: itemLayout.height,
|
||||
width: itemLayout.width,
|
||||
sectionId: itemLayout.sectionId,
|
||||
xOffset: itemLayout.xOffset,
|
||||
yOffset: itemLayout.yOffset,
|
||||
})
|
||||
.where(and(eq(itemLayouts.itemId, input.id), eq(itemLayouts.layoutId, itemLayout.layoutId)));
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: input.id,
|
||||
kind: item.kind,
|
||||
boardId: item.boardId,
|
||||
advancedOptions: input.advancedOptions,
|
||||
options: optionsResult,
|
||||
layouts: input.layouts,
|
||||
};
|
||||
}),
|
||||
deleteItem: protectedProcedure
|
||||
.input(byIdSchema)
|
||||
.output(z.void())
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "DELETE",
|
||||
path: "/api/items/{id}",
|
||||
tags: ["items"],
|
||||
protect: true,
|
||||
summary: "Delete an item from board",
|
||||
},
|
||||
})
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const item = await ctx.db.query.items.findFirst({
|
||||
where: (fields, { eq }) => eq(fields.id, input.id),
|
||||
});
|
||||
const error = new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Item not found",
|
||||
});
|
||||
if (!item) throw error;
|
||||
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "modify", error);
|
||||
|
||||
await ctx.db.delete(items).where(eq(items.id, input.id));
|
||||
}),
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
@@ -36,7 +37,12 @@ export const locationRouter = createTRPCRouter({
|
||||
.input(locationSearchCityInput)
|
||||
.output(locationSearchCityOutput)
|
||||
.query(async ({ input }) => {
|
||||
const res = await fetchWithTimeoutAsync(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||
const res = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
|
||||
}),
|
||||
});
|
||||
@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const healthMonitoringRouter = createTRPCRouter({
|
||||
getSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -26,7 +26,7 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.23",
|
||||
"next": "16.0.10",
|
||||
"ldapts": "8.0.35",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"octokit": "^5.0.5",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"undici": "7.16.0",
|
||||
"zod": "^4.1.13",
|
||||
"zod": "^4.2.1",
|
||||
"zod-validation-error": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -37,16 +37,16 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"better-sqlite3": "^12.5.0",
|
||||
"dns-caching": "^0.2.9",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"ioredis": "5.8.2",
|
||||
"mysql2": "3.15.3",
|
||||
"mysql2": "3.16.0",
|
||||
"pg": "^8.16.3",
|
||||
"superjson": "2.2.6",
|
||||
"winston": "3.19.0",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createBooleanSchema, createEnv } from "../env";
|
||||
|
||||
export const dnsEnv = createEnv({
|
||||
server: {
|
||||
ENABLE_DNS_CACHING: createBooleanSchema(false),
|
||||
ENABLE_DNS_CACHING: createBooleanSchema(true),
|
||||
},
|
||||
experimental__runtimeEnv: process.env,
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { Agent } from "undici";
|
||||
import { EnvHttpProxyAgent } from "undici";
|
||||
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
@@ -7,16 +7,15 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
// The below import statement initializes dns-caching
|
||||
import "@homarr/core/infrastructure/dns/init";
|
||||
|
||||
interface HttpAgentOptions extends Agent.Options {
|
||||
interface HttpAgentOptions extends EnvHttpProxyAgent.Options {
|
||||
logger?: ILogger;
|
||||
}
|
||||
|
||||
export class UndiciHttpAgent extends Agent {
|
||||
export class UndiciHttpAgent extends EnvHttpProxyAgent {
|
||||
private logger: ILogger;
|
||||
|
||||
constructor(props?: HttpAgentOptions) {
|
||||
super(props);
|
||||
|
||||
this.logger = props?.logger ?? createLogger({ module: "httpAgent" });
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ export const createCertificateAgentAsync = async (override?: {
|
||||
};
|
||||
|
||||
export const createHttpsAgentAsync = async (override?: Pick<AgentOptions, "ca" | "checkServerIdentity">) => {
|
||||
return new HttpsAgent(
|
||||
override ?? {
|
||||
ca: await getAllTrustedCertificatesAsync(),
|
||||
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
|
||||
},
|
||||
);
|
||||
return new HttpsAgent({
|
||||
ca: await getAllTrustedCertificatesAsync(),
|
||||
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
|
||||
// Override the ca and checkServerIdentity if provided
|
||||
...override,
|
||||
proxyEnv: process.env,
|
||||
});
|
||||
};
|
||||
|
||||
export const createAxiosCertificateInstanceAsync = async (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { TestLogger } from "../logs";
|
||||
|
||||
vi.mock("undici", () => {
|
||||
return {
|
||||
Agent: class Agent {
|
||||
EnvHttpProxyAgent: class EnvHttpProxyAgent {
|
||||
dispatch(_options: Dispatcher.DispatchOptions, _handler: Dispatcher.DispatchHandler): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@trpc/client": "^11.8.0",
|
||||
"@trpc/server": "^11.8.0",
|
||||
"@trpc/tanstack-react-query": "^11.8.0",
|
||||
"@tanstack/react-query": "^5.90.14",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@trpc/tanstack-react-query": "^11.8.1",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.2.3",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -49,14 +49,14 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.10",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"@testcontainers/mysql": "^11.10.0",
|
||||
"@testcontainers/postgresql": "^11.10.0",
|
||||
"@testcontainers/mysql": "^11.11.0",
|
||||
"@testcontainers/postgresql": "^11.11.0",
|
||||
"better-sqlite3": "^12.5.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.15.3",
|
||||
"mysql2": "3.16.0",
|
||||
"pg": "^8.16.3",
|
||||
"superjson": "2.2.6"
|
||||
},
|
||||
@@ -67,7 +67,7 @@
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.16.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.27.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.7.4",
|
||||
"tsx": "4.20.4",
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { createSelectSchema } from "drizzle-zod";
|
||||
|
||||
import { apps, boards, groups, invites, itemLayouts, items, searchEngines, serverSettings, users } from "./schema";
|
||||
import { apps, boards, groups, invites, searchEngines, serverSettings, users } from "./schema";
|
||||
|
||||
export const selectAppSchema = createSelectSchema(apps);
|
||||
export const selectBoardSchema = createSelectSchema(boards);
|
||||
export const selectitemSchema = createSelectSchema(items);
|
||||
export const insertItemSchema = createInsertSchema(items);
|
||||
export const selectItemLayoutSchema = createSelectSchema(itemLayouts);
|
||||
export const selectGroupSchema = createSelectSchema(groups);
|
||||
export const selectInviteSchema = createSelectSchema(invites);
|
||||
export const selectSearchEnginesSchema = createSelectSchema(searchEngines);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.3.3",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -298,6 +298,13 @@ export const integrationDefs = {
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/truenas"),
|
||||
},
|
||||
unraid: {
|
||||
name: "Unraid",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/unraid"),
|
||||
},
|
||||
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
|
||||
mock: {
|
||||
name: "Mock",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^8.3.10",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.10",
|
||||
"react": "19.2.3",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -19,11 +20,12 @@ export class GitHubIconRepository extends IconRepository {
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
if (!this.repositoryIndexingUrl || !this.repositoryBlobUrlTemplate) {
|
||||
const url = this.repositoryIndexingUrl;
|
||||
if (!url || !this.repositoryBlobUrlTemplate) {
|
||||
throw new Error("Repository URLs are required for this repository");
|
||||
}
|
||||
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const response = await withTimeoutAsync(async (signal) => fetchWithTrustedCertificatesAsync(url, { signal }));
|
||||
const listOfFiles = (await response.json()) as GitHubApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -19,7 +20,9 @@ export class JsdelivrIconRepository extends IconRepository {
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const response = await withTimeoutAsync(async (signal) =>
|
||||
fetchWithTrustedCertificatesAsync(this.repositoryIndexingUrl, { signal }),
|
||||
);
|
||||
const listOfFiles = (await response.json()) as JsdelivrApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"tsdav": "^2.1.6",
|
||||
"undici": "7.16.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -38,6 +38,7 @@ import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
|
||||
import { QuayIntegration } from "../quay/quay-integration";
|
||||
import { TrueNasIntegration } from "../truenas/truenas-integration";
|
||||
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
||||
import { UnraidIntegration } from "../unraid/unraid-integration";
|
||||
import type { Integration, IntegrationInput } from "./integration";
|
||||
|
||||
export const createIntegrationAsync = async <TKind extends keyof typeof integrationCreators>(
|
||||
@@ -101,6 +102,7 @@ export const integrationCreators = {
|
||||
ntfy: NTFYIntegration,
|
||||
mock: MockIntegration,
|
||||
truenas: TrueNasIntegration,
|
||||
unraid: UnraidIntegration,
|
||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||
|
||||
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
|
||||
|
||||
@@ -22,6 +22,7 @@ export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
|
||||
export { PlexIntegration } from "./plex/plex-integration";
|
||||
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
|
||||
export { TrueNasIntegration } from "./truenas/truenas-integration";
|
||||
export { UnraidIntegration } from "./unraid/unraid-integration";
|
||||
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
|
||||
export { ICalIntegration } from "./ical/ical-integration";
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { fetch as undiciFetch } from "undici/types/fetch";
|
||||
|
||||
import { humanFileSize } from "@homarr/common";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration";
|
||||
import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types";
|
||||
import type { UnraidSystemInfo } from "./unraid-types";
|
||||
import { unraidSystemInfoSchema } from "./unraid-types";
|
||||
|
||||
const logger = createLogger({ module: "UnraidIntegration" });
|
||||
|
||||
@HandleIntegrationErrors([])
|
||||
export class UnraidIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
await this.queryGraphQLAsync<{ info: UnraidSystemInfo }>(
|
||||
`
|
||||
query {
|
||||
info {
|
||||
os { platform }
|
||||
}
|
||||
}
|
||||
`,
|
||||
input.fetchAsync,
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
public async getSystemInfoAsync(): Promise<SystemHealthMonitoring> {
|
||||
const systemInfo = await this.getSystemInformationAsync();
|
||||
|
||||
const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
|
||||
const cpuCount = systemInfo.info.cpu.cores;
|
||||
|
||||
// We use "info" object instead of the stats since this is the exact amount the kernel sees, which is what Unraid displays.
|
||||
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
|
||||
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
|
||||
const uptime = dayjs(systemInfo.info.os.uptime);
|
||||
|
||||
return {
|
||||
version: systemInfo.info.os.release,
|
||||
cpuModelName: systemInfo.info.cpu.brand,
|
||||
cpuUtilization: cpuUtilization / cpuCount,
|
||||
memUsedInBytes: usedMemory,
|
||||
memAvailableInBytes: totalMemory - usedMemory,
|
||||
uptime: dayjs().diff(uptime, "seconds"),
|
||||
network: null, // Not implemented, see https://github.com/unraid/api/issues/1602
|
||||
loadAverage: null,
|
||||
rebootRequired: false,
|
||||
availablePkgUpdates: 0,
|
||||
cpuTemp: undefined, // Not implemented, see https://github.com/unraid/api/issues/1597
|
||||
fileSystem: systemInfo.array.disks.map((disk) => ({
|
||||
deviceName: disk.name,
|
||||
used: humanFileSize(disk.fsUsed * 1024), // API is in KiB (kibibytes), covert to bytes
|
||||
available: `${disk.size * 1024}`, // API is in KiB (kibibytes), covert to bytes
|
||||
percentage: (disk.fsUsed / disk.size) * 100, // The units are the same, therefore the actual unit is irrelevant
|
||||
})),
|
||||
smart: systemInfo.array.disks.map((disk) => ({
|
||||
deviceName: disk.name,
|
||||
temperature: disk.temp,
|
||||
overallStatus: disk.status,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private async getSystemInformationAsync(): Promise<UnraidSystemInfo> {
|
||||
logger.debug("Retrieving system information", {
|
||||
url: this.url("/graphql"),
|
||||
});
|
||||
|
||||
const query = `
|
||||
query {
|
||||
metrics {
|
||||
cpu {
|
||||
percentTotal
|
||||
cpus {
|
||||
percentTotal
|
||||
}
|
||||
},
|
||||
memory {
|
||||
available
|
||||
used
|
||||
free
|
||||
total
|
||||
swapFree
|
||||
swapTotal
|
||||
swapUsed
|
||||
percentTotal
|
||||
}
|
||||
}
|
||||
array {
|
||||
state
|
||||
capacity {
|
||||
disks {
|
||||
free
|
||||
total
|
||||
used
|
||||
}
|
||||
}
|
||||
disks {
|
||||
name
|
||||
size
|
||||
fsFree
|
||||
fsUsed
|
||||
status
|
||||
temp
|
||||
}
|
||||
}
|
||||
info {
|
||||
devices {
|
||||
network {
|
||||
speed
|
||||
dhcp
|
||||
model
|
||||
model
|
||||
}
|
||||
}
|
||||
os {
|
||||
platform,
|
||||
distro,
|
||||
release,
|
||||
uptime
|
||||
},
|
||||
cpu {
|
||||
manufacturer,
|
||||
brand,
|
||||
cores,
|
||||
threads
|
||||
},
|
||||
memory {
|
||||
layout {
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await this.queryGraphQLAsync<UnraidSystemInfo>(query);
|
||||
const result = await unraidSystemInfoSchema.parseAsync(response);
|
||||
|
||||
logger.debug("Retrieved system information", {
|
||||
url: this.url("/graphql"),
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async queryGraphQLAsync<T>(
|
||||
query: string,
|
||||
fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync,
|
||||
): Promise<T> {
|
||||
const url = this.url("/graphql");
|
||||
const apiKey = this.getSecretValue("apiKey");
|
||||
|
||||
logger.debug("Sending GraphQL query", {
|
||||
url: url.toString(),
|
||||
});
|
||||
|
||||
const response = await fetchAsync(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": apiKey,
|
||||
},
|
||||
body: JSON.stringify({ query }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ResponseError(response);
|
||||
}
|
||||
|
||||
const json = (await response.json()) as { data: T; errors?: { message: string }[] };
|
||||
|
||||
if (json.errors) {
|
||||
throw new Error(`GraphQL errors: ${json.errors.map((error) => error.message).join(", ")}`);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import z from "zod";
|
||||
|
||||
export const unraidSystemInfoSchema = z.object({
|
||||
metrics: z.object({
|
||||
cpu: z.object({
|
||||
percentTotal: z.number(),
|
||||
cpus: z.array(
|
||||
z.object({
|
||||
percentTotal: z.number(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
memory: z.object({
|
||||
available: z.number(),
|
||||
used: z.number(),
|
||||
free: z.number(),
|
||||
total: z.number().min(0),
|
||||
percentTotal: z.number().min(0).max(100),
|
||||
}),
|
||||
}),
|
||||
array: z.object({
|
||||
state: z.string(),
|
||||
capacity: z.object({
|
||||
disks: z.object({
|
||||
free: z.coerce.number(),
|
||||
total: z.coerce.number(),
|
||||
used: z.coerce.number(),
|
||||
}),
|
||||
}),
|
||||
disks: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
size: z.number(),
|
||||
fsFree: z.number(),
|
||||
fsUsed: z.number(),
|
||||
status: z.string(),
|
||||
temp: z.number(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
info: z.object({
|
||||
devices: z.object({
|
||||
network: z.array(
|
||||
z.object({
|
||||
speed: z.number(),
|
||||
dhcp: z.boolean(),
|
||||
model: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
os: z.object({
|
||||
platform: z.string(),
|
||||
distro: z.string(),
|
||||
release: z.string(),
|
||||
uptime: z.coerce.date(),
|
||||
}),
|
||||
cpu: z.object({
|
||||
manufacturer: z.string(),
|
||||
brand: z.string(),
|
||||
cores: z.number(),
|
||||
threads: z.number(),
|
||||
}),
|
||||
memory: z.object({
|
||||
layout: z.array(
|
||||
z.object({
|
||||
size: z.number(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type UnraidSystemInfo = z.infer<typeof unraidSystemInfoSchema>;
|
||||
@@ -36,10 +36,10 @@
|
||||
"@mantine/core": "^8.3.10",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
"@mantine/core": "^8.3.10",
|
||||
"@mantine/hooks": "^8.3.10",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"superjson": "2.2.6",
|
||||
"zod": "^4.1.13",
|
||||
"zod": "^4.2.1",
|
||||
"zod-form-data": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"octokit": "^5.0.5",
|
||||
"superjson": "2.2.6",
|
||||
"undici": "7.16.0",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -12,9 +12,7 @@ export const dockerContainersRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "dockerContainersResult",
|
||||
widgetKind: "dockerContainers",
|
||||
async requestAsync() {
|
||||
const containers = await getContainersWithStatsAsync();
|
||||
|
||||
return containers;
|
||||
return await getContainersWithStatsAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(20, "seconds"),
|
||||
});
|
||||
@@ -28,7 +26,7 @@ async function getContainersWithStatsAsync() {
|
||||
dockerInstances.map(async ({ instance, host }) => {
|
||||
const instanceContainers = await instance.listContainers({ all: true });
|
||||
return instanceContainers
|
||||
.filter((container) => dockerLabels.hide in container.Labels === false)
|
||||
.filter((container) => !(dockerLabels.hide in container.Labels))
|
||||
.map((container) => ({ ...container, instance: host }));
|
||||
}),
|
||||
).then((res) => res.flat());
|
||||
@@ -45,7 +43,21 @@ async function getContainersWithStatsAsync() {
|
||||
const instance = dockerInstances.find(({ host }) => host === container.instance)?.instance;
|
||||
if (!instance) return null;
|
||||
|
||||
const stats = await instance.getContainer(container.Id).stats({ stream: false, "one-shot": true });
|
||||
// Get stats, falling back to an empty stats object if fetch fails
|
||||
// calculateCpuUsage and calculateMemoryUsage will return 0 for invalid/missing stats
|
||||
const stats = await instance
|
||||
.getContainer(container.Id)
|
||||
.stats({ stream: false, "one-shot": true })
|
||||
.catch(
|
||||
() =>
|
||||
({
|
||||
cpu_stats: { online_cpus: 0, cpu_usage: { total_usage: 0 }, system_cpu_usage: 0 },
|
||||
memory_stats: { usage: 0 },
|
||||
}) as ContainerStats,
|
||||
);
|
||||
|
||||
const cpuUsage = calculateCpuUsage(stats);
|
||||
const memoryUsage = calculateMemoryUsage(stats);
|
||||
|
||||
return {
|
||||
id: container.Id,
|
||||
@@ -57,19 +69,8 @@ async function getContainersWithStatsAsync() {
|
||||
if (!extractedImage) return false;
|
||||
return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
|
||||
})?.url ?? null,
|
||||
cpuUsage: calculateCpuUsage(stats),
|
||||
// memory usage by default includes cache, which should not be shown as it is also not shown with docker stats command
|
||||
// The below type is probably wrong, sometimes stats can be undefined
|
||||
// See https://docs.docker.com/reference/cli/docker/container/stats/ how it is / was calculated
|
||||
memoryUsage:
|
||||
stats.memory_stats.usage -
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
(stats.memory_stats.stats?.cache ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
stats.memory_stats.stats?.total_inactive_file ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
stats.memory_stats.stats?.inactive_file ??
|
||||
0),
|
||||
cpuUsage,
|
||||
memoryUsage,
|
||||
image: container.Image,
|
||||
ports: container.Ports,
|
||||
};
|
||||
@@ -79,10 +80,36 @@ async function getContainersWithStatsAsync() {
|
||||
}
|
||||
|
||||
function calculateCpuUsage(stats: ContainerStats): number {
|
||||
const numberOfCpus = stats.cpu_stats.online_cpus || 1;
|
||||
// Handle containers with missing or invalid stats (e.g., exited, dead containers)
|
||||
if (!stats.cpu_stats.online_cpus || stats.cpu_stats.online_cpus === 0 || !stats.cpu_stats.cpu_usage.total_usage) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const numberOfCpus = stats.cpu_stats.online_cpus;
|
||||
const usage = stats.cpu_stats.system_cpu_usage;
|
||||
if (usage === 0) return 0;
|
||||
if (!usage || usage === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (stats.cpu_stats.cpu_usage.total_usage / usage) * numberOfCpus * 100;
|
||||
}
|
||||
|
||||
function calculateMemoryUsage(stats: ContainerStats): number {
|
||||
// Handle containers with missing or invalid stats (e.g., exited, dead containers)
|
||||
if (!stats.memory_stats.usage) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// memory usage by default includes cache, which should not be shown as it is also not shown with docker stats command
|
||||
// See https://docs.docker.com/reference/cli/docker/container/stats/ how it is / was calculated
|
||||
return (
|
||||
stats.memory_stats.usage -
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
(stats.memory_stats.stats?.cache ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
stats.memory_stats.stats?.total_inactive_file ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
stats.memory_stats.stats?.inactive_file ??
|
||||
0)
|
||||
);
|
||||
}
|
||||
@@ -57,7 +57,12 @@ export const createRequestIntegrationJobHandler = <
|
||||
reduceWidgetOptionsWithDefaultValues(
|
||||
itemForIntegration.kind,
|
||||
{
|
||||
defaultSearchEngineId: serverSettings.search.defaultSearchEngineId,
|
||||
openSearchInNewTab: true,
|
||||
firstDayOfWeek: 1,
|
||||
homeBoardId: serverSettings.board.homeBoardId,
|
||||
mobileHomeBoardId: serverSettings.board.mobileHomeBoardId,
|
||||
pingIconsEnabled: true,
|
||||
enableStatusByDefault: serverSettings.board.enableStatusByDefault,
|
||||
forceDisableStatus: serverSettings.board.forceDisableStatus,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -11,7 +12,9 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand
|
||||
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
|
||||
const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`;
|
||||
|
||||
const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`);
|
||||
const response = await withTimeoutAsync(async (signal) =>
|
||||
fetchWithTrustedCertificatesAsync(`https://api.mcsrvstat.us${path}`, { signal }),
|
||||
);
|
||||
return responseSchema.parse(await response.json());
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,9 +10,12 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "fetchStockPriceResult",
|
||||
widgetKind: "stockPrice",
|
||||
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
||||
const response = await fetchWithTimeoutAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
);
|
||||
const response = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
const data = dataSchema.parse(await response.json());
|
||||
|
||||
if ("error" in data) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { env } from "@homarr/common/env";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createChannelWithLatestAndEvents } from "@homarr/redis";
|
||||
import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler";
|
||||
@@ -23,7 +23,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
|
||||
const octokit = new Octokit({
|
||||
request: {
|
||||
fetch: fetchWithTimeoutAsync,
|
||||
fetch: fetchWithTrustedCertificatesAsync,
|
||||
},
|
||||
});
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,9 +10,12 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "weatherAtLocation",
|
||||
widgetKind: "weather",
|
||||
async requestAsync(input: { latitude: number; longitude: number }) {
|
||||
const res = await fetchWithTimeoutAsync(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
);
|
||||
const res = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
const json: unknown = await res.json();
|
||||
const weather = await atLocationOutput.parseAsync(json);
|
||||
return {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.3.10",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
"@mantine/hooks": "^8.3.10",
|
||||
"@mantine/spotlight": "^8.3.10",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"jotai": "^2.16.0",
|
||||
"next": "16.0.10",
|
||||
"jotai": "^2.16.1",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.10",
|
||||
"next-intl": "4.6.0",
|
||||
"next": "16.1.1",
|
||||
"next-intl": "4.6.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
},
|
||||
"mobileBoard": {
|
||||
"label": "Mobiles Board",
|
||||
"description": "Nur Boards welche für die Gruppe zugänglich sind, können ausgewählt werden"
|
||||
"description": "Nur Boards, welche für die Gruppe zugänglich sind, können ausgewählt werden"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
@@ -355,7 +355,7 @@
|
||||
},
|
||||
"view-all": {
|
||||
"label": "Alle Boards anzeigen",
|
||||
"description": "Mitgliedern das Ansehen von allen Boards erlauben"
|
||||
"description": "Mitgliedern das Ansehen von Boards erlauben"
|
||||
},
|
||||
"modify-all": {
|
||||
"label": "Alle Boards ändern",
|
||||
@@ -743,7 +743,7 @@
|
||||
},
|
||||
"authorization": {
|
||||
"title": "Autorisierungsfehler",
|
||||
"description": "Die Anfrage wurde nicht autorisiert. Bitte überprüfen Sie, ob die Anmeldedaten korrekt sind und Sie diese mit mit den entsprechenden Berechtigungen konfiguriert haben."
|
||||
"description": "Die Anfrage wurde nicht autorisiert. Bitte überprüfen Sie, ob die Anmeldedaten korrekt sind und Sie diese mit den entsprechenden Berechtigungen konfiguriert haben."
|
||||
},
|
||||
"statusCode": {
|
||||
"title": "Antwort-Fehler",
|
||||
@@ -964,7 +964,7 @@
|
||||
},
|
||||
"personalAccessToken": {
|
||||
"label": "Persönlicher Zugangs-Token",
|
||||
"newLabel": "Neuer Persönliches Zugangs-Token"
|
||||
"newLabel": "Neuer persönlicher Zugangs-Token"
|
||||
},
|
||||
"topic": {
|
||||
"label": "Thema",
|
||||
@@ -1237,7 +1237,7 @@
|
||||
},
|
||||
"openAllInNewTabs": {
|
||||
"title": "Alle in tabs öffnen",
|
||||
"text": "Einige Browser können aus Sicherheitsgründen das Öffnen von mehreren Tabs aufeinmal blockieren. Homarr konnte nicht alle Fenster öffnen, weil Ihr Browser diese Aktion blockiert hat. Bitte erlauben Sie \"Pop-up Fenster öffnen\" und versuchen Sie es erneut."
|
||||
"text": "Einige Browser können aus Sicherheitsgründen das gleichzeitige Öffnen von mehreren Tabs blockieren. Homarr konnte nicht alle Fenster öffnen, weil Ihr Browser diese Aktion blockiert hat. Bitte erlauben Sie \"Pop-up Fenster öffnen\" und versuchen Sie es erneut."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1949,7 +1949,7 @@
|
||||
"name": "Name",
|
||||
"state": "Status",
|
||||
"cpuUsage": "CPU-Auslastung",
|
||||
"memoryUsage": "Speicherverbrauch"
|
||||
"memoryUsage": "Speichernutzung"
|
||||
}
|
||||
},
|
||||
"descendingDefaultSort": {
|
||||
@@ -2639,7 +2639,7 @@
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Änderungen erfolgreich angewendet",
|
||||
"message": "Das Board wurde erfolgreich dupliziert"
|
||||
"message": "Das Board wurde erfolgreich gespeichert"
|
||||
},
|
||||
"error": {
|
||||
"title": "Änderungen konnten nicht angewendet werden",
|
||||
@@ -2676,7 +2676,7 @@
|
||||
},
|
||||
"onlyImportApps": {
|
||||
"label": "Nur Apps importieren",
|
||||
"description": "Fügt nur die Apps hinzu, das Board muss manuell neu erstellt werden"
|
||||
"description": "Fügt nur die Apps hinzu. Das Board selbst muss manuell neu erstellt werden"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
@@ -2926,7 +2926,7 @@
|
||||
"error": {
|
||||
"noBoard": {
|
||||
"title": "Willkommen bei Homarr",
|
||||
"description": "Ein schlankens, modernes Dashboard, das alle Ihre Apps und Dienste bereit stellt.",
|
||||
"description": "Ein schlankes, modernes Dashboard, das alle Ihre Apps und Dienste bereitstellt.",
|
||||
"link": "Erstellen Sie Ihr erstes Board",
|
||||
"notice": "Um diese Seite verschwinden zu lassen, erstellen Sie ein Board und setzen es als Home Board"
|
||||
},
|
||||
@@ -3366,7 +3366,7 @@
|
||||
"label": "Index Manager"
|
||||
},
|
||||
"healthMonitoring": {
|
||||
"label": "Überwachung des Gesundheit"
|
||||
"label": "Überwachung des Zustandes"
|
||||
},
|
||||
"dnsHole": {
|
||||
"label": "DNS Hole Daten"
|
||||
@@ -3512,7 +3512,7 @@
|
||||
},
|
||||
"about": {
|
||||
"version": "Version {version}",
|
||||
"text": "Homarr ist ein gemeinschaftsorientiertes Open-Source Projekt, das von Freiwilligen betreut wird. Dank dieser Menschen ist Homarr seit 2021 ein wachsendes Projekt. Unser Team, sitzhaft mit ihren Migliedern in vielen verschiedenen Ländern, arbeitet in ihrer Freizeit ohne Vergütung.",
|
||||
"text": "Homarr ist ein gemeinschaftsorientiertes Open-Source-Projekt, das von Freiwilligen betreut wird. Dank dieser Menschen ist Homarr seit 2021 ein wachsendes Projekt. Unser Team, dessen Mitglieder in vielen verschiedenen Ländern ansässig sind, arbeitet in seiner Freizeit und ohne Vergütung.",
|
||||
"accordion": {
|
||||
"contributors": {
|
||||
"title": "Mitwirkende",
|
||||
@@ -4395,7 +4395,7 @@
|
||||
"configControl": "Konfiguration",
|
||||
"searchEngineType": {
|
||||
"generic": "Allgemein",
|
||||
"fromIntegration": "Aus den Integration"
|
||||
"fromIntegration": "Aus der Integration"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -4498,7 +4498,7 @@
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "Zertifikat entfernt",
|
||||
"message": "Das Zertifikat wurde erfolgreich entfern"
|
||||
"message": "Das Zertifikat wurde erfolgreich entfernt"
|
||||
},
|
||||
"error": {
|
||||
"title": "Zertifikat nicht entfernt",
|
||||
|
||||
@@ -1941,19 +1941,19 @@
|
||||
"description": "Estadísticas de tus contenedores (Este widget sólo puede ser añadido con privilegios de administrador)",
|
||||
"option": {
|
||||
"enableRowSorting": {
|
||||
"label": ""
|
||||
"label": "Habilitar ordenar elementos"
|
||||
},
|
||||
"defaultSort": {
|
||||
"label": "",
|
||||
"label": "Columna usada para ordenar por defecto",
|
||||
"option": {
|
||||
"name": "",
|
||||
"state": "",
|
||||
"cpuUsage": "",
|
||||
"memoryUsage": ""
|
||||
"name": "Nombre",
|
||||
"state": "Estado",
|
||||
"cpuUsage": "Uso de CPU",
|
||||
"memoryUsage": "Uso de memoria"
|
||||
}
|
||||
},
|
||||
"descendingDefaultSort": {
|
||||
"label": ""
|
||||
"label": "Invertir orden"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"description": "Pour commencer, veuillez selectionner comment vous souhaitez configurer votre instance Homarr.",
|
||||
"action": {
|
||||
"scratch": "Partir de zéro",
|
||||
"importOldmarr": "Importer à partir d'Homarr avant la 1.0"
|
||||
"importOldmarr": "Importer depuis Homarr avant la 1.0"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"description": "Configurer le comportement d'importation"
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "{count} tableaux trouvés",
|
||||
"title": "Tableaux {count} trouvés",
|
||||
"description": "Choisissez tous les tableaux avec la taille que vous souhaitez importer",
|
||||
"action": {
|
||||
"selectAll": "Tout sélectionner",
|
||||
@@ -113,7 +113,7 @@
|
||||
"subtitle": "Bon retour parmi nous ! Veuillez entrer vos identifiants"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Rejoindre Homarr",
|
||||
"title": "Rejoignez Homarr",
|
||||
"subtitle": "Bienvenue sur Homarr! Veuillez créer votre compte",
|
||||
"description": "Vous avez été invité par {username}"
|
||||
},
|
||||
@@ -649,14 +649,14 @@
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
"title": "Existant",
|
||||
"label": "Sélectionner une application existante"
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"title": "Nouveau",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Lien de l'application",
|
||||
"description": "Lien d'ouverture de l'application depuis le tableau de bord"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,9 +676,9 @@
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
"add": "Lier une application",
|
||||
"remove": "Délier",
|
||||
"select": "Sélectionnez une application à lier"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -709,7 +709,7 @@
|
||||
"description": "L'intégration \"{kind}\" peut être utilisée avec les moteurs de recherche. Cochez ceci pour configurer automatiquement le moteur de recherche."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
"sectionTitle": "Application liée"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Créer une application",
|
||||
@@ -991,8 +991,8 @@
|
||||
"newLabel": "Nouvel ID d'installation"
|
||||
},
|
||||
"privateKey": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Clé privée",
|
||||
"newLabel": "Nouvelle clé privée"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1044,14 +1044,14 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"success": "Succès",
|
||||
"beta": "Bêta",
|
||||
"error": "Erreur",
|
||||
"action": {
|
||||
"add": "Ajouter",
|
||||
"apply": "Appliquer",
|
||||
"backToOverview": "Retourner à l'aperçu",
|
||||
"change": "",
|
||||
"change": "Modifier",
|
||||
"create": "Créer",
|
||||
"createAnother": "Créer et recommencer",
|
||||
"edit": "Modifier",
|
||||
@@ -1174,8 +1174,8 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kilometersPerHour": "",
|
||||
"milesPerHour": ""
|
||||
"kilometersPerHour": "km/h",
|
||||
"milesPerHour": "mph"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1190,7 +1190,7 @@
|
||||
"label": "Titre"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": ""
|
||||
"label": "Classes Css personnalisées"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "Couleur de la bordure"
|
||||
@@ -1321,21 +1321,21 @@
|
||||
"label": "Activer la vérification du statut"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"label": "Mise en page",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
"row": "Horizontale",
|
||||
"row-reverse": "Horizontal (inversé)",
|
||||
"column": "Vertical",
|
||||
"column-reverse": "Vertical (inversé)"
|
||||
}
|
||||
},
|
||||
"descriptionDisplayMode": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"label": "Mode d'affichage de la description",
|
||||
"description": "Choisissez comment afficher la description de l'application",
|
||||
"option": {
|
||||
"normal": "",
|
||||
"tooltip": "",
|
||||
"hidden": ""
|
||||
"normal": "Au sein du widget",
|
||||
"tooltip": "Comme infobulle",
|
||||
"hidden": "Masquer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1584,11 +1584,11 @@
|
||||
},
|
||||
"placeholder": "Commencer à écrire vos notes",
|
||||
"dismiss": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "Abandonner les modifications ?",
|
||||
"message": "Vous avez des modifications non enregistrées dans votre bloc-notes. Êtes-vous sûr de vouloir les supprimer ?",
|
||||
"action": {
|
||||
"discard": "",
|
||||
"keepEditing": ""
|
||||
"discard": "Ignorer les modifications",
|
||||
"keepEditing": "Continuer les modifications"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1743,7 +1743,7 @@
|
||||
"name": "Calendrier",
|
||||
"description": "Afficher les événements de vos intégrations dans une vue calendrier pendant une certaine période de temps relative",
|
||||
"duration": {
|
||||
"allDay": ""
|
||||
"allDay": "Tous les jours"
|
||||
},
|
||||
"option": {
|
||||
"releaseType": {
|
||||
@@ -1780,7 +1780,7 @@
|
||||
"description": "Uniquement sur la météo actuelle"
|
||||
},
|
||||
"useImperialSpeed": {
|
||||
"label": ""
|
||||
"label": "Utiliser mph pour la vitesse du vent"
|
||||
},
|
||||
"location": {
|
||||
"label": "Lieu de la météo"
|
||||
@@ -1941,19 +1941,19 @@
|
||||
"description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)",
|
||||
"option": {
|
||||
"enableRowSorting": {
|
||||
"label": ""
|
||||
"label": "Activer le tri des éléments"
|
||||
},
|
||||
"defaultSort": {
|
||||
"label": "",
|
||||
"label": "Colonne de tri par défaut",
|
||||
"option": {
|
||||
"name": "",
|
||||
"state": "",
|
||||
"cpuUsage": "",
|
||||
"memoryUsage": ""
|
||||
"name": "Nom",
|
||||
"state": "État",
|
||||
"cpuUsage": "Utilisation du CPU",
|
||||
"memoryUsage": "Utilisation mémoire"
|
||||
}
|
||||
},
|
||||
"descendingDefaultSort": {
|
||||
"label": ""
|
||||
"label": "Inverser le tri"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -2036,21 +2036,21 @@
|
||||
"name": "Nom",
|
||||
"id": "Id",
|
||||
"metadata": {
|
||||
"title": "",
|
||||
"title": "Statistiques détaillées",
|
||||
"video": {
|
||||
"title": "",
|
||||
"resolution": ""
|
||||
"title": "Vidéo",
|
||||
"resolution": "Résolution"
|
||||
},
|
||||
"audio": {
|
||||
"title": "",
|
||||
"channelCount": "",
|
||||
"codec": ""
|
||||
"title": "Audio",
|
||||
"channelCount": "Canaux audio",
|
||||
"codec": "Codec audio"
|
||||
},
|
||||
"transcoding": {
|
||||
"title": "",
|
||||
"container": "",
|
||||
"title": "Transcodage",
|
||||
"container": "Conteneur",
|
||||
"resolution": "Résolution",
|
||||
"target": ""
|
||||
"target": "Codec cible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2248,7 +2248,7 @@
|
||||
"unknown": "Inconnu",
|
||||
"pending": "En attente",
|
||||
"processing": "Traitement en cours",
|
||||
"requested": "",
|
||||
"requested": "En attente",
|
||||
"partiallyAvailable": "Partiel",
|
||||
"available": "Disponible",
|
||||
"blacklisted": "Sur la liste noire",
|
||||
@@ -2361,7 +2361,7 @@
|
||||
"label": "Nombre maximum de publications"
|
||||
},
|
||||
"hideDescription": {
|
||||
"label": ""
|
||||
"label": "Masquer la description"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2542,7 +2542,7 @@
|
||||
},
|
||||
"firewall": {
|
||||
"name": "Surveillance du pare-feu",
|
||||
"description": "",
|
||||
"description": "Affiche un résumé des pare-feu",
|
||||
"tab": {
|
||||
"system": "Système",
|
||||
"interfaces": "Interfaces"
|
||||
@@ -2556,16 +2556,16 @@
|
||||
"widget": {
|
||||
"fwname": "Nom",
|
||||
"version": "Version",
|
||||
"versiontitle": "",
|
||||
"cputitle": "",
|
||||
"memorytitle": "",
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"versiontitle": "Versions",
|
||||
"cputitle": "Utilisation du CPU",
|
||||
"memorytitle": "Utilisation de la mémoire",
|
||||
"cpu": "CPU",
|
||||
"memory": "Mémoire",
|
||||
"interfaces": {
|
||||
"name": "",
|
||||
"trans": "",
|
||||
"recv": "",
|
||||
"title": ""
|
||||
"name": "nom",
|
||||
"trans": "Transmis",
|
||||
"recv": "Reçus",
|
||||
"title": "Interfaces réseau"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2576,37 +2576,37 @@
|
||||
"option": {}
|
||||
},
|
||||
"systemResources": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Ressources du système",
|
||||
"description": "CPU, Mémoire, Disque et autre utilisation matérielle de votre système",
|
||||
"option": {
|
||||
"hasShadow": {
|
||||
"label": ""
|
||||
"label": "Activer l'ombrage des cartes"
|
||||
},
|
||||
"visibleCharts": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"label": "Graphiques visibles",
|
||||
"description": "Sélectionnez les graphiques à afficher.",
|
||||
"option": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"network": ""
|
||||
"cpu": "CPU",
|
||||
"memory": "Mémoire",
|
||||
"network": "Réseau"
|
||||
}
|
||||
},
|
||||
"labelDisplayMode": {
|
||||
"label": "",
|
||||
"label": "Mode d'affichage de l'étiquette",
|
||||
"option": {
|
||||
"textWithIcon": "",
|
||||
"text": "",
|
||||
"icon": "",
|
||||
"hidden": ""
|
||||
"textWithIcon": "Afficher le texte avec l'icône",
|
||||
"text": "Afficher uniquement le texte",
|
||||
"icon": "Afficher uniquement l'icône",
|
||||
"hidden": "Masquer l'étiquette"
|
||||
}
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"network": "",
|
||||
"up": "",
|
||||
"down": ""
|
||||
"cpu": "CPU",
|
||||
"memory": "RAM",
|
||||
"network": "NET",
|
||||
"up": "UP",
|
||||
"down": "HORS SERVICE"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3030,8 +3030,8 @@
|
||||
"integration": "Intégrations",
|
||||
"app": "Applications",
|
||||
"group": "Groupes",
|
||||
"searchEngine": "",
|
||||
"media": ""
|
||||
"searchEngine": "Moteurs de recherche",
|
||||
"media": "Médias"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "Tableaux de bord",
|
||||
@@ -3040,8 +3040,8 @@
|
||||
"authorization": "Autorisation"
|
||||
},
|
||||
"heroBanner": {
|
||||
"title": "",
|
||||
"subtitle": ""
|
||||
"title": "Bienvenue sur votre",
|
||||
"subtitle": "Tableau {app}"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
@@ -3387,19 +3387,19 @@
|
||||
"label": "Conteneurs Docker"
|
||||
},
|
||||
"firewallCpu": {
|
||||
"label": ""
|
||||
"label": "Processeur du pare-feu"
|
||||
},
|
||||
"firewallMemory": {
|
||||
"label": ""
|
||||
"label": "Mémoire du pare-feu"
|
||||
},
|
||||
"firewallVersion": {
|
||||
"label": ""
|
||||
"label": "Version du pare-feu"
|
||||
},
|
||||
"firewallInterfaces": {
|
||||
"label": ""
|
||||
"label": "Interfaces du pare-feu"
|
||||
},
|
||||
"weather": {
|
||||
"label": ""
|
||||
"label": "Météo"
|
||||
}
|
||||
},
|
||||
"interval": {
|
||||
@@ -3410,10 +3410,10 @@
|
||||
"weeklyMonday": "Chaque semaine le lundi",
|
||||
"update": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Intervalle mis à jour avec succès"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "L'intervalle de mise à jour a échoué"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3422,55 +3422,55 @@
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
"label": "Nom"
|
||||
},
|
||||
"interval": {
|
||||
"label": "Intervalle de planification"
|
||||
},
|
||||
"lastExecution": {
|
||||
"label": ""
|
||||
"label": "Dernière exécution"
|
||||
},
|
||||
"actions": {
|
||||
"label": ""
|
||||
"label": "Actions"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"search": ""
|
||||
"search": "Rechercher des tâches {count}..."
|
||||
},
|
||||
"action": {
|
||||
"refresh": {
|
||||
"label": ""
|
||||
"label": "Rafraîchir"
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Tâches actualisées avec succès"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Échec de l'actualisation des tâches"
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Tâche déclenchée avec succès"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Échec du déclenchement de la tâche"
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Tâche activée avec succès"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Tâche désactivée avec succès"
|
||||
}
|
||||
},
|
||||
"toggle": {
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Impossible de basculer le statut de la tâche"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3527,19 +3527,19 @@
|
||||
"subtitle": "{count} utilisés dans le Code d'Homarr"
|
||||
},
|
||||
"hotkeys": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"title": "Raccourcis clavier",
|
||||
"subtitle": "Raccourcis clavier pour améliorer votre flux de travail",
|
||||
"field": {
|
||||
"shortcut": "",
|
||||
"action": ""
|
||||
"shortcut": "Raccourci",
|
||||
"action": "Action"
|
||||
},
|
||||
"action": {
|
||||
"toggleBoardEdit": "",
|
||||
"toggleColorScheme": "",
|
||||
"saveNotebook": "",
|
||||
"openSpotlight": ""
|
||||
"toggleBoardEdit": "Activer/désactiver le mode d'édition du tableau",
|
||||
"toggleColorScheme": "Basculer en mode clair/sombre",
|
||||
"saveNotebook": "Enregistrer le bloc-notes (uniquement à l'intérieur du widget notebook)",
|
||||
"openSpotlight": "Ouvrir la recherche"
|
||||
},
|
||||
"note": ""
|
||||
"note": "Astuce : Mod correspond à la touche Ctrl ou ⌘ sur macOS"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3995,7 +3995,7 @@
|
||||
"tools": {
|
||||
"label": "Outils",
|
||||
"tasks": {
|
||||
"label": ""
|
||||
"label": "Tâches"
|
||||
},
|
||||
"docker": {
|
||||
"label": "Docker"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -177,8 +177,8 @@
|
||||
}
|
||||
},
|
||||
"forgotPassword": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Ste pozabili geslo?",
|
||||
"description": "Administrator lahko zažene ukaz:"
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
@@ -622,7 +622,7 @@
|
||||
"description": "",
|
||||
"action": ""
|
||||
},
|
||||
"add": ""
|
||||
"add": "Dodaj aplikacijo"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -1059,10 +1059,10 @@
|
||||
"insert": "Vstavite",
|
||||
"remove": "Odstrani",
|
||||
"save": "Shrani",
|
||||
"saveChanges": "Shranjevanje sprememb",
|
||||
"saveChanges": "Shrani spremembe",
|
||||
"cancel": "Prekliči",
|
||||
"delete": "Izbriši",
|
||||
"discard": "",
|
||||
"discard": "Zapusti",
|
||||
"close": "",
|
||||
"confirm": "Potrdi",
|
||||
"continue": "",
|
||||
@@ -1125,13 +1125,13 @@
|
||||
"userAvatar": {
|
||||
"menu": {
|
||||
"switchToDarkMode": "",
|
||||
"switchToLightMode": "",
|
||||
"management": "",
|
||||
"preferences": "Vaše želje",
|
||||
"logout": "",
|
||||
"switchToLightMode": "Svetli način",
|
||||
"management": "Nastavitve",
|
||||
"preferences": "Prilagoditev",
|
||||
"logout": "Odjava",
|
||||
"login": "Prijava",
|
||||
"homeBoard": "",
|
||||
"loggedOut": "",
|
||||
"homeBoard": "Vaša nadzorna plošča",
|
||||
"loggedOut": "Odjava uspešna",
|
||||
"updateAvailable": ""
|
||||
}
|
||||
},
|
||||
@@ -1182,7 +1182,7 @@
|
||||
"section": {
|
||||
"dynamic": {
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Nova dinamična vsebina",
|
||||
"remove": ""
|
||||
},
|
||||
"option": {
|
||||
@@ -1208,7 +1208,7 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Nova kategorija",
|
||||
"edit": "",
|
||||
"remove": "",
|
||||
"moveUp": "Premaknite se navzgor",
|
||||
@@ -1243,12 +1243,12 @@
|
||||
},
|
||||
"item": {
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Nova vsebina",
|
||||
"import": "",
|
||||
"edit": "",
|
||||
"moveResize": "",
|
||||
"duplicate": "",
|
||||
"remove": ""
|
||||
"edit": "Uredi gradnik",
|
||||
"moveResize": "Premakni/spremeni",
|
||||
"duplicate": "Podvoji",
|
||||
"remove": "Izbriši"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
@@ -2638,8 +2638,8 @@
|
||||
"edit": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Spremembe so uveljavljene",
|
||||
"message": "Vsebina je bila uspšeno shranjena"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
@@ -2647,8 +2647,8 @@
|
||||
}
|
||||
},
|
||||
"confirmLeave": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Neshranjene spremembe",
|
||||
"message": "Vsebina ni shranjena, želite nadaljevati?"
|
||||
}
|
||||
},
|
||||
"oldImport": {
|
||||
@@ -2716,16 +2716,16 @@
|
||||
},
|
||||
"field": {
|
||||
"pageTitle": {
|
||||
"label": ""
|
||||
"label": "Naslov strani"
|
||||
},
|
||||
"metaTitle": {
|
||||
"label": ""
|
||||
"label": "Meta naslov"
|
||||
},
|
||||
"logoImageUrl": {
|
||||
"label": ""
|
||||
"label": "URL logotipa"
|
||||
},
|
||||
"faviconImageUrl": {
|
||||
"label": ""
|
||||
"label": "URL ikone"
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "",
|
||||
@@ -2835,7 +2835,7 @@
|
||||
"metaTitle": ""
|
||||
},
|
||||
"setting": {
|
||||
"title": "",
|
||||
"title": "Nastavitve za {boardName} nadzorno ploščo",
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Splošno",
|
||||
@@ -2844,9 +2844,9 @@
|
||||
"layout": {
|
||||
"title": "Postavitev",
|
||||
"responsive": {
|
||||
"title": "",
|
||||
"title": "Predloga",
|
||||
"action": {
|
||||
"add": ""
|
||||
"add": "Dodaj predlogo"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2959,14 +2959,14 @@
|
||||
"layout": {
|
||||
"field": {
|
||||
"name": {
|
||||
"label": ""
|
||||
"label": "Ime"
|
||||
},
|
||||
"columnCount": {
|
||||
"label": ""
|
||||
"label": "Število stolpcev"
|
||||
},
|
||||
"breakpoint": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Prekinitvene točke",
|
||||
"description": "Postavitev bo uporabljena na vseh zaslonih, večjih od te prelomne točke, do naslednje večje prelomne točke."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4046,7 +4046,7 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "",
|
||||
"placeholder": "Iskanje vsebine",
|
||||
"nothingFound": "",
|
||||
"error": {
|
||||
"fetch": ""
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@mantine/hooks": "^8.3.10",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"svgson": "^5.3.1"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^4.1.13",
|
||||
"zod": "^4.2.1",
|
||||
"zod-form-data": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -52,33 +52,33 @@
|
||||
"@mantine/core": "^8.3.10",
|
||||
"@mantine/hooks": "^8.3.10",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tiptap/extension-color": "3.13.0",
|
||||
"@tiptap/extension-highlight": "3.13.0",
|
||||
"@tiptap/extension-image": "3.13.0",
|
||||
"@tiptap/extension-link": "^3.13.0",
|
||||
"@tiptap/extension-placeholder": "^3.13.0",
|
||||
"@tiptap/extension-table": "3.13.0",
|
||||
"@tiptap/extension-table-cell": "3.13.0",
|
||||
"@tiptap/extension-table-header": "3.13.0",
|
||||
"@tiptap/extension-table-row": "3.13.0",
|
||||
"@tiptap/extension-task-item": "3.13.0",
|
||||
"@tiptap/extension-task-list": "3.13.0",
|
||||
"@tiptap/extension-text-align": "3.13.0",
|
||||
"@tiptap/extension-text-style": "3.13.0",
|
||||
"@tiptap/extension-underline": "3.13.0",
|
||||
"@tiptap/react": "^3.13.0",
|
||||
"@tiptap/starter-kit": "^3.13.0",
|
||||
"@tiptap/extension-color": "3.14.0",
|
||||
"@tiptap/extension-highlight": "3.14.0",
|
||||
"@tiptap/extension-image": "3.14.0",
|
||||
"@tiptap/extension-link": "^3.14.0",
|
||||
"@tiptap/extension-placeholder": "^3.14.0",
|
||||
"@tiptap/extension-table": "3.14.0",
|
||||
"@tiptap/extension-table-cell": "3.14.0",
|
||||
"@tiptap/extension-table-header": "3.14.0",
|
||||
"@tiptap/extension-table-row": "3.14.0",
|
||||
"@tiptap/extension-task-item": "3.14.0",
|
||||
"@tiptap/extension-task-list": "3.14.0",
|
||||
"@tiptap/extension-text-align": "3.14.0",
|
||||
"@tiptap/extension-text-style": "3.14.0",
|
||||
"@tiptap/extension-underline": "3.14.0",
|
||||
"@tiptap/react": "^3.14.0",
|
||||
"@tiptap/starter-kit": "^3.14.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.4",
|
||||
"video.js": "^8.23.4",
|
||||
"zod": "^4.1.13"
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -27,13 +27,24 @@ export const WidgetMultiTextInput = ({ property, kind, options }: CommonWidgetIn
|
||||
};
|
||||
|
||||
const currentValidationResult = React.useMemo(() => {
|
||||
return options.validate.safeParse(search);
|
||||
if (!options.validate) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
};
|
||||
}
|
||||
|
||||
const validationResult = options.validate.safeParse(search);
|
||||
return {
|
||||
success: validationResult.success,
|
||||
result: validationResult,
|
||||
};
|
||||
}, [options.validate, search]);
|
||||
|
||||
const error = React.useMemo(() => {
|
||||
/* hide the error when nothing is being typed since "" is not valid but is not an explicit error */
|
||||
if (!currentValidationResult.success && search.length !== 0) {
|
||||
return currentValidationResult.error.issues[0]?.message;
|
||||
if (!currentValidationResult.success && currentValidationResult.result && search.length !== 0) {
|
||||
return currentValidationResult.result.error?.issues[0]?.message;
|
||||
}
|
||||
return null;
|
||||
}, [currentValidationResult, search]);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ActionIcon, Avatar, Group, Stack, Text } from "@mantine/core";
|
||||
import { IconBookmark, IconX } from "@tabler/icons-react";
|
||||
import z from "zod";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -57,7 +56,6 @@ export const { definition, componentLoader } = createWidgetDefinition("bookmarks
|
||||
isLoading,
|
||||
};
|
||||
},
|
||||
validate: z.array(z.string()),
|
||||
}),
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -42,9 +42,7 @@ export interface WidgetDefinition {
|
||||
icon: TablerIcon;
|
||||
supportedIntegrations?: IntegrationKind[];
|
||||
integrationsRequired?: boolean;
|
||||
createOptions: (
|
||||
settings: Pick<SettingsContextProps, "enableStatusByDefault" | "firstDayOfWeek" | "forceDisableStatus">,
|
||||
) => WidgetOptionsRecord;
|
||||
createOptions: (settings: SettingsContextProps) => WidgetOptionsRecord;
|
||||
errors?: Partial<
|
||||
Record<
|
||||
DefaultErrorData["code"],
|
||||
|
||||
@@ -115,7 +115,7 @@ export type inferSupportedIntegrationsStrict<TKind extends WidgetKind> = (Widget
|
||||
|
||||
export const reduceWidgetOptionsWithDefaultValues = (
|
||||
kind: WidgetKind,
|
||||
settings: Pick<SettingsContextProps, "enableStatusByDefault" | "firstDayOfWeek" | "forceDisableStatus">,
|
||||
settings: SettingsContextProps,
|
||||
currentValue: Record<string, unknown> = {},
|
||||
) => {
|
||||
const definition = widgetImports[kind].definition;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { z } from "zod/v4";
|
||||
import type { ZodType } from "zod/v4";
|
||||
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||
|
||||
import type { inferSelectOptionValue, SelectOption } from "./_inputs/widget-select-input";
|
||||
import type { ReleasesRepository } from "./releases/releases-repository";
|
||||
@@ -39,7 +38,6 @@ export interface SortableItemListInput<TItem, TOptionValue extends UniqueIdentif
|
||||
}) => React.ReactNode;
|
||||
uniqueIdentifier: (item: TItem) => TOptionValue;
|
||||
useData: (values: TOptionValue[]) => { data: TItem[] | undefined; isLoading: boolean; error: unknown };
|
||||
validate: z.ZodArray<z.ZodType>;
|
||||
}
|
||||
|
||||
interface SelectInput<TOptions extends readonly SelectOption[]> extends CommonInput<
|
||||
@@ -70,13 +68,12 @@ const optionsFactory = {
|
||||
type: "switch" as const,
|
||||
defaultValue: input?.defaultValue ?? false,
|
||||
withDescription: input?.withDescription ?? false,
|
||||
validate: z.boolean(),
|
||||
}),
|
||||
text: (input?: TextInput) => ({
|
||||
type: "text" as const,
|
||||
defaultValue: input?.defaultValue ?? "",
|
||||
withDescription: input?.withDescription ?? false,
|
||||
validate: input?.validate ?? z.string(),
|
||||
validate: input?.validate,
|
||||
}),
|
||||
multiSelect: <const TOptions extends SelectOption[]>(input: MultiSelectInput<TOptions>) => ({
|
||||
type: "multiSelect" as const,
|
||||
@@ -84,9 +81,6 @@ const optionsFactory = {
|
||||
options: input.options,
|
||||
searchable: input.searchable ?? false,
|
||||
withDescription: input.withDescription ?? false,
|
||||
validate: z.array(
|
||||
zodEnumFromArray(input.options.map((option) => (typeof option === "string" ? option : option.value))),
|
||||
),
|
||||
}),
|
||||
select: <const TOptions extends SelectOption[]>(input: SelectInput<TOptions>) => ({
|
||||
type: "select" as const,
|
||||
@@ -94,7 +88,6 @@ const optionsFactory = {
|
||||
options: input.options,
|
||||
searchable: input.searchable ?? false,
|
||||
withDescription: input.withDescription ?? false,
|
||||
validate: zodEnumFromArray(input.options.map((option) => (typeof option === "string" ? option : option.value))),
|
||||
}),
|
||||
number: (input: NumberInput) => ({
|
||||
type: "number" as const,
|
||||
@@ -129,20 +122,19 @@ const optionsFactory = {
|
||||
defaultValue: input?.defaultValue ?? [],
|
||||
withDescription: input?.withDescription ?? false,
|
||||
values: [] as string[],
|
||||
validate: input?.validate ?? z.array(z.string()),
|
||||
validate: input?.validate,
|
||||
}),
|
||||
multiReleasesRepositories: (input: CommonInput<ReleasesRepository[]> & { validate: ZodType }) => ({
|
||||
multiReleasesRepositories: (input?: CommonInput<ReleasesRepository[]> & { validate?: ZodType }) => ({
|
||||
type: "multiReleasesRepositories" as const,
|
||||
defaultValue: input.defaultValue ?? [],
|
||||
withDescription: input.withDescription ?? false,
|
||||
defaultValue: input?.defaultValue ?? [],
|
||||
withDescription: input?.withDescription ?? false,
|
||||
values: [] as ReleasesRepository[],
|
||||
validate: input.validate,
|
||||
validate: input?.validate,
|
||||
}),
|
||||
app: () => ({
|
||||
type: "app" as const,
|
||||
defaultValue: "",
|
||||
withDescription: false,
|
||||
validate: z.string(),
|
||||
}),
|
||||
sortableItemList: <const TItem, const TOptionValue extends UniqueIdentifier>(
|
||||
input: SortableItemListInput<TItem, TOptionValue>,
|
||||
@@ -154,7 +146,6 @@ const optionsFactory = {
|
||||
uniqueIdentifier: input.uniqueIdentifier,
|
||||
useData: input.useData,
|
||||
withDescription: false,
|
||||
validate: input.validate,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export const SystemResourceMemoryChart = ({
|
||||
return (
|
||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
||||
<Text c="dimmed" size="xs">
|
||||
{humanFileSize(value)} / {humanFileSize(totalCapacityInBytes)} (
|
||||
{humanFileSize(Math.round(value))} / {humanFileSize(totalCapacityInBytes)} (
|
||||
{Math.round((value / totalCapacityInBytes) * 100)}%)
|
||||
</Text>
|
||||
</Paper>
|
||||
|
||||
@@ -22,6 +22,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo
|
||||
});
|
||||
const memoryCapacityInBytes =
|
||||
(data[0]?.healthInfo.memAvailableInBytes ?? 0) + (data[0]?.healthInfo.memUsedInBytes ?? 0);
|
||||
|
||||
const [items, setItems] = useState<{ cpu: number; memory: number; network: { up: number; down: number } | null }[]>(
|
||||
data.map((item) => ({
|
||||
cpu: item.healthInfo.cpuUtilization,
|
||||
|
||||
@@ -14,7 +14,7 @@ const labelDisplayModeOptions = {
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("systemResources", {
|
||||
icon: IconGraphFilled,
|
||||
supportedIntegrations: ["dashDot", "openmediavault", "truenas"],
|
||||
supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid"],
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
hasShadow: factory.switch({ defaultValue: true }),
|
||||
|
||||
Generated
+2217
-2611
File diff suppressed because it is too large.
Load diff
@@ -1 +1 @@
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated
|
||||
@@ -1 +1 @@
|
||||
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"Skick","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12619811/medium/0331f1de413a645b4516bc4e037f31bb.jpg"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowLine truncated
|
||||
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"httpsLine truncated
|
||||
@@ -17,14 +17,14 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "16.0.10",
|
||||
"@next/eslint-plugin-next": "16.1.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-config-turbo": "^2.6.3",
|
||||
"eslint-config-turbo": "^2.7.2",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^6.1.1",
|
||||
"typescript-eslint": "^8.49.0"
|
||||
"typescript-eslint": "^8.50.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
|
||||
Reference in new issue
Block a user