Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf2e18d248 | ||
|
|
1efe97d8e6 | ||
|
|
469d7bf6fb | ||
|
|
0082984f9c | ||
|
|
2e789795ed | ||
|
|
88591d973f | ||
|
|
6218a401de | ||
|
|
a787e3deca | ||
|
|
d195d260aa | ||
|
|
d0973a9290 | ||
|
|
6866735cc1 | ||
|
|
481999af5c | ||
|
|
6a45645354 | ||
|
|
f22a763d96 | ||
|
|
7dd579d3c0 | ||
|
|
13ea6d08a0 | ||
|
|
8c1e5c8947 | ||
|
|
b397ab72c9 | ||
|
|
792a8bd11e | ||
|
|
f680fd170f | ||
|
|
916a261467 | ||
|
|
72d636c0f0 | ||
|
|
c8a61a9df3 | ||
|
|
0731b7c5ef | ||
|
|
f2437c4731 | ||
|
|
652ed1a5ed | ||
|
|
c48588c6a7 | ||
|
|
ce0c72b008 | ||
|
|
1057b478c3 | ||
|
|
4d53193fcc | ||
|
|
95abd2c9d3 | ||
|
|
f88984cda8 | ||
|
|
0927f45b25 | ||
|
|
ed24ce6381 | ||
|
|
24607e266b | ||
|
|
9cdf2aa1f2 | ||
|
|
74611b75a7 | ||
|
|
cf131c5c51 | ||
|
|
efd8a55b6e | ||
|
|
c0c16be9e4 | ||
|
|
327611dfc6 | ||
|
|
c5149d4262 | ||
|
|
3f039bf57d | ||
|
|
053cb8bec3 | ||
|
|
813f2fa5ef | ||
|
|
3d0c9fde0c | ||
|
|
c74367e564 | ||
|
|
5e2b693f6a | ||
|
|
8df71f7695 | ||
|
|
db6bb870e3 | ||
|
|
dc44fac42a | ||
|
|
26c2fa555b | ||
|
|
3ce3d221cd | ||
|
|
62e9bb0c49 | ||
|
|
b11eaff043 | ||
|
|
845f11526e | ||
|
|
7982def853 | ||
|
|
13e13ba352 | ||
|
|
55b743496b | ||
|
|
4a6aecceea | ||
|
|
2454cac7be | ||
|
|
41a03ef4e0 | ||
|
|
7879b31630 | ||
|
|
57ea82f088 | ||
|
|
22d599c0cf | ||
|
|
c861878058 | ||
|
|
9f5ae31222 | ||
|
|
6ec058c835 | ||
|
|
faffb818f9 | ||
|
|
bdf9439e49 | ||
|
|
75b36bb160 | ||
|
|
43587656bb | ||
|
|
8cab4b0333 | ||
|
|
154ad1e01e | ||
|
|
cd2d0ba53f | ||
|
|
37d7c8535f | ||
|
|
a0e0083b16 | ||
|
|
5317a25cfb | ||
|
|
b619ba1d46 | ||
|
|
2baab5402d | ||
|
|
174851bd19 | ||
|
|
a7b195d2cb | ||
|
|
0ab78142b3 | ||
|
|
3f73474d31 | ||
|
|
f2f5128cbd | ||
|
|
50bf7cdf19 | ||
|
|
ec15529ca4 | ||
|
|
db8a3291da | ||
|
|
e2410f143c | ||
|
|
cb2862bc79 | ||
|
|
dbe8d84202 | ||
|
|
612038ad07 | ||
|
|
72ad935ddf | ||
|
|
859747037b | ||
|
|
dfa87939f2 | ||
|
|
402d948fe7 | ||
|
|
d8480c05a6 | ||
|
|
6047eb4477 | ||
|
|
8b1e3babc0 | ||
|
|
1e869f6a96 | ||
|
|
7607140f94 | ||
|
|
f037da5831 | ||
|
|
f0ef4729bf | ||
|
|
ab1418514d | ||
|
|
a5b6c120dd | ||
|
|
9e7d5ad629 | ||
|
|
a86cdb9fb9 | ||
|
|
270fce6a0e | ||
|
|
2680255f44 | ||
|
|
ddf87d278e | ||
|
|
822d2c194b |
No files matched your search
@@ -33,6 +33,10 @@ body:
|
|||||||
options:
|
options:
|
||||||
# The below comment is used to insert a new version with on-release.yml
|
# The below comment is used to insert a new version with on-release.yml
|
||||||
#NEXT_VERSION#
|
#NEXT_VERSION#
|
||||||
|
- 1.59.1
|
||||||
|
- 1.59.0
|
||||||
|
- 1.58.1
|
||||||
|
- 1.58.0
|
||||||
- 1.57.1
|
- 1.57.1
|
||||||
- 1.57.0
|
- 1.57.0
|
||||||
- 1.56.1
|
- 1.56.1
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
matchPackageNames: ["tsx"],
|
matchPackageNames: ["tsx"],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
|
||||||
|
{
|
||||||
|
matchPackageNames: ["undici"],
|
||||||
|
matchNewValue: "8.*",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||||
automerge: true,
|
automerge: true,
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ jobs:
|
|||||||
approve-automatic-prs:
|
approve-automatic-prs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
|
# 158783068: homarr-renovate[bot]
|
||||||
|
# 190541745: homarr-crowdin[bot]
|
||||||
|
# 210161987: homarr-update-contributors[bot]
|
||||||
|
# 175486441: homarr-releases[bot]
|
||||||
|
# Extracted from https://api.github.com/users/<username>
|
||||||
|
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@v6
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
skip-stability-days:
|
skip-stability-days:
|
||||||
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
|
if: ${{ !startsWith(github.head_ref, 'renovate/') && github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
name: Skip Stability Days
|
name: Skip Stability Days
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v5
|
- uses: pnpm/action-setup@v6
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
+5
-1
@@ -67,4 +67,8 @@ e2e/shared/tmp
|
|||||||
apps/nextjs/public/images/background.png
|
apps/nextjs/public/images/background.png
|
||||||
|
|
||||||
# next-intl
|
# next-intl
|
||||||
en.d.json.ts
|
en.d.json.ts
|
||||||
|
|
||||||
|
# personal agent config
|
||||||
|
AGENTS.md
|
||||||
|
.opencode
|
||||||
@@ -1,3 +1,78 @@
|
|||||||
|
## [1.59.2](https://github.com/homarr-labs/homarr/compare/v1.59.1...v1.59.2) (2026-04-17)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **about:** add missing dependencies ([#5480](https://github.com/homarr-labs/homarr/issues/5480)) ([c48588c](https://github.com/homarr-labs/homarr/commit/c48588c6a748920b05f8afcd79fad17dc65f2930))
|
||||||
|
* **deps:** update dependency @drizzle-team/brocli to ^0.12.0 ([#5489](https://github.com/homarr-labs/homarr/issues/5489)) ([74611b7](https://github.com/homarr-labs/homarr/commit/74611b75a736df43a77ebc780d2e0eee5c40e3f3))
|
||||||
|
* **deps:** update dependency @immich/sdk to ^2.7.0 ([#5490](https://github.com/homarr-labs/homarr/issues/5490)) ([9cdf2aa](https://github.com/homarr-labs/homarr/commit/9cdf2aa1f2b1477522879095b603b100c1dd3ac0))
|
||||||
|
* **deps:** update dependency @immich/sdk to ^2.7.2 ([#5494](https://github.com/homarr-labs/homarr/issues/5494)) ([0927f45](https://github.com/homarr-labs/homarr/commit/0927f45b2573753152fd5e9e318ba62b88abca35))
|
||||||
|
* **deps:** update dependency fast-xml-parser to ^5.5.11 ([#5500](https://github.com/homarr-labs/homarr/issues/5500)) ([652ed1a](https://github.com/homarr-labs/homarr/commit/652ed1a5ed0beeb2dbd44976890f24dc12b90cf6))
|
||||||
|
* **deps:** update dependency isomorphic-dompurify to ^3.8.0 ([#5503](https://github.com/homarr-labs/homarr/issues/5503)) ([0731b7c](https://github.com/homarr-labs/homarr/commit/0731b7c5ef272bcf69c566cdec723f2696309e67))
|
||||||
|
* **deps:** update dependency jotai to ^2.19.1 ([#5487](https://github.com/homarr-labs/homarr/issues/5487)) ([efd8a55](https://github.com/homarr-labs/homarr/commit/efd8a55b6ea113bc42bc90d9ccb0db6c1d2b68ef))
|
||||||
|
* **deps:** update dependency next-intl to v4.9.1 [security] ([#5470](https://github.com/homarr-labs/homarr/issues/5470)) ([f2437c4](https://github.com/homarr-labs/homarr/commit/f2437c473106689e9fa93e7dcbea84b00800ea4a))
|
||||||
|
* **deps:** update dependency swagger-ui-react to ^5.32.2 ([#5488](https://github.com/homarr-labs/homarr/issues/5488)) ([cf131c5](https://github.com/homarr-labs/homarr/commit/cf131c5c51f6c21f942ab89d02353f93dc05774c))
|
||||||
|
* **deps:** update dependency typescript-eslint to ^8.58.1 ([#5496](https://github.com/homarr-labs/homarr/issues/5496)) ([f88984c](https://github.com/homarr-labs/homarr/commit/f88984cda844beb1099a08c9cdeb6691ad13b5a8))
|
||||||
|
* **deps:** update react monorepo to v19.2.5 ([#5502](https://github.com/homarr-labs/homarr/issues/5502)) ([c8a61a9](https://github.com/homarr-labs/homarr/commit/c8a61a9df3087de28c76f573623725eec246dd32))
|
||||||
|
* **deps:** update testcontainers-node monorepo to ^11.14.0 ([#5498](https://github.com/homarr-labs/homarr/issues/5498)) ([1057b47](https://github.com/homarr-labs/homarr/commit/1057b478c37d02e9de7bcd155f0b3fb764149abe))
|
||||||
|
* **deps:** update tiptap monorepo to v3.22.3 ([#5499](https://github.com/homarr-labs/homarr/issues/5499)) ([95abd2c](https://github.com/homarr-labs/homarr/commit/95abd2c9d3a563c7eaebe12f3b8015e3299e27e8))
|
||||||
|
* **deps:** update turbo monorepo to ^2.9.4 ([#5476](https://github.com/homarr-labs/homarr/issues/5476)) ([813f2fa](https://github.com/homarr-labs/homarr/commit/813f2fa5efb999d3364882578fdf456537dba3a6))
|
||||||
|
* **deps:** update turbo monorepo to ^2.9.5 ([#5491](https://github.com/homarr-labs/homarr/issues/5491)) ([ed24ce6](https://github.com/homarr-labs/homarr/commit/ed24ce6381466fa530ce225363231303be4ed92e))
|
||||||
|
* preserve all-day event dates in Nextcloud calendar integration ([#5523](https://github.com/homarr-labs/homarr/issues/5523)) ([2e78979](https://github.com/homarr-labs/homarr/commit/2e789795ed11c816550b6a3ce88fe92a360ede9c))
|
||||||
|
|
||||||
|
## [1.59.1](https://github.com/homarr-labs/homarr/compare/v1.59.0...v1.59.1) (2026-04-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **qbittorrent:** requests fail ([#5473](https://github.com/homarr-labs/homarr/issues/5473)) ([db6bb87](https://github.com/homarr-labs/homarr/commit/db6bb870e3be14c7895a2882755d3aee09e44fa6))
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* **deps:** downgrade undici from v8 to v7 ([#5473](https://github.com/homarr-labs/homarr/issues/5473)) ([dc44fac](https://github.com/homarr-labs/homarr/commit/dc44fac42aab5679427b2921402dfa11e634e1cf))
|
||||||
|
|
||||||
|
## [1.59.0](https://github.com/homarr-labs/homarr/compare/v1.58.1...v1.59.0) (2026-04-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Anchor integration and Anchor Note widget ([#5040](https://github.com/homarr-labs/homarr/issues/5040)) ([7879b31](https://github.com/homarr-labs/homarr/commit/7879b3163074b6af9381b8eb70dced55faddc506))
|
||||||
|
* **rss-feed:** add optional poster ([#5452](https://github.com/homarr-labs/homarr/issues/5452)) ([6ec058c](https://github.com/homarr-labs/homarr/commit/6ec058c835040dbf72cd2490d49f94be7da066dc))
|
||||||
|
* **rss-feed:** add periodic live updates ([#5449](https://github.com/homarr-labs/homarr/issues/5449)) ([faffb81](https://github.com/homarr-labs/homarr/commit/faffb818f99900e3abb5fa78cfbb7980692362b5))
|
||||||
|
* speedtest tracker integration ([#5363](https://github.com/homarr-labs/homarr/issues/5363)) ([22d599c](https://github.com/homarr-labs/homarr/commit/22d599c0cf64b39f3d616ceadc6c2ac32551f99d))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** update dependency @tabler/icons-react to ^3.41.1 ([#5400](https://github.com/homarr-labs/homarr/issues/5400)) ([f0ef472](https://github.com/homarr-labs/homarr/commit/f0ef4729bfb7c3672968b77b9b757bc34877c60f))
|
||||||
|
* **deps:** update dependency adm-zip to v0.5.17 ([#5422](https://github.com/homarr-labs/homarr/issues/5422)) ([dbe8d84](https://github.com/homarr-labs/homarr/commit/dbe8d84202b00687f16784cbedf61d7488efbbff))
|
||||||
|
* **deps:** update dependency dotenv to ^17.4.0 ([#5427](https://github.com/homarr-labs/homarr/issues/5427)) ([50bf7cd](https://github.com/homarr-labs/homarr/commit/50bf7cdf194c13bcbf95ef8a6a1bc7eb4c9c3865))
|
||||||
|
* **deps:** update dependency dotenv to ^17.4.1 ([#5462](https://github.com/homarr-labs/homarr/issues/5462)) ([845f115](https://github.com/homarr-labs/homarr/commit/845f11526ea7b3dd6f543fad48a158a4782c52bb))
|
||||||
|
* **deps:** update dependency fast-xml-parser to ^5.5.10 ([#5439](https://github.com/homarr-labs/homarr/issues/5439)) ([a7b195d](https://github.com/homarr-labs/homarr/commit/a7b195d2cb25fffe81266e7f6fa9499d94cae733))
|
||||||
|
* **deps:** update dependency next to v16.2.3 [security] ([#5461](https://github.com/homarr-labs/homarr/issues/5461)) ([7982def](https://github.com/homarr-labs/homarr/commit/7982def853fd69c269946dc2d6377186512baaf9))
|
||||||
|
* **deps:** update dependency next-intl to v4.8.4 ([#5413](https://github.com/homarr-labs/homarr/issues/5413)) ([6047eb4](https://github.com/homarr-labs/homarr/commit/6047eb4477584172f7d8e6e14237c251ac487d81))
|
||||||
|
* **deps:** update dependency next-intl to v4.9.0 ([#5426](https://github.com/homarr-labs/homarr/issues/5426)) ([612038a](https://github.com/homarr-labs/homarr/commit/612038ad070681c582cb54ac4298bc07700765c9))
|
||||||
|
* **deps:** update dependency react-quill-new to ^3.8.3 ([#5463](https://github.com/homarr-labs/homarr/issues/5463)) ([13e13ba](https://github.com/homarr-labs/homarr/commit/13e13ba3526436b044ac83d08c346e8e406ca3d4))
|
||||||
|
* **deps:** update dependency sass to ^1.99.0 ([#5437](https://github.com/homarr-labs/homarr/issues/5437)) ([3f73474](https://github.com/homarr-labs/homarr/commit/3f73474d3125279d94395272365aa67e0a0f69e8))
|
||||||
|
* **deps:** update dependency typescript-eslint to ^8.58.0 ([#5409](https://github.com/homarr-labs/homarr/issues/5409)) ([1e869f6](https://github.com/homarr-labs/homarr/commit/1e869f6a96d7e34af068b5666e01093a4b20fe97))
|
||||||
|
* **deps:** update dependency undici to v8 ([#5432](https://github.com/homarr-labs/homarr/issues/5432)) ([f2f5128](https://github.com/homarr-labs/homarr/commit/f2f5128cbd961b6576e195ac8b695f12996c377e))
|
||||||
|
* **deps:** update dependency undici to v8.0.1 ([#5440](https://github.com/homarr-labs/homarr/issues/5440)) ([a0e0083](https://github.com/homarr-labs/homarr/commit/a0e0083b16ea6ce42a0e3594ad53321210c98141))
|
||||||
|
* **deps:** update dependency undici to v8.0.2 ([#5446](https://github.com/homarr-labs/homarr/issues/5446)) ([75b36bb](https://github.com/homarr-labs/homarr/commit/75b36bb1609a0ea1a97158f4674ebf8f4c5f205e))
|
||||||
|
* **deps:** update nextjs monorepo to v16.2.2 ([#5418](https://github.com/homarr-labs/homarr/issues/5418)) ([8597470](https://github.com/homarr-labs/homarr/commit/859747037b4a0caca9e208e0b4ebce638b6c5d4e))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.96.0 ([#5410](https://github.com/homarr-labs/homarr/issues/5410)) ([8b1e3ba](https://github.com/homarr-labs/homarr/commit/8b1e3babc02d8ab6a7db9fd85325a0b92813ce8c))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.96.1 ([#5421](https://github.com/homarr-labs/homarr/issues/5421)) ([72ad935](https://github.com/homarr-labs/homarr/commit/72ad935ddf831c008e53ae3a1c5e924cc82710d1))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.96.2 ([#5442](https://github.com/homarr-labs/homarr/issues/5442)) ([b619ba1](https://github.com/homarr-labs/homarr/commit/b619ba1d46768cc3ed1d3739e0f379e0059bc2d9))
|
||||||
|
* **deps:** update tiptap monorepo to v3.22.0 ([#5414](https://github.com/homarr-labs/homarr/issues/5414)) ([402d948](https://github.com/homarr-labs/homarr/commit/402d948fe7d36406b7a75a4e4861330c6bcaa78b))
|
||||||
|
* **deps:** update tiptap monorepo to v3.22.1 ([#5434](https://github.com/homarr-labs/homarr/issues/5434)) ([2baab54](https://github.com/homarr-labs/homarr/commit/2baab5402d79caba0e1b3c53f52fdf0cbeb6875d))
|
||||||
|
* **deps:** update tiptap monorepo to v3.22.2 ([#5448](https://github.com/homarr-labs/homarr/issues/5448)) ([8cab4b0](https://github.com/homarr-labs/homarr/commit/8cab4b03333050376ae22a14eaaa1c0f045992d3))
|
||||||
|
* **deps:** update turbo monorepo to ^2.9.1 ([#5408](https://github.com/homarr-labs/homarr/issues/5408)) ([7607140](https://github.com/homarr-labs/homarr/commit/7607140f94f5b19a6b9cbacc7deabd2afc004d24))
|
||||||
|
* **deps:** update turbo monorepo to ^2.9.2 ([#5415](https://github.com/homarr-labs/homarr/issues/5415)) ([d8480c0](https://github.com/homarr-labs/homarr/commit/d8480c05a63525dc655a73675c2a9eec575a9539))
|
||||||
|
* **deps:** update turbo monorepo to ^2.9.3 ([#5419](https://github.com/homarr-labs/homarr/issues/5419)) ([dfa8793](https://github.com/homarr-labs/homarr/commit/dfa87939f200719af9f34e0e409895cf6e5a9c13))
|
||||||
|
* **logs:** missing fields from top level error with metadata in output ([#5456](https://github.com/homarr-labs/homarr/issues/5456)) ([41a03ef](https://github.com/homarr-labs/homarr/commit/41a03ef4e03675c5adf1171be152a767679bc466))
|
||||||
|
* **security:** remove salt from database ([#5412](https://github.com/homarr-labs/homarr/issues/5412)) ([2454cac](https://github.com/homarr-labs/homarr/commit/2454cac7be14207f822bf3e38f9a7fd261645dc5))
|
||||||
|
|
||||||
|
## [1.58.1](https://github.com/homarr-labs/homarr/compare/v1.58.0...v1.58.1) (2026-04-04)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* arabic text in english translations ([a86cdb9](https://github.com/homarr-labs/homarr/commit/a86cdb9fb91a10c8250f849e70c17777c18ada77))
|
||||||
|
|
||||||
## [1.58.0](https://github.com/homarr-labs/homarr/compare/v1.57.1...v1.58.0) (2026-04-03)
|
## [1.58.0](https://github.com/homarr-labs/homarr/compare/v1.57.1...v1.58.0) (2026-04-03)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
+57
-56
@@ -15,10 +15,10 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "catalog:",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "catalog:",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "catalog:",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "catalog:",
|
||||||
"@homarr/analytics": "workspace:^0.1.0",
|
"@homarr/analytics": "workspace:^0.1.0",
|
||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"@homarr/auth": "workspace:^0.1.0",
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"@homarr/docker": "workspace:^0.1.0",
|
"@homarr/docker": "workspace:^0.1.0",
|
||||||
"@homarr/form": "workspace:^0.1.0",
|
"@homarr/form": "workspace:^0.1.0",
|
||||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||||
"@homarr/gridstack": "^1.12.0",
|
"@homarr/gridstack": "catalog:",
|
||||||
"@homarr/icons": "workspace:^0.1.0",
|
"@homarr/icons": "workspace:^0.1.0",
|
||||||
"@homarr/image-proxy": "workspace:^0.1.0",
|
"@homarr/image-proxy": "workspace:^0.1.0",
|
||||||
"@homarr/integrations": "workspace:^0.1.0",
|
"@homarr/integrations": "workspace:^0.1.0",
|
||||||
@@ -48,61 +48,62 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/colors-generator": "^8.3.18",
|
"@mantine/colors-generator": "catalog:",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dropzone": "^8.3.18",
|
"@mantine/dropzone": "catalog:",
|
||||||
"@mantine/hooks": "^8.3.18",
|
"@mantine/hooks": "catalog:",
|
||||||
"@mantine/modals": "^8.3.18",
|
"@mantine/modals": "catalog:",
|
||||||
"@mantine/tiptap": "^8.3.18",
|
"@mantine/tiptap": "catalog:",
|
||||||
"@million/lint": "1.0.14",
|
"@million/lint": "catalog:",
|
||||||
"@tabler/icons-react": "^3.41.0",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tanstack/react-query": "^5.95.2",
|
"@tanstack/react-query": "catalog:",
|
||||||
"@tanstack/react-query-devtools": "^5.95.2",
|
"@tanstack/react-query-devtools": "catalog:",
|
||||||
"@tanstack/react-query-next-experimental": "^5.95.2",
|
"@tanstack/react-query-next-experimental": "catalog:",
|
||||||
"@trpc/client": "^11.16.0",
|
"@trpc/client": "catalog:",
|
||||||
"@trpc/next": "^11.16.0",
|
"@trpc/next": "catalog:",
|
||||||
"@trpc/react-query": "^11.16.0",
|
"@trpc/react-query": "catalog:",
|
||||||
"@trpc/server": "^11.16.0",
|
"@trpc/server": "catalog:",
|
||||||
"@xterm/addon-canvas": "^0.7.0",
|
"@xterm/addon-canvas": "catalog:",
|
||||||
"@xterm/addon-fit": "0.11.0",
|
"@xterm/addon-fit": "catalog:",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "catalog:",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "catalog:",
|
||||||
"chroma-js": "^3.2.0",
|
"chroma-js": "catalog:",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "catalog:",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "catalog:",
|
||||||
"flag-icons": "^7.5.0",
|
"flag-icons": "catalog:",
|
||||||
"glob": "^13.0.6",
|
"glob": "catalog:",
|
||||||
"isomorphic-dompurify": "^3.7.1",
|
"isomorphic-dompurify": "catalog:",
|
||||||
"jotai": "^2.19.0",
|
"jotai": "catalog:",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "catalog:",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"react-error-boundary": "^6.1.1",
|
"react-error-boundary": "catalog:",
|
||||||
"react-simple-code-editor": "^0.14.1",
|
"react-simple-code-editor": "catalog:",
|
||||||
"sass": "^1.98.0",
|
"sass": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"swagger-ui-react": "^5.32.1",
|
"swagger-ui-react": "catalog:",
|
||||||
"use-deep-compare-effect": "^1.8.1",
|
"use-deep-compare-effect": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"yaml": "catalog:",
|
||||||
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/chroma-js": "3.1.2",
|
"@types/chroma-js": "catalog:",
|
||||||
"@types/node": "^24.12.0",
|
"@types/node": "catalog:",
|
||||||
"@types/prismjs": "^1.26.6",
|
"@types/prismjs": "catalog:",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "catalog:",
|
||||||
"@types/swagger-ui-react": "^5.18.0",
|
"@types/swagger-ui-react": "catalog:",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"node-loader": "^2.1.0",
|
"node-loader": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
.contributorCard {
|
.contributorCard {
|
||||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dependencyCard:hover {
|
||||||
|
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||||
|
}
|
||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
Kbd,
|
Kbd,
|
||||||
List,
|
SimpleGrid,
|
||||||
ListItem,
|
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableTbody,
|
TableTbody,
|
||||||
@@ -22,9 +21,11 @@ import {
|
|||||||
TableThead,
|
TableThead,
|
||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
Title,
|
Title,
|
||||||
|
UnstyledButton,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconKeyboard, IconLanguage, IconLibrary, IconUsers } from "@tabler/icons-react";
|
import { IconKeyboard, IconLanguage, IconLibrary, IconPackage, IconUsers } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { capitalize, objectEntries } from "@homarr/common";
|
import { capitalize, objectEntries } from "@homarr/common";
|
||||||
import { hotkeys } from "@homarr/definitions";
|
import { hotkeys } from "@homarr/definitions";
|
||||||
@@ -33,7 +34,8 @@ import { getScopedI18n } from "@homarr/translation/server";
|
|||||||
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
|
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
|
||||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||||
import { createMetaTitle } from "~/metadata";
|
import { createMetaTitle } from "~/metadata";
|
||||||
import { getPackageAttributesAsync } from "~/versions/package-reader";
|
import type { PackageJsonDependencies } from "~/versions/package-reader";
|
||||||
|
import { getPackageVersion } from "~/versions/package-reader";
|
||||||
import type githubContributorsJson from "../../../../../../../static-data/contributors.json";
|
import type githubContributorsJson from "../../../../../../../static-data/contributors.json";
|
||||||
import type crowdinContributorsJson from "../../../../../../../static-data/translators.json";
|
import type crowdinContributorsJson from "../../../../../../../static-data/translators.json";
|
||||||
import classes from "./about.module.css";
|
import classes from "./about.module.css";
|
||||||
@@ -57,7 +59,10 @@ const getHostAsync = async () => {
|
|||||||
export default async function AboutPage() {
|
export default async function AboutPage() {
|
||||||
const baseServerUrl = `http://${await getHostAsync()}`;
|
const baseServerUrl = `http://${await getHostAsync()}`;
|
||||||
const t = await getScopedI18n("management.page.about");
|
const t = await getScopedI18n("management.page.about");
|
||||||
const attributes = await getPackageAttributesAsync();
|
const version = getPackageVersion();
|
||||||
|
const dependencies = (await fetch(`${baseServerUrl}/api/about/dependencies`).then((res) =>
|
||||||
|
res.json(),
|
||||||
|
)) as PackageJsonDependencies;
|
||||||
const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>
|
const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>
|
||||||
res.json(),
|
res.json(),
|
||||||
)) as typeof githubContributorsJson;
|
)) as typeof githubContributorsJson;
|
||||||
@@ -76,7 +81,7 @@ export default async function AboutPage() {
|
|||||||
<Title order={1} tt="uppercase">
|
<Title order={1} tt="uppercase">
|
||||||
Homarr
|
Homarr
|
||||||
</Title>
|
</Title>
|
||||||
<Title order={2}>{t("version", { version: attributes.version })}</Title>
|
<Title order={2}>{t("version", { version })}</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
</Center>
|
</Center>
|
||||||
@@ -137,25 +142,42 @@ export default async function AboutPage() {
|
|||||||
<Text>{t("accordion.libraries.title")}</Text>
|
<Text>{t("accordion.libraries.title")}</Text>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{t("accordion.libraries.subtitle", {
|
{t("accordion.libraries.subtitle", {
|
||||||
count: String(Object.keys(attributes.dependencies).length),
|
count: String(Object.keys(dependencies).length),
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</AccordionControl>
|
</AccordionControl>
|
||||||
<AccordionPanel>
|
<AccordionPanel>
|
||||||
<List>
|
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing="md">
|
||||||
{Object.entries(attributes.dependencies)
|
{Object.entries(dependencies)
|
||||||
|
.filter(([, value]) => !value.includes("workspace:"))
|
||||||
.sort(([key1], [key2]) => key1.localeCompare(key2))
|
.sort(([key1], [key2]) => key1.localeCompare(key2))
|
||||||
.map(([key, value]) => (
|
.map(([name, version]) => (
|
||||||
<ListItem key={key}>
|
<UnstyledButton
|
||||||
{value.includes("workspace:") ? (
|
key={name}
|
||||||
<Text>{key}</Text>
|
component="a"
|
||||||
) : (
|
href={`https://www.npmjs.com/package/${name}`}
|
||||||
<a href={`https://www.npmjs.com/package/${key}`}>{key}</a>
|
target="_blank"
|
||||||
)}
|
>
|
||||||
</ListItem>
|
<Card withBorder radius="md" p="xs" className={classes.dependencyCard}>
|
||||||
|
<Group gap="sm" wrap="nowrap">
|
||||||
|
<ThemeIcon variant="light" size="lg" radius="md">
|
||||||
|
<IconPackage size="1.5rem" stroke={1.5} />
|
||||||
|
</ThemeIcon>
|
||||||
|
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text size="sm" fw="bold" lineClamp={1} title={name}>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
v{version.replace("^", "").replace("~", "")}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</UnstyledButton>
|
||||||
))}
|
))}
|
||||||
</List>
|
</SimpleGrid>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="hotkeys">
|
<AccordionItem value="hotkeys">
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { getDependenciesAsync } from "../../../../versions/package-reader";
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const dependencies = await getDependenciesAsync();
|
||||||
|
return NextResponse.json(dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dynamic = "force-static";
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
import fsPromises from "fs/promises";
|
import fsPromises from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
|
import { parse as parseYaml } from "yaml";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
import packageJson from "../../../../package.json";
|
import packageJson from "../../../../package.json";
|
||||||
|
|
||||||
const getPackageVersion = () => packageJson.version;
|
export const getPackageVersion = () => packageJson.version;
|
||||||
const getDependenciesAsync = async (): Promise<PackageJsonDependencies> => {
|
export const getDependenciesAsync = async (): Promise<PackageJsonDependencies> => {
|
||||||
const pathNames = await glob("**/package.json", {
|
const pathNames = await glob("**/package.json", {
|
||||||
ignore: "node_modules/**",
|
ignore: "**/node_modules/**",
|
||||||
cwd: "../../",
|
cwd: "../../",
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
@@ -15,21 +19,37 @@ const getDependenciesAsync = async (): Promise<PackageJsonDependencies> => {
|
|||||||
.map((packageContent) => (JSON.parse(packageContent) as PackageJson).dependencies)
|
.map((packageContent) => (JSON.parse(packageContent) as PackageJson).dependencies)
|
||||||
.filter((dependencies) => dependencies !== undefined);
|
.filter((dependencies) => dependencies !== undefined);
|
||||||
|
|
||||||
|
const catalog = await parseDependencyCatalogsAsync();
|
||||||
|
|
||||||
let dependencies = {};
|
let dependencies = {};
|
||||||
for (const dependenciesOfPackage of packageDependencies) {
|
for (const dependenciesOfPackage of packageDependencies) {
|
||||||
dependencies = { ...dependencies, ...dependenciesOfPackage };
|
const resolvedDependencies = Object.entries(dependenciesOfPackage).map(([name, version]) => {
|
||||||
|
const catalogVersion = catalog.get(name);
|
||||||
|
return [name, catalogVersion ?? version] as const;
|
||||||
|
});
|
||||||
|
dependencies = { ...dependencies, ...Object.fromEntries(resolvedDependencies) };
|
||||||
}
|
}
|
||||||
return dependencies;
|
return dependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPackageAttributesAsync = async () => {
|
type DependencyCatalog = Map<string, string>;
|
||||||
return {
|
const parseDependencyCatalogsAsync = async (): Promise<DependencyCatalog> => {
|
||||||
version: getPackageVersion(),
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
dependencies: await getDependenciesAsync(),
|
const workspaceConfigContent = await fsPromises.readFile(
|
||||||
};
|
path.join(currentDir, "..", "..", "..", "..", "pnpm-workspace.yaml"),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
const workspaceConfig: unknown = parseYaml(workspaceConfigContent);
|
||||||
|
const parseResult = workspaceConfigCatalogSchema.parse(workspaceConfig);
|
||||||
|
|
||||||
|
return new Map(Object.entries(parseResult.catalog ?? {}));
|
||||||
};
|
};
|
||||||
|
|
||||||
type PackageJsonDependencies = Record<string, string>;
|
const workspaceConfigCatalogSchema = z.object({
|
||||||
|
catalog: z.record(z.string(), z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PackageJsonDependencies = Record<string, string>;
|
||||||
interface PackageJson {
|
interface PackageJson {
|
||||||
dependencies: PackageJsonDependencies | undefined;
|
dependencies: PackageJsonDependencies | undefined;
|
||||||
}
|
}
|
||||||
+12
-12
@@ -37,22 +37,22 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "catalog:",
|
||||||
"fastify": "^5.8.4",
|
"fastify": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"undici": "7.24.6"
|
"undici": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "^24.12.0",
|
"@types/node": "catalog:",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "catalog:",
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"tsx": "4.20.4",
|
"tsx": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "catalog:",
|
||||||
"tsx": "4.20.4",
|
"tsx": "catalog:",
|
||||||
"ws": "^8.20.0"
|
"ws": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "catalog:",
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+38
-24
@@ -64,6 +64,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
<a href="https://homarr.dev/docs/integrations/anchor" target="_blank" rel="noreferrer noopener">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/anchor.svg" alt="Anchor" width="90" height="90" />
|
||||||
|
<br/>
|
||||||
|
<p align="center">Anchor</p>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/aria2" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/aria2" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg" alt="Aria2" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg" alt="Aria2" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -97,15 +104,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Deluge</p>
|
<p align="center">Deluge</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/docker-hub" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/docker-hub" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg" alt="Docker Hub" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg" alt="Docker Hub" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">Docker<br/>Hub</p>
|
<p align="center">Docker<br/>Hub</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/emby" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/emby" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg" alt="Emby" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg" alt="Emby" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -146,15 +153,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Home<br/>Assistant</p>
|
<p align="center">Home<br/>Assistant</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/ical" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/ical" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg" alt="iCal" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg" alt="iCal" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">iCal</p>
|
<p align="center">iCal</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/immich" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/immich" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/immich.svg" alt="Immich" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/immich.svg" alt="Immich" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -195,15 +202,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Nextcloud</p>
|
<p align="center">Nextcloud</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/npm" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/npm" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg" alt="NPM" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg" alt="NPM" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">NPM</p>
|
<p align="center">NPM</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/ntfy" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/ntfy" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg" alt="ntfy" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg" alt="ntfy" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -244,15 +251,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Pi-hole</p>
|
<p align="center">Pi-hole</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/plex" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/plex" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg" alt="Plex" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg" alt="Plex" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">Plex</p>
|
<p align="center">Plex</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/prowlarr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/prowlarr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg" alt="Prowlarr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg" alt="Prowlarr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -293,15 +300,15 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Readarr</p>
|
<p align="center">Readarr</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/sabnzbd" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/sabnzbd" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg" alt="SABnzbd" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg" alt="SABnzbd" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">SABnzbd</p>
|
<p align="center">SABnzbd</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/search-ch" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/search-ch" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png" alt="Search.ch" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png" alt="Search.ch" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -330,13 +337,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
<a href="https://homarr.dev/docs/integrations/speedtest-tracker" target="_blank" rel="noreferrer noopener">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png" alt="Speedtest Tracker" width="90" height="90" />
|
||||||
|
<br/>
|
||||||
|
<p align="center">Speedtest<br/>Tracker</p>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/tdarr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/tdarr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png" alt="Tdarr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png" alt="Tdarr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
<p align="center">Tdarr</p>
|
<p align="center">Tdarr</p>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
<td align="center">
|
<tr><td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/tracearr" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/tracearr" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/tracearr.svg" alt="Tracearr" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/tracearr.svg" alt="Tracearr" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
@@ -349,8 +363,8 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<p align="center">Transmission</p>
|
<p align="center">Transmission</p>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr><td align="center">
|
<td align="center">
|
||||||
<a href="https://homarr.dev/docs/integrations/truenas" target="_blank" rel="noreferrer noopener">
|
<a href="https://homarr.dev/docs/integrations/truenas" target="_blank" rel="noreferrer noopener">
|
||||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg" alt="TrueNAS" width="90" height="90" />
|
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg" alt="TrueNAS" width="90" height="90" />
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
+29
-29
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homarr",
|
"name": "homarr",
|
||||||
"version": "1.58.0",
|
"version": "1.59.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env CI=true turbo build",
|
"build": "cross-env CI=true turbo build",
|
||||||
@@ -36,28 +36,28 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "catalog:",
|
||||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
"@semantic-release/commit-analyzer": "catalog:",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "catalog:",
|
||||||
"@semantic-release/github": "^12.0.6",
|
"@semantic-release/github": "catalog:",
|
||||||
"@semantic-release/npm": "^13.1.5",
|
"@semantic-release/npm": "catalog:",
|
||||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
"@semantic-release/release-notes-generator": "catalog:",
|
||||||
"@testcontainers/redis": "^11.13.0",
|
"@testcontainers/redis": "catalog:",
|
||||||
"@turbo/gen": "^2.8.21",
|
"@turbo/gen": "catalog:",
|
||||||
"@vitejs/plugin-react": "^5.2.0",
|
"@vitejs/plugin-react": "catalog:",
|
||||||
"@vitest/coverage-v8": "^4.1.2",
|
"@vitest/coverage-v8": "catalog:",
|
||||||
"@vitest/ui": "^4.1.2",
|
"@vitest/ui": "catalog:",
|
||||||
"conventional-changelog-conventionalcommits": "^9.3.1",
|
"conventional-changelog-conventionalcommits": "catalog:",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "catalog:",
|
||||||
"jsdom": "^29.0.1",
|
"jsdom": "catalog:",
|
||||||
"json5": "^2.2.3",
|
"json5": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"semantic-release": "^25.0.3",
|
"semantic-release": "catalog:",
|
||||||
"testcontainers": "^11.13.0",
|
"testcontainers": "catalog:",
|
||||||
"turbo": "^2.8.21",
|
"turbo": "catalog:",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "catalog:",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
"vite-tsconfig-paths": "catalog:",
|
||||||
"vitest": "^4.1.2"
|
"vitest": "catalog:"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.0",
|
"packageManager": "pnpm@10.33.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -79,23 +79,23 @@
|
|||||||
"overrides": {
|
"overrides": {
|
||||||
"@babel/helpers@<7.26.10": ">=7.29.2",
|
"@babel/helpers@<7.26.10": ">=7.29.2",
|
||||||
"@babel/runtime@<7.26.10": ">=7.29.2",
|
"@babel/runtime@<7.26.10": ">=7.29.2",
|
||||||
"axios@>=1.0.0 <1.8.2": ">=1.14.0",
|
"axios@>=1.0.0 <1.8.2": ">=1.15.0",
|
||||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.5",
|
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.5",
|
||||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.5",
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.5",
|
||||||
"esbuild@<=0.24.2": ">=0.27.4",
|
"esbuild@<=0.24.2": ">=0.28.0",
|
||||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||||
"hono@<4.6.5": ">=4.12.9",
|
"hono@<4.6.5": ">=4.12.12",
|
||||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.7",
|
"nanoid@>=4.0.0 <5.0.9": ">=5.1.7",
|
||||||
"prismjs@<1.30.0": ">=1.30.0",
|
"prismjs@<1.30.0": ">=1.30.0",
|
||||||
"proxmox-api>undici": "7.24.6",
|
"proxmox-api>undici": "7.24.6",
|
||||||
"react-is": "^19.2.4",
|
"react-is": "^19.2.5",
|
||||||
"rollup@>=4.0.0 <4.22.4": ">=4.60.0",
|
"rollup@>=4.0.0 <4.22.4": ">=4.60.1",
|
||||||
"sha.js@<=2.4.11": ">=2.4.12",
|
"sha.js@<=2.4.11": ">=2.4.12",
|
||||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2",
|
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2",
|
||||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2",
|
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2",
|
||||||
"tmp@<=0.2.3": ">=0.2.5",
|
"tmp@<=0.2.3": ">=0.2.5",
|
||||||
"vite@>=5.0.0 <=5.4.18": ">=8.0.3"
|
"vite@>=5.0.0 <=5.4.18": ">=8.0.8"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||||
|
|||||||
@@ -28,14 +28,14 @@
|
|||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/docker": "workspace:^0.1.0",
|
"@homarr/docker": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@umami/node": "^0.4.0",
|
"@umami/node": "catalog:",
|
||||||
"superjson": "2.2.6"
|
"superjson": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-16
@@ -40,26 +40,26 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@kubernetes/client-node": "^1.4.0",
|
"@kubernetes/client-node": "catalog:",
|
||||||
"@tanstack/react-query": "^5.95.2",
|
"@tanstack/react-query": "catalog:",
|
||||||
"@trpc/client": "^11.16.0",
|
"@trpc/client": "catalog:",
|
||||||
"@trpc/react-query": "^11.16.0",
|
"@trpc/react-query": "catalog:",
|
||||||
"@trpc/server": "^11.16.0",
|
"@trpc/server": "catalog:",
|
||||||
"@trpc/tanstack-react-query": "^11.16.0",
|
"@trpc/tanstack-react-query": "catalog:",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"trpc-to-openapi": "^3.2.0",
|
"trpc-to-openapi": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
import { hashPasswordAsync } from "@homarr/auth";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { generateSecureRandomToken } from "@homarr/common/server";
|
import { generateSecureRandomToken } from "@homarr/common/server";
|
||||||
import { db, eq } from "@homarr/db";
|
import { db, eq } from "@homarr/db";
|
||||||
@@ -14,7 +14,6 @@ export const apiKeysRouter = createTRPCRouter({
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
apiKey: false,
|
apiKey: false,
|
||||||
salt: false,
|
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
user: {
|
user: {
|
||||||
@@ -29,14 +28,12 @@ export const apiKeysRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
create: permissionRequiredProcedure.requiresPermission("admin").mutation(async ({ ctx }) => {
|
create: permissionRequiredProcedure.requiresPermission("admin").mutation(async ({ ctx }) => {
|
||||||
const salt = await createSaltAsync();
|
|
||||||
const randomToken = generateSecureRandomToken(64);
|
const randomToken = generateSecureRandomToken(64);
|
||||||
const hashedRandomToken = await hashPasswordAsync(randomToken, salt);
|
const hashedRandomToken = await hashPasswordAsync(randomToken);
|
||||||
const id = createId();
|
const id = createId();
|
||||||
await db.insert(apiKeys).values({
|
await db.insert(apiKeys).values({
|
||||||
id,
|
id,
|
||||||
apiKey: hashedRandomToken,
|
apiKey: hashedRandomToken,
|
||||||
salt,
|
|
||||||
userId: ctx.session.user.id,
|
userId: ctx.session.user.id,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ async function setupUrlRestrictionAsync(dbUrls: string[]) {
|
|||||||
hideDescription: false,
|
hideDescription: false,
|
||||||
textLinesClamp: 3,
|
textLinesClamp: 3,
|
||||||
maximumAmountPosts: 5,
|
maximumAmountPosts: 5,
|
||||||
|
showPosterImage: true,
|
||||||
} satisfies WidgetComponentProps<"rssFeed">["options"]),
|
} satisfies WidgetComponentProps<"rssFeed">["options"]),
|
||||||
}) satisfies InferInsertModel<typeof items>,
|
}) satisfies InferInsertModel<typeof items>,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { MySqlRawQueryResult } from "drizzle-orm/mysql2";
|
|||||||
import type { QueryResult } from "pg";
|
import type { QueryResult } from "pg";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
import { comparePasswordsAsync, hashPasswordAsync } from "@homarr/auth";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
@@ -11,8 +11,8 @@ import { and, eq, handleTransactionsAsync, like } from "@homarr/db";
|
|||||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||||
import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema";
|
import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema";
|
||||||
import { selectUserSchema } from "@homarr/db/validationSchemas";
|
import { selectUserSchema } from "@homarr/db/validationSchemas";
|
||||||
import { credentialsAdminGroup } from "@homarr/definitions";
|
|
||||||
import type { SupportedAuthProvider } from "@homarr/definitions";
|
import type { SupportedAuthProvider } from "@homarr/definitions";
|
||||||
|
import { credentialsAdminGroup } from "@homarr/definitions";
|
||||||
import { byIdSchema } from "@homarr/validation/common";
|
import { byIdSchema } from "@homarr/validation/common";
|
||||||
import type { userBaseCreateSchema } from "@homarr/validation/user";
|
import type { userBaseCreateSchema } from "@homarr/validation/user";
|
||||||
import {
|
import {
|
||||||
@@ -92,15 +92,13 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.username);
|
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.username);
|
||||||
|
|
||||||
const salt = await createSaltAsync();
|
const hashedPassword = await hashPasswordAsync(input.password);
|
||||||
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
|
||||||
|
|
||||||
const userId = createId();
|
const userId = createId();
|
||||||
const user = {
|
const user = {
|
||||||
id: userId,
|
id: userId,
|
||||||
name: input.username,
|
name: input.username,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
salt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await handleTransactionsAsync(ctx.db, {
|
await handleTransactionsAsync(ctx.db, {
|
||||||
@@ -393,7 +391,6 @@ export const userRouter = createTRPCRouter({
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
password: true,
|
password: true,
|
||||||
salt: true,
|
|
||||||
provider: true,
|
provider: true,
|
||||||
},
|
},
|
||||||
where: eq(users.id, input.userId),
|
where: eq(users.id, input.userId),
|
||||||
@@ -423,8 +420,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isPreviousPasswordRequired) {
|
if (isPreviousPasswordRequired) {
|
||||||
const previousPasswordHash = await hashPasswordAsync(input.previousPassword, dbUser.salt ?? "");
|
const isValid = await comparePasswordsAsync(input.previousPassword, dbUser.password ?? "");
|
||||||
const isValid = previousPasswordHash === dbUser.password;
|
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -434,8 +430,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const salt = await createSaltAsync();
|
const hashedPassword = await hashPasswordAsync(input.password);
|
||||||
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
|
||||||
await ctx.db
|
await ctx.db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({
|
.set({
|
||||||
@@ -583,8 +578,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => {
|
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => {
|
||||||
const salt = await createSaltAsync();
|
const hashedPassword = await hashPasswordAsync(input.password);
|
||||||
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
|
||||||
|
|
||||||
const userId = createId();
|
const userId = createId();
|
||||||
await db.insert(users).values({
|
await db.insert(users).values({
|
||||||
@@ -592,7 +586,6 @@ const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBase
|
|||||||
name: input.username,
|
name: input.username,
|
||||||
email: input.email,
|
email: input.email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
salt,
|
|
||||||
});
|
});
|
||||||
return userId;
|
return userId;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { ResponseError } from "@homarr/common/server";
|
||||||
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
|
import { anchorNotesListInputSchema, anchorNoteUpdateInputSchema, createIntegrationAsync } from "@homarr/integrations";
|
||||||
|
import { anchorNoteRequestHandler, anchorNotesListRequestHandler } from "@homarr/request-handler/anchor-notes";
|
||||||
|
|
||||||
|
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
|
||||||
|
|
||||||
|
const noteIdInput = z.object({
|
||||||
|
noteId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = createLogger({ module: "anchorNotesRouter" });
|
||||||
|
|
||||||
|
const isJsonDeltaString = (value: string) => {
|
||||||
|
try {
|
||||||
|
const parsed: unknown = JSON.parse(value);
|
||||||
|
return typeof parsed === "object" && parsed !== null && Array.isArray((parsed as { ops?: unknown }).ops);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeAnchorContent = (content: string | undefined) => {
|
||||||
|
if (content === undefined) return undefined;
|
||||||
|
|
||||||
|
const trimmed = content.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return JSON.stringify({ ops: [{ insert: "\n" }] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJsonDeltaString(content)) return content;
|
||||||
|
|
||||||
|
const normalizedText = content.endsWith("\n") ? content : `${content}\n`;
|
||||||
|
return JSON.stringify({ ops: [{ insert: normalizedText }] });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const anchorNotesRouter = createTRPCRouter({
|
||||||
|
listNotes: publicProcedure
|
||||||
|
.concat(createOneIntegrationMiddleware("query", "anchor"))
|
||||||
|
.input(anchorNotesListInputSchema)
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const handler = anchorNotesListRequestHandler.handler(ctx.integration, { ...input, limit: input.limit ?? 50 });
|
||||||
|
|
||||||
|
const { data } = await handler.getCachedOrUpdatedDataAsync({
|
||||||
|
forceUpdate: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
|
getNote: publicProcedure
|
||||||
|
.concat(createOneIntegrationMiddleware("query", "anchor"))
|
||||||
|
.input(noteIdInput)
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const handler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId });
|
||||||
|
|
||||||
|
const { data } = await handler.getCachedOrUpdatedDataAsync({
|
||||||
|
forceUpdate: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
|
updateNote: protectedProcedure
|
||||||
|
.concat(createOneIntegrationMiddleware("interact", "anchor"))
|
||||||
|
.input(anchorNoteUpdateInputSchema)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const integrationInstance = await createIntegrationAsync(ctx.integration);
|
||||||
|
const normalizedContent = normalizeAnchorContent(input.content);
|
||||||
|
const isContentNormalized =
|
||||||
|
input.content !== undefined && normalizedContent !== undefined && input.content !== normalizedContent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedNote = await integrationInstance.updateNoteAsync({
|
||||||
|
noteId: input.noteId,
|
||||||
|
...(input.title !== undefined ? { title: input.title } : {}),
|
||||||
|
...(normalizedContent !== undefined ? { content: normalizedContent } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const noteHandler = anchorNoteRequestHandler.handler(ctx.integration, { noteId: input.noteId });
|
||||||
|
await noteHandler.invalidateAsync();
|
||||||
|
|
||||||
|
return updatedNote;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ResponseError && error.statusCode === 400) {
|
||||||
|
logger.warn("Anchor update note failed validation", {
|
||||||
|
noteId: input.noteId,
|
||||||
|
integrationId: ctx.integration.id,
|
||||||
|
contentLength: input.content?.length ?? 0,
|
||||||
|
contentIsJsonDelta: input.content ? isJsonDeltaString(input.content) : false,
|
||||||
|
contentNormalized: isContentNormalized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ResponseError && error.statusCode === 403) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You do not have permission to edit this note",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ResponseError && error.statusCode === 404) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Note not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { lazy } from "@trpc/server";
|
|||||||
import { createTRPCRouter } from "../../trpc";
|
import { createTRPCRouter } from "../../trpc";
|
||||||
|
|
||||||
export const widgetRouter = createTRPCRouter({
|
export const widgetRouter = createTRPCRouter({
|
||||||
|
anchorNotes: lazy(() => import("./anchor-notes").then((mod) => mod.anchorNotesRouter)),
|
||||||
coolify: lazy(() => import("./coolify").then((mod) => mod.coolifyRouter)),
|
coolify: lazy(() => import("./coolify").then((mod) => mod.coolifyRouter)),
|
||||||
immich: lazy(() => import("./immich").then((mod) => mod.immichRouter)),
|
immich: lazy(() => import("./immich").then((mod) => mod.immichRouter)),
|
||||||
notebook: lazy(() => import("./notebook").then((mod) => mod.notebookRouter)),
|
notebook: lazy(() => import("./notebook").then((mod) => mod.notebookRouter)),
|
||||||
@@ -28,4 +29,5 @@ export const widgetRouter = createTRPCRouter({
|
|||||||
notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)),
|
notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)),
|
||||||
timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)),
|
timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)),
|
||||||
tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)),
|
tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)),
|
||||||
|
speedtestTracker: lazy(() => import("./speedtest-tracker").then((mod) => mod.speedtestTrackerRouter)),
|
||||||
});
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { observable } from "@trpc/server/observable";
|
||||||
import SuperJSON from "superjson";
|
import SuperJSON from "superjson";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
@@ -5,45 +6,59 @@ import type { Session } from "@homarr/auth";
|
|||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
import { eq } from "@homarr/db";
|
import { eq } from "@homarr/db";
|
||||||
import { items, users } from "@homarr/db/schema";
|
import { items, users } from "@homarr/db/schema";
|
||||||
|
import type { ExtendedFeedEntry } from "@homarr/request-handler/rss-feeds";
|
||||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||||
|
|
||||||
import type { WidgetComponentProps } from "../../../../widgets/src";
|
import type { WidgetComponentProps } from "../../../../widgets/src";
|
||||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||||
|
|
||||||
|
const feedsInput = z.object({
|
||||||
|
urls: z.array(z.string()).max(100),
|
||||||
|
maximumAmountPosts: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
export const rssFeedRouter = createTRPCRouter({
|
export const rssFeedRouter = createTRPCRouter({
|
||||||
getFeeds: publicProcedure
|
getFeeds: publicProcedure.input(feedsInput).query(async ({ ctx, input }) => {
|
||||||
.input(
|
const urls = (await canAccessAllFeedsAsync(ctx.db, ctx.session))
|
||||||
z.object({
|
? input.urls
|
||||||
urls: z.array(z.string()).max(100),
|
: await restrictUrlsAsync(ctx.db, input.urls);
|
||||||
maximumAmountPosts: z.number(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const urls = (await canAccessAllFeedsAsync(ctx.db, ctx.session))
|
|
||||||
? input.urls
|
|
||||||
: await restrictUrlsAsync(ctx.db, input.urls);
|
|
||||||
|
|
||||||
const rssFeeds = await Promise.all(
|
const rssFeeds = await Promise.all(
|
||||||
urls.map(async (url) => {
|
urls.map(async (url) => {
|
||||||
const innerHandler = rssFeedsRequestHandler.handler({
|
const innerHandler = rssFeedsRequestHandler.handler({
|
||||||
url,
|
url,
|
||||||
count: input.maximumAmountPosts,
|
count: input.maximumAmountPosts,
|
||||||
});
|
|
||||||
return await innerHandler.getCachedOrUpdatedDataAsync({
|
|
||||||
forceUpdate: false,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return rssFeeds
|
|
||||||
.flatMap((rssFeed) => rssFeed.data.entries)
|
|
||||||
.slice(0, input.maximumAmountPosts)
|
|
||||||
.sort((entryA, entryB) => {
|
|
||||||
return entryA.published && entryB.published
|
|
||||||
? new Date(entryB.published).getTime() - new Date(entryA.published).getTime()
|
|
||||||
: 0;
|
|
||||||
});
|
});
|
||||||
}),
|
return await innerHandler.getCachedOrUpdatedDataAsync({
|
||||||
|
forceUpdate: false,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return rssFeeds
|
||||||
|
.flatMap((rssFeed) => rssFeed.data.entries)
|
||||||
|
.slice(0, input.maximumAmountPosts)
|
||||||
|
.sort((entryA, entryB) => {
|
||||||
|
return entryA.published && entryB.published
|
||||||
|
? new Date(entryB.published).getTime() - new Date(entryA.published).getTime()
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
subscribeFeeds: publicProcedure.input(feedsInput).subscription(({ input }) => {
|
||||||
|
// It is not necessary to check access to all feeds here, as the client will only subscribe to feeds which already exist
|
||||||
|
return observable<{ url: string; entries: ExtendedFeedEntry[] }>((emit) => {
|
||||||
|
const unsubscribes = input.urls.map((url) => {
|
||||||
|
const innerHandler = rssFeedsRequestHandler.handler({ url, count: input.maximumAmountPosts });
|
||||||
|
return innerHandler.subscribe((data) => {
|
||||||
|
emit.next({ url, entries: data.entries });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function canAccessAllFeedsAsync(db: Database, session: Session | null) {
|
export async function canAccessAllFeedsAsync(db: Database, session: Session | null) {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { observable } from "@trpc/server/observable";
|
||||||
|
|
||||||
|
import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types";
|
||||||
|
import { speedtestTrackerRequestHandler } from "@homarr/request-handler/speedtest-tracker";
|
||||||
|
|
||||||
|
import { createManyIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
|
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||||
|
|
||||||
|
export const speedtestTrackerRouter = createTRPCRouter({
|
||||||
|
getDashboard: publicProcedure
|
||||||
|
.concat(createManyIntegrationMiddleware("query", "speedtestTracker"))
|
||||||
|
.query(async ({ ctx }) => {
|
||||||
|
const results = await Promise.all(
|
||||||
|
ctx.integrations.map(async (integration) => {
|
||||||
|
const innerHandler = speedtestTrackerRequestHandler.handler(integration, {});
|
||||||
|
const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||||
|
|
||||||
|
return {
|
||||||
|
integrationId: integration.id,
|
||||||
|
integrationName: integration.name,
|
||||||
|
integrationUrl: integration.url,
|
||||||
|
dashboard: data,
|
||||||
|
updatedAt: timestamp,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}),
|
||||||
|
|
||||||
|
subscribeToDashboard: publicProcedure
|
||||||
|
.concat(createManyIntegrationMiddleware("query", "speedtestTracker"))
|
||||||
|
.subscription(({ ctx }) => {
|
||||||
|
return observable<{
|
||||||
|
integrationId: string;
|
||||||
|
dashboard: SpeedtestTrackerDashboardData;
|
||||||
|
timestamp: Date;
|
||||||
|
}>((emit) => {
|
||||||
|
const unsubscribes = ctx.integrations.map((integration) => {
|
||||||
|
const innerHandler = speedtestTrackerRequestHandler.handler(integration, {});
|
||||||
|
return innerHandler.subscribe((dashboard) => {
|
||||||
|
emit.next({
|
||||||
|
integrationId: integration.id,
|
||||||
|
dashboard,
|
||||||
|
timestamp: new Date(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -5,7 +5,7 @@ import type { Database } from "@homarr/db";
|
|||||||
import { eq } from "@homarr/db";
|
import { eq } from "@homarr/db";
|
||||||
import { apiKeys } from "@homarr/db/schema";
|
import { apiKeys } from "@homarr/db/schema";
|
||||||
|
|
||||||
import { hashPasswordAsync } from "../security";
|
import { comparePasswordsAsync } from "../security";
|
||||||
import { createSessionAsync } from "../server";
|
import { createSessionAsync } from "../server";
|
||||||
|
|
||||||
const logger = createLogger({ module: "apiKeyAuth" });
|
const logger = createLogger({ module: "apiKeyAuth" });
|
||||||
@@ -41,7 +41,6 @@ export const getSessionFromApiKeyAsync = async (
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
apiKey: true,
|
apiKey: true,
|
||||||
salt: true,
|
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
user: {
|
user: {
|
||||||
@@ -60,9 +59,9 @@ export const getSessionFromApiKeyAsync = async (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashedApiKey = await hashPasswordAsync(apiKey, apiKeyFromDb.salt);
|
const isValid = await comparePasswordsAsync(apiKey, apiKeyFromDb.apiKey);
|
||||||
|
|
||||||
if (apiKeyFromDb.apiKey !== hashedApiKey) {
|
if (!isValid) {
|
||||||
logger.warn("Failed to authenticate with api-key", { ipAddress, userAgent, reason: "API_KEY_MISMATCH" });
|
logger.warn("Failed to authenticate with api-key", { ipAddress, userAgent, reason: "API_KEY_MISMATCH" });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createId } from "@homarr/common";
|
|||||||
import { apiKeys, users } from "@homarr/db/schema";
|
import { apiKeys, users } from "@homarr/db/schema";
|
||||||
import { createDb } from "@homarr/db/test";
|
import { createDb } from "@homarr/db/test";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "../../security";
|
import { hashPasswordAsync } from "../../security";
|
||||||
import { getSessionFromApiKeyAsync } from "../get-api-key-session";
|
import { getSessionFromApiKeyAsync } from "../get-api-key-session";
|
||||||
|
|
||||||
// Mock the logger to avoid console output during tests
|
// Mock the logger to avoid console output during tests
|
||||||
@@ -118,11 +118,9 @@ const setupAsync = async (options?: SetupOptions) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (options?.token) {
|
if (options?.token) {
|
||||||
const salt = await createSaltAsync();
|
|
||||||
await db.insert(apiKeys).values({
|
await db.insert(apiKeys).values({
|
||||||
id: defaultApiKeyId,
|
id: defaultApiKeyId,
|
||||||
apiKey: await hashPasswordAsync(options.token, salt),
|
apiKey: await hashPasswordAsync(options.token),
|
||||||
salt,
|
|
||||||
userId: defaultUserId,
|
userId: defaultUserId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -24,30 +24,30 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "^0.41.1",
|
"@auth/core": "catalog:",
|
||||||
"@auth/drizzle-adapter": "^1.11.1",
|
"@auth/drizzle-adapter": "catalog:",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "catalog:",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "catalog:",
|
||||||
"ldapts": "8.1.7",
|
"ldapts": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"next-auth": "5.0.0-beta.30",
|
"next-auth": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/bcrypt": "6.0.0",
|
"@types/bcrypt": "catalog:",
|
||||||
"@types/cookies": "0.9.2",
|
"@types/cookies": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import bcrypt from "bcrypt";
|
|
||||||
import type { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
@@ -7,6 +6,8 @@ import { and, eq } from "@homarr/db";
|
|||||||
import { users } from "@homarr/db/schema";
|
import { users } from "@homarr/db/schema";
|
||||||
import type { userSignInSchema } from "@homarr/validation/user";
|
import type { userSignInSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
|
import { comparePasswordsAsync } from "../../../security";
|
||||||
|
|
||||||
const logger = createLogger({ module: "basicAuthorization" });
|
const logger = createLogger({ module: "basicAuthorization" });
|
||||||
|
|
||||||
export const authorizeWithBasicCredentialsAsync = async (
|
export const authorizeWithBasicCredentialsAsync = async (
|
||||||
@@ -23,7 +24,7 @@ export const authorizeWithBasicCredentialsAsync = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info("User is trying to log in. Checking password...", { userName: user.name });
|
logger.info("User is trying to log in. Checking password...", { userName: user.name });
|
||||||
const isValidPassword = await bcrypt.compare(credentials.password, user.password);
|
const isValidPassword = await comparePasswordsAsync(credentials.password, user.password);
|
||||||
|
|
||||||
if (!isValidPassword) {
|
if (!isValidPassword) {
|
||||||
logger.warn("Password for user was incorrect", { userName: user.name });
|
logger.warn("Password for user was incorrect", { userName: user.name });
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createId } from "@homarr/common";
|
|||||||
import { users } from "@homarr/db/schema";
|
import { users } from "@homarr/db/schema";
|
||||||
import { createDb } from "@homarr/db/test";
|
import { createDb } from "@homarr/db/test";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "../../security";
|
import { hashPasswordAsync } from "../../security";
|
||||||
import { authorizeWithBasicCredentialsAsync } from "../credentials/authorization/basic-authorization";
|
import { authorizeWithBasicCredentialsAsync } from "../credentials/authorization/basic-authorization";
|
||||||
|
|
||||||
const defaultUserId = createId();
|
const defaultUserId = createId();
|
||||||
@@ -13,12 +13,10 @@ describe("authorizeWithBasicCredentials", () => {
|
|||||||
test("should authorize user with correct credentials", async () => {
|
test("should authorize user with correct credentials", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const db = createDb();
|
const db = createDb();
|
||||||
const salt = await createSaltAsync();
|
|
||||||
await db.insert(users).values({
|
await db.insert(users).values({
|
||||||
id: defaultUserId,
|
id: defaultUserId,
|
||||||
name: "test",
|
name: "test",
|
||||||
salt,
|
password: await hashPasswordAsync("test"),
|
||||||
password: await hashPasswordAsync("test", salt),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -34,12 +32,10 @@ describe("authorizeWithBasicCredentials", () => {
|
|||||||
test("should not authorize user with incorrect credentials", async () => {
|
test("should not authorize user with incorrect credentials", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const db = createDb();
|
const db = createDb();
|
||||||
const salt = await createSaltAsync();
|
|
||||||
await db.insert(users).values({
|
await db.insert(users).values({
|
||||||
id: defaultUserId,
|
id: defaultUserId,
|
||||||
name: "test",
|
name: "test",
|
||||||
salt,
|
password: await hashPasswordAsync("test"),
|
||||||
password: await hashPasswordAsync("test", salt),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -55,12 +51,10 @@ describe("authorizeWithBasicCredentials", () => {
|
|||||||
test("should not authorize user with incorrect username", async () => {
|
test("should not authorize user with incorrect username", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const db = createDb();
|
const db = createDb();
|
||||||
const salt = await createSaltAsync();
|
|
||||||
await db.insert(users).values({
|
await db.insert(users).values({
|
||||||
id: defaultUserId,
|
id: defaultUserId,
|
||||||
name: "test",
|
name: "test",
|
||||||
salt,
|
password: await hashPasswordAsync("test"),
|
||||||
password: await hashPasswordAsync("test", salt),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
export const createSaltAsync = async () => {
|
export const hashPasswordAsync = async (password: string) => {
|
||||||
return bcrypt.genSalt(10);
|
return bcrypt.hash(password, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hashPasswordAsync = async (password: string, salt: string) => {
|
/**
|
||||||
return bcrypt.hash(password, salt);
|
* Compares a plaintext password with a hashed password and returns whether they match.
|
||||||
|
* @param password plaintext password
|
||||||
|
* @param hash hashed password
|
||||||
|
* @returns wheter it is the same
|
||||||
|
*/
|
||||||
|
export const comparePasswordsAsync = async (password: string, hash: string) => {
|
||||||
|
return await bcrypt.compare(password, hash);
|
||||||
};
|
};
|
||||||
@@ -1,25 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "../security";
|
import { comparePasswordsAsync, hashPasswordAsync } from "../security";
|
||||||
|
|
||||||
describe("createSalt should return a salt", () => {
|
|
||||||
it("should return a salt", async () => {
|
|
||||||
const result = await createSaltAsync();
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result.length).toBeGreaterThan(25);
|
|
||||||
});
|
|
||||||
it("should return a different salt each time", async () => {
|
|
||||||
const result1 = await createSaltAsync();
|
|
||||||
const result2 = await createSaltAsync();
|
|
||||||
expect(result1).not.toEqual(result2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hashPassword should return a hash", () => {
|
describe("hashPassword should return a hash", () => {
|
||||||
it("should return a hash", async () => {
|
it("should return a hash", async () => {
|
||||||
const password = "password";
|
const password = "password";
|
||||||
const salt = await createSaltAsync();
|
const result = await hashPasswordAsync(password);
|
||||||
const result = await hashPasswordAsync(password, salt);
|
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
expect(result.length).toBeGreaterThan(55);
|
expect(result.length).toBeGreaterThan(55);
|
||||||
expect(result).not.toEqual(password);
|
expect(result).not.toEqual(password);
|
||||||
@@ -27,21 +13,44 @@ describe("hashPassword should return a hash", () => {
|
|||||||
it("should return a different hash each time", async () => {
|
it("should return a different hash each time", async () => {
|
||||||
const password = "password";
|
const password = "password";
|
||||||
const password2 = "another password";
|
const password2 = "another password";
|
||||||
const salt = await createSaltAsync();
|
|
||||||
|
|
||||||
const result1 = await hashPasswordAsync(password, salt);
|
const result1 = await hashPasswordAsync(password);
|
||||||
const result2 = await hashPasswordAsync(password2, salt);
|
const result2 = await hashPasswordAsync(password2);
|
||||||
|
|
||||||
expect(result1).not.toEqual(result2);
|
expect(result1).not.toEqual(result2);
|
||||||
});
|
});
|
||||||
it("should return a different hash for the same password with different salts", async () => {
|
it("should return a different hash for the same password with different salts", async () => {
|
||||||
const password = "password";
|
const password = "password";
|
||||||
const salt1 = await createSaltAsync();
|
|
||||||
const salt2 = await createSaltAsync();
|
|
||||||
|
|
||||||
const result1 = await hashPasswordAsync(password, salt1);
|
const result1 = await hashPasswordAsync(password);
|
||||||
const result2 = await hashPasswordAsync(password, salt2);
|
const result2 = await hashPasswordAsync(password);
|
||||||
|
|
||||||
expect(result1).not.toEqual(result2);
|
expect(result1).not.toEqual(result2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("comparePasswordsAsync should compare passwords correctly", () => {
|
||||||
|
it("should return true for matching passwords", async () => {
|
||||||
|
// Arrange
|
||||||
|
const password = "password";
|
||||||
|
const hash = await hashPasswordAsync(password);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await comparePasswordsAsync(password, hash);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
it("should return false for non-matching passwords", async () => {
|
||||||
|
// Arrange
|
||||||
|
const password = "password";
|
||||||
|
const wrongPassword = "wrongpassword";
|
||||||
|
const hash = await hashPasswordAsync(password);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await comparePasswordsAsync(wrongPassword, hash);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,19 +25,19 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@drizzle-team/brocli": "^0.11.0",
|
"@drizzle-team/brocli": "catalog:",
|
||||||
"@homarr/auth": "workspace:^0.1.0",
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"dotenv": "^17.3.1"
|
"dotenv": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { command, string } from "@drizzle-team/brocli";
|
import { command, string } from "@drizzle-team/brocli";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
import { hashPasswordAsync } from "@homarr/auth";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { generateSecureRandomToken } from "@homarr/common/server";
|
import { generateSecureRandomToken } from "@homarr/common/server";
|
||||||
import { and, count, db, eq } from "@homarr/db";
|
import { and, count, db, eq } from "@homarr/db";
|
||||||
@@ -67,9 +67,8 @@ export const recreateAdmin = command({
|
|||||||
permission: "admin",
|
permission: "admin",
|
||||||
});
|
});
|
||||||
|
|
||||||
const salt = await createSaltAsync();
|
|
||||||
const password = generateSecureRandomToken(24);
|
const password = generateSecureRandomToken(24);
|
||||||
const hashedPassword = await hashPasswordAsync(password, salt);
|
const hashedPassword = await hashPasswordAsync(password);
|
||||||
|
|
||||||
const userId = createId();
|
const userId = createId();
|
||||||
await db.insert(users).values({
|
await db.insert(users).values({
|
||||||
@@ -77,7 +76,6 @@ export const recreateAdmin = command({
|
|||||||
name: result.data,
|
name: result.data,
|
||||||
provider: "credentials",
|
provider: "credentials",
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
salt,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.insert(groupMembers).values({
|
await db.insert(groupMembers).values({
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const resetPassword = command({
|
|||||||
where: and(eq(users.name, options.username), eq(users.provider, "credentials")),
|
where: and(eq(users.name, options.username), eq(users.provider, "credentials")),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.salt) {
|
if (!user?.password) {
|
||||||
console.error(`User ${options.username} not found`);
|
console.error(`User ${options.username} not found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ export const resetPassword = command({
|
|||||||
await db
|
await db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({
|
.set({
|
||||||
password: await hashPasswordAsync(newPassword, user.salt),
|
password: await hashPasswordAsync(newPassword),
|
||||||
})
|
})
|
||||||
.where(eq(users.id, user.id));
|
.where(eq(users.id, user.id));
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const usersUpdatePassword = command({
|
|||||||
where: options.id ? eq(users.id, options.id) : eq(users.name, options.username!),
|
where: options.id ? eq(users.id, options.id) : eq(users.name, options.username!),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.salt) {
|
if (!user?.password) {
|
||||||
console.error("User not found or has no credentials record");
|
console.error("User not found or has no credentials record");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ export const usersUpdatePassword = command({
|
|||||||
await db
|
await db
|
||||||
.update(users)
|
.update(users)
|
||||||
.set({
|
.set({
|
||||||
password: await hashPasswordAsync(options.password, user.salt),
|
password: await hashPasswordAsync(options.password),
|
||||||
})
|
})
|
||||||
.where(eq(users.id, user.id));
|
.where(eq(users.id, user.id));
|
||||||
|
|
||||||
|
|||||||
@@ -27,22 +27,22 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@paralleldrive/cuid2": "^3.3.0",
|
"@paralleldrive/cuid2": "catalog:",
|
||||||
"cookie": "1.1.1",
|
"cookie": "catalog:",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"octokit": "^5.0.5",
|
"octokit": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"undici": "7.24.6",
|
"undici": "catalog:",
|
||||||
"zod": "^4.3.6",
|
"zod": "catalog:",
|
||||||
"zod-validation-error": "^5.0.0"
|
"zod-validation-error": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+14
-14
@@ -37,24 +37,24 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@t3-oss/env-nextjs": "^0.13.11",
|
"@t3-oss/env-nextjs": "catalog:",
|
||||||
"better-sqlite3": "^12.8.0",
|
"better-sqlite3": "catalog:",
|
||||||
"dns-caching": "^0.2.9",
|
"dns-caching": "catalog:",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "catalog:",
|
||||||
"ioredis": "5.10.1",
|
"ioredis": "catalog:",
|
||||||
"mysql2": "3.20.0",
|
"mysql2": "catalog:",
|
||||||
"pg": "^8.20.0",
|
"pg": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"winston": "3.19.0",
|
"winston": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "catalog:",
|
||||||
"@types/pg": "^8.20.0",
|
"@types/pg": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||||
|
|
||||||
export const formatMetadata = (metadata: Record<string, unknown> | Error, ignoreKeys?: string[]) => {
|
export const formatMetadata = (metadata: Record<string, unknown> | Error, ignoreKeys?: string[]) => {
|
||||||
const metadataObject = metadata instanceof ErrorWithMetadata ? metadata.metadata : metadata;
|
const metadataObject =
|
||||||
|
metadata instanceof ErrorWithMetadata || ("metadata" in metadata && typeof metadata.metadata === "object")
|
||||||
|
? (metadata.metadata as Record<string, unknown>)
|
||||||
|
: metadata;
|
||||||
|
|
||||||
const filteredMetadata = Object.keys(metadataObject)
|
const filteredMetadata = Object.keys(metadataObject)
|
||||||
.filter((key) => !ignoreKeys?.includes(key))
|
.filter((key) => !ignoreKeys?.includes(key))
|
||||||
|
|||||||
@@ -28,21 +28,21 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
"@tanstack/react-query": "^5.95.2",
|
"@tanstack/react-query": "catalog:",
|
||||||
"@trpc/client": "^11.16.0",
|
"@trpc/client": "catalog:",
|
||||||
"@trpc/server": "^11.16.0",
|
"@trpc/server": "catalog:",
|
||||||
"@trpc/tanstack-react-query": "^11.16.0",
|
"@trpc/tanstack-react-query": "catalog:",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "catalog:",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,14 +27,14 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^",
|
"@homarr/core": "workspace:^",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"node-cron": "^4.2.1"
|
"node-cron": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,13 +38,13 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"semver-parser": "^4.1.8"
|
"semver-parser": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,9 @@ import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
|||||||
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
|
||||||
import { db, eq } from "@homarr/db";
|
import { db, eq } from "@homarr/db";
|
||||||
import { items } from "@homarr/db/schema";
|
import { items } from "@homarr/db/schema";
|
||||||
// This import is done that way to avoid circular dependencies.
|
|
||||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||||
|
|
||||||
|
// This import is done that way to avoid circular dependencies.
|
||||||
import type { WidgetComponentProps } from "../../../widgets";
|
import type { WidgetComponentProps } from "../../../widgets";
|
||||||
import { createCronJob } from "../lib";
|
import { createCronJob } from "../lib";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `apiKey` DROP COLUMN `salt`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `salt`;
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -260,6 +260,13 @@
|
|||||||
"when": 1760968518445,
|
"when": 1760968518445,
|
||||||
"tag": "0036_add_app_reference_to_integration",
|
"tag": "0036_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 37,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1775399941430,
|
||||||
|
"tag": "0037_remove_salt",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "apiKey" DROP COLUMN "salt";--> statement-breakpoint
|
||||||
|
ALTER TABLE "user" DROP COLUMN "salt";
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -22,6 +22,13 @@
|
|||||||
"when": 1760968530084,
|
"when": 1760968530084,
|
||||||
"tag": "0002_add_app_reference_to_integration",
|
"tag": "0002_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1775399963041,
|
||||||
|
"tag": "0003_remove_salt",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `apiKey` DROP COLUMN `salt`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` DROP COLUMN `salt`;
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -246,6 +246,13 @@
|
|||||||
"when": 1760968503571,
|
"when": 1760968503571,
|
||||||
"tag": "0034_add_app_reference_to_integration",
|
"tag": "0034_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1775399918831,
|
||||||
|
"tag": "0035_remove_salt",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+21
-21
@@ -42,35 +42,35 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "^0.41.1",
|
"@auth/core": "catalog:",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@paralleldrive/cuid2": "^3.3.0",
|
"@paralleldrive/cuid2": "catalog:",
|
||||||
"@testcontainers/mysql": "^11.13.0",
|
"@testcontainers/mysql": "catalog:",
|
||||||
"@testcontainers/postgresql": "^11.13.0",
|
"@testcontainers/postgresql": "catalog:",
|
||||||
"better-sqlite3": "^12.8.0",
|
"better-sqlite3": "catalog:",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "catalog:",
|
||||||
"drizzle-kit": "^0.31.10",
|
"drizzle-kit": "catalog:",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "catalog:",
|
||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "catalog:",
|
||||||
"mysql2": "3.20.0",
|
"mysql2": "catalog:",
|
||||||
"pg": "^8.20.0",
|
"pg": "catalog:",
|
||||||
"superjson": "2.2.6"
|
"superjson": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "catalog:",
|
||||||
"@types/pg": "^8.20.0",
|
"@types/pg": "catalog:",
|
||||||
"dotenv-cli": "^11.0.0",
|
"dotenv-cli": "catalog:",
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "catalog:",
|
||||||
"tsx": "4.20.4",
|
"tsx": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/mysql";
|
|||||||
export const apiKeys = mysqlTable("apiKey", {
|
export const apiKeys = mysqlTable("apiKey", {
|
||||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||||
apiKey: text().notNull(),
|
apiKey: text().notNull(),
|
||||||
salt: text().notNull(),
|
|
||||||
userId: varchar({ length: 64 })
|
userId: varchar({ length: 64 })
|
||||||
.notNull()
|
.notNull()
|
||||||
.references((): AnyMySqlColumn => users.id, {
|
.references((): AnyMySqlColumn => users.id, {
|
||||||
@@ -66,7 +65,6 @@ export const users = mysqlTable("user", {
|
|||||||
emailVerified: timestamp(),
|
emailVerified: timestamp(),
|
||||||
image: text(),
|
image: text(),
|
||||||
password: text(),
|
password: text(),
|
||||||
salt: text(),
|
|
||||||
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
||||||
homeBoardId: varchar({ length: 64 }).references((): AnyMySqlColumn => boards.id, {
|
homeBoardId: varchar({ length: 64 }).references((): AnyMySqlColumn => boards.id, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/postgresql"
|
|||||||
export const apiKeys = pgTable("apiKey", {
|
export const apiKeys = pgTable("apiKey", {
|
||||||
id: varchar({ length: 64 }).notNull().primaryKey(),
|
id: varchar({ length: 64 }).notNull().primaryKey(),
|
||||||
apiKey: text().notNull(),
|
apiKey: text().notNull(),
|
||||||
salt: text().notNull(),
|
|
||||||
userId: varchar({ length: 64 })
|
userId: varchar({ length: 64 })
|
||||||
.notNull()
|
.notNull()
|
||||||
.references((): AnyPgColumn => users.id, {
|
.references((): AnyPgColumn => users.id, {
|
||||||
@@ -65,7 +64,6 @@ export const users = pgTable("user", {
|
|||||||
emailVerified: timestamp(),
|
emailVerified: timestamp(),
|
||||||
image: text(),
|
image: text(),
|
||||||
password: text(),
|
password: text(),
|
||||||
salt: text(),
|
|
||||||
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
|
||||||
homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export * from "@homarr/core/infrastructure/certificates/hostnames/db/sqlite";
|
|||||||
export const apiKeys = sqliteTable("apiKey", {
|
export const apiKeys = sqliteTable("apiKey", {
|
||||||
id: text().notNull().primaryKey(),
|
id: text().notNull().primaryKey(),
|
||||||
apiKey: text().notNull(),
|
apiKey: text().notNull(),
|
||||||
salt: text().notNull(),
|
|
||||||
userId: text()
|
userId: text()
|
||||||
.notNull()
|
.notNull()
|
||||||
.references((): AnySQLiteColumn => users.id, {
|
.references((): AnySQLiteColumn => users.id, {
|
||||||
@@ -48,7 +47,6 @@ export const users = sqliteTable("user", {
|
|||||||
emailVerified: int({ mode: "timestamp_ms" }),
|
emailVerified: int({ mode: "timestamp_ms" }),
|
||||||
image: text(),
|
image: text(),
|
||||||
password: text(),
|
password: text(),
|
||||||
salt: text(),
|
|
||||||
provider: text().$type<SupportedAuthProvider>().default("credentials").notNull(),
|
provider: text().$type<SupportedAuthProvider>().default("credentials").notNull(),
|
||||||
homeBoardId: text().references((): AnySQLiteColumn => boards.id, {
|
homeBoardId: text().references((): AnySQLiteColumn => boards.id, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
|
|||||||
@@ -24,15 +24,15 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"fast-xml-parser": "^5.5.9",
|
"fast-xml-parser": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"tsx": "4.20.4",
|
"tsx": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +153,7 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/getting-started/installation/synology"
|
| "/docs/getting-started/installation/synology"
|
||||||
| "/docs/getting-started/installation/unraid"
|
| "/docs/getting-started/installation/unraid"
|
||||||
| "/docs/integrations/adguard-home"
|
| "/docs/integrations/adguard-home"
|
||||||
|
| "/docs/integrations/anchor"
|
||||||
| "/docs/integrations/aria2"
|
| "/docs/integrations/aria2"
|
||||||
| "/docs/integrations/codeberg"
|
| "/docs/integrations/codeberg"
|
||||||
| "/docs/integrations/dash-dot"
|
| "/docs/integrations/dash-dot"
|
||||||
@@ -188,9 +189,11 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/integrations/radarr"
|
| "/docs/integrations/radarr"
|
||||||
| "/docs/integrations/readarr"
|
| "/docs/integrations/readarr"
|
||||||
| "/docs/integrations/sabnzbd"
|
| "/docs/integrations/sabnzbd"
|
||||||
|
| "/docs/integrations/search-ch"
|
||||||
| "/docs/integrations/seerr"
|
| "/docs/integrations/seerr"
|
||||||
| "/docs/integrations/slskd"
|
| "/docs/integrations/slskd"
|
||||||
| "/docs/integrations/sonarr"
|
| "/docs/integrations/sonarr"
|
||||||
|
| "/docs/integrations/speedtest-tracker"
|
||||||
| "/docs/integrations/tdarr"
|
| "/docs/integrations/tdarr"
|
||||||
| "/docs/integrations/tracearr"
|
| "/docs/integrations/tracearr"
|
||||||
| "/docs/integrations/transmission"
|
| "/docs/integrations/transmission"
|
||||||
@@ -207,6 +210,7 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/management/settings"
|
| "/docs/management/settings"
|
||||||
| "/docs/management/tasks"
|
| "/docs/management/tasks"
|
||||||
| "/docs/management/users"
|
| "/docs/management/users"
|
||||||
|
| "/docs/widgets/anchor-note"
|
||||||
| "/docs/widgets/app"
|
| "/docs/widgets/app"
|
||||||
| "/docs/widgets/bookmarks"
|
| "/docs/widgets/bookmarks"
|
||||||
| "/docs/widgets/calendar"
|
| "/docs/widgets/calendar"
|
||||||
@@ -235,8 +239,10 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/widgets/rss-feed"
|
| "/docs/widgets/rss-feed"
|
||||||
| "/docs/widgets/smart-home-entity-state"
|
| "/docs/widgets/smart-home-entity-state"
|
||||||
| "/docs/widgets/smart-home-execute-automation"
|
| "/docs/widgets/smart-home-execute-automation"
|
||||||
|
| "/docs/widgets/speedtest-tracker"
|
||||||
| "/docs/widgets/stock-price"
|
| "/docs/widgets/stock-price"
|
||||||
| "/docs/widgets/system-resources"
|
| "/docs/widgets/system-resources"
|
||||||
|
| "/docs/widgets/timetable"
|
||||||
| "/docs/widgets/tracearr"
|
| "/docs/widgets/tracearr"
|
||||||
| "/docs/widgets/video"
|
| "/docs/widgets/video"
|
||||||
| "/docs/widgets/weather"
|
| "/docs/widgets/weather"
|
||||||
|
|||||||
@@ -312,6 +312,13 @@ export const integrationDefs = {
|
|||||||
category: ["calendar"],
|
category: ["calendar"],
|
||||||
documentationUrl: createDocumentationLink("/docs/integrations/ical"),
|
documentationUrl: createDocumentationLink("/docs/integrations/ical"),
|
||||||
},
|
},
|
||||||
|
anchor: {
|
||||||
|
name: "Anchor",
|
||||||
|
secretKinds: [["apiKey"]],
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/anchor.svg",
|
||||||
|
category: ["notes"],
|
||||||
|
documentationUrl: createDocumentationLink("/docs/integrations/anchor"),
|
||||||
|
},
|
||||||
truenas: {
|
truenas: {
|
||||||
name: "TrueNAS",
|
name: "TrueNAS",
|
||||||
secretKinds: [["username", "password"]],
|
secretKinds: [["username", "password"]],
|
||||||
@@ -340,7 +347,6 @@ export const integrationDefs = {
|
|||||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png",
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png",
|
||||||
defaultUrl: "https://search.ch",
|
defaultUrl: "https://search.ch",
|
||||||
category: ["timetable"],
|
category: ["timetable"],
|
||||||
// @ts-expect-error - docs page will be created when integration is merged
|
|
||||||
documentationUrl: createDocumentationLink("/docs/integrations/search-ch"),
|
documentationUrl: createDocumentationLink("/docs/integrations/search-ch"),
|
||||||
},
|
},
|
||||||
immich: {
|
immich: {
|
||||||
@@ -357,6 +363,13 @@ export const integrationDefs = {
|
|||||||
category: ["mediaMonitoring"],
|
category: ["mediaMonitoring"],
|
||||||
documentationUrl: createDocumentationLink("/docs/integrations/tracearr"),
|
documentationUrl: createDocumentationLink("/docs/integrations/tracearr"),
|
||||||
},
|
},
|
||||||
|
speedtestTracker: {
|
||||||
|
name: "Speedtest Tracker",
|
||||||
|
secretKinds: [["apiKey"]],
|
||||||
|
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/speedtest-tracker.png",
|
||||||
|
category: ["speedtest"],
|
||||||
|
documentationUrl: createDocumentationLink("/docs/integrations/speedtest-tracker"),
|
||||||
|
},
|
||||||
// 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)
|
// 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: {
|
mock: {
|
||||||
name: "Mock",
|
name: "Mock",
|
||||||
@@ -445,7 +458,9 @@ export const integrationCategories = [
|
|||||||
"firewall",
|
"firewall",
|
||||||
"timetable",
|
"timetable",
|
||||||
"photoService",
|
"photoService",
|
||||||
|
"notes",
|
||||||
"mediaMonitoring",
|
"mediaMonitoring",
|
||||||
|
"speedtest",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type IntegrationCategory = (typeof integrationCategories)[number];
|
export type IntegrationCategory = (typeof integrationCategories)[number];
|
||||||
@@ -5,6 +5,7 @@ export const widgetKinds = [
|
|||||||
"iframe",
|
"iframe",
|
||||||
"video",
|
"video",
|
||||||
"notebook",
|
"notebook",
|
||||||
|
"anchorNote",
|
||||||
"dnsHoleSummary",
|
"dnsHoleSummary",
|
||||||
"dnsHoleControls",
|
"dnsHoleControls",
|
||||||
"smartHome-entityState",
|
"smartHome-entityState",
|
||||||
@@ -35,5 +36,6 @@ export const widgetKinds = [
|
|||||||
"immich-serverStats",
|
"immich-serverStats",
|
||||||
"immich-albumCarousel",
|
"immich-albumCarousel",
|
||||||
"tracearr",
|
"tracearr",
|
||||||
|
"speedtestTracker",
|
||||||
] as const;
|
] as const;
|
||||||
export type WidgetKind = (typeof widgetKinds)[number];
|
export type WidgetKind = (typeof widgetKinds)[number];
|
||||||
@@ -26,14 +26,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"dockerode": "^4.0.10"
|
"dockerode": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/dockerode": "^4.0.1",
|
"@types/dockerode": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,15 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/form": "^8.3.18",
|
"@mantine/form": "catalog:",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,15 +30,15 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,14 +25,14 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"bcrypt": "^6.0.0"
|
"bcrypt": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/bcrypt": "6.0.0",
|
"@types/bcrypt": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,39 +25,39 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ctrl/deluge": "^8.0.0",
|
"@ctrl/deluge": "catalog:",
|
||||||
"@ctrl/qbittorrent": "^9.11.2",
|
"@ctrl/qbittorrent": "catalog:",
|
||||||
"@ctrl/transmission": "^7.4.0",
|
"@ctrl/transmission": "catalog:",
|
||||||
"@gitbeaker/rest": "^43.8.0",
|
"@gitbeaker/rest": "catalog:",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/image-proxy": "workspace:^0.1.0",
|
"@homarr/image-proxy": "workspace:^0.1.0",
|
||||||
"@homarr/node-unifi": "^2.6.0",
|
"@homarr/node-unifi": "catalog:",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@immich/sdk": "^2.6.3",
|
"@immich/sdk": "catalog:",
|
||||||
"@jellyfin/sdk": "^0.13.0",
|
"@jellyfin/sdk": "catalog:",
|
||||||
"@octokit/auth-app": "^8.2.0",
|
"@octokit/auth-app": "catalog:",
|
||||||
"ical.js": "^2.2.1",
|
"ical.js": "catalog:",
|
||||||
"maria2": "^0.4.1",
|
"maria2": "catalog:",
|
||||||
"octokit": "^5.0.5",
|
"octokit": "catalog:",
|
||||||
"proxmox-api": "1.1.1",
|
"proxmox-api": "catalog:",
|
||||||
"timezones-ical-library": "^2.1.3",
|
"timezones-ical-library": "catalog:",
|
||||||
"tsdav": "^2.1.8",
|
"tsdav": "catalog:",
|
||||||
"undici": "7.24.6",
|
"undici": "catalog:",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node-unifi": "^2.5.1",
|
"@types/node-unifi": "catalog:",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import type { fetch as undiciFetch } from "undici";
|
||||||
|
|
||||||
|
import { ResponseError } from "@homarr/common/server";
|
||||||
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||||
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
|
|
||||||
|
import type { IntegrationTestingInput } from "../base/integration";
|
||||||
|
import { Integration } from "../base/integration";
|
||||||
|
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||||
|
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||||
|
import { anchorNoteSchema, anchorNoteSummaryListSchema } from "./anchor-types";
|
||||||
|
import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary, AnchorNoteUpdateInput } from "./anchor-types";
|
||||||
|
|
||||||
|
const logger = createLogger({ module: "anchorIntegration" });
|
||||||
|
|
||||||
|
export class AnchorIntegration extends Integration {
|
||||||
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||||
|
const response = await input.fetchAsync(this.url("/api/notes", { limit: 1 }), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.getSecretValue("apiKey")}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||||
|
|
||||||
|
anchorNoteSummaryListSchema.parse(await response.json());
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listNotesAsync(input: AnchorNotesListInput = {}): Promise<AnchorNoteSummary[]> {
|
||||||
|
const queryParams = buildListQuery(input);
|
||||||
|
const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, "/api/notes", {
|
||||||
|
queryParams,
|
||||||
|
});
|
||||||
|
return anchorNoteSummaryListSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getNoteAsync(noteId: string): Promise<AnchorNote> {
|
||||||
|
const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, `/api/notes/${noteId}`);
|
||||||
|
return anchorNoteSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateNoteAsync(input: AnchorNoteUpdateInput): Promise<AnchorNote> {
|
||||||
|
const { noteId, ...updateData } = input;
|
||||||
|
const response = await this.requestAsync(fetchWithTrustedCertificatesAsync, `/api/notes/${noteId}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: updateData,
|
||||||
|
});
|
||||||
|
if (response === null) {
|
||||||
|
return this.getNoteAsync(noteId);
|
||||||
|
}
|
||||||
|
return anchorNoteSchema.parse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestAsync(
|
||||||
|
fetchAsync: typeof undiciFetch,
|
||||||
|
path: `/${string}`,
|
||||||
|
options?: {
|
||||||
|
queryParams?: Record<string, string>;
|
||||||
|
method?: string;
|
||||||
|
body?: unknown;
|
||||||
|
},
|
||||||
|
): Promise<unknown> {
|
||||||
|
const url = this.url(path, options?.queryParams);
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Authorization: `Bearer ${this.getSecretValue("apiKey")}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options?.body !== undefined) {
|
||||||
|
headers["Content-Type"] = "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchAsync(url, {
|
||||||
|
method: options?.method ?? "GET",
|
||||||
|
headers,
|
||||||
|
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
let errorBody: string | null = null;
|
||||||
|
try {
|
||||||
|
errorBody = await response.text();
|
||||||
|
} catch {
|
||||||
|
errorBody = null;
|
||||||
|
}
|
||||||
|
logger.warn("Anchor request failed", {
|
||||||
|
statusCode: response.status,
|
||||||
|
url,
|
||||||
|
errorBody: errorBody ? errorBody.slice(0, 200) : null,
|
||||||
|
});
|
||||||
|
throw new ResponseError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildListQuery = (input: AnchorNotesListInput) => {
|
||||||
|
const queryParams: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (input.search?.trim()) {
|
||||||
|
queryParams.search = input.search.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.tagId?.trim()) {
|
||||||
|
queryParams.tagId = input.tagId.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof input.limit === "number") {
|
||||||
|
queryParams.limit = input.limit.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(queryParams).length > 0 ? queryParams : undefined;
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const anchorNotesListInputSchema = z.object({
|
||||||
|
search: z.string().optional(),
|
||||||
|
tagId: z.string().optional(),
|
||||||
|
limit: z.number().min(1).max(200).optional(),
|
||||||
|
});
|
||||||
|
export type AnchorNotesListInput = z.infer<typeof anchorNotesListInputSchema>;
|
||||||
|
|
||||||
|
export const anchorNotePermissionSchema = z.enum(["owner", "viewer", "editor"]);
|
||||||
|
export type AnchorNotePermission = z.infer<typeof anchorNotePermissionSchema>;
|
||||||
|
|
||||||
|
export const anchorNoteSummarySchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
isPinned: z.boolean(),
|
||||||
|
tagIds: z.array(z.string()),
|
||||||
|
permission: anchorNotePermissionSchema,
|
||||||
|
});
|
||||||
|
export type AnchorNoteSummary = z.infer<typeof anchorNoteSummarySchema>;
|
||||||
|
|
||||||
|
export const anchorNoteSchema = anchorNoteSummarySchema.extend({
|
||||||
|
content: z.string().nullable().optional(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
isArchived: z.boolean(),
|
||||||
|
background: z.string().nullable().optional(),
|
||||||
|
userId: z.string(),
|
||||||
|
});
|
||||||
|
export type AnchorNote = z.infer<typeof anchorNoteSchema>;
|
||||||
|
|
||||||
|
export const anchorNoteSummaryListSchema = z.array(anchorNoteSummarySchema);
|
||||||
|
|
||||||
|
export const anchorNoteUpdateInputSchema = z
|
||||||
|
.object({
|
||||||
|
noteId: z.string(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
})
|
||||||
|
.refine((value) => value.title !== undefined || value.content !== undefined, {
|
||||||
|
message: "At least one field to update must be provided",
|
||||||
|
});
|
||||||
|
export type AnchorNoteUpdateInput = z.infer<typeof anchorNoteUpdateInputSchema>;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { IntegrationKind } from "@homarr/definitions";
|
import type { IntegrationKind } from "@homarr/definitions";
|
||||||
|
|
||||||
import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration";
|
import { AdGuardHomeIntegration } from "../adguard-home/adguard-home-integration";
|
||||||
|
import { AnchorIntegration } from "../anchor/anchor-integration";
|
||||||
import { CodebergIntegration } from "../codeberg/codeberg-integration";
|
import { CodebergIntegration } from "../codeberg/codeberg-integration";
|
||||||
import { CoolifyIntegration } from "../coolify/coolify-integration";
|
import { CoolifyIntegration } from "../coolify/coolify-integration";
|
||||||
import { DashDotIntegration } from "../dashdot/dashdot-integration";
|
import { DashDotIntegration } from "../dashdot/dashdot-integration";
|
||||||
@@ -42,6 +43,7 @@ import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
|
|||||||
import { QuayIntegration } from "../quay/quay-integration";
|
import { QuayIntegration } from "../quay/quay-integration";
|
||||||
import { SearchChIntegration } from "../search-ch/search-ch-integration";
|
import { SearchChIntegration } from "../search-ch/search-ch-integration";
|
||||||
import { SeerrIntegration } from "../seerr/seerr-integration";
|
import { SeerrIntegration } from "../seerr/seerr-integration";
|
||||||
|
import { SpeedtestTrackerIntegration } from "../speedtest-tracker/speedtest-tracker-integration";
|
||||||
import { TracearrIntegration } from "../tracearr/tracearr-integration";
|
import { TracearrIntegration } from "../tracearr/tracearr-integration";
|
||||||
import { TrueNasIntegration } from "../truenas/truenas-integration";
|
import { TrueNasIntegration } from "../truenas/truenas-integration";
|
||||||
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
||||||
@@ -71,6 +73,7 @@ type IntegrationInstance = new (integration: IntegrationInput) => Integration;
|
|||||||
|
|
||||||
// factories are an array, to differentiate in js between class constructors and functions
|
// factories are an array, to differentiate in js between class constructors and functions
|
||||||
export const integrationCreators = {
|
export const integrationCreators = {
|
||||||
|
anchor: AnchorIntegration,
|
||||||
piHole: [createPiHoleIntegrationAsync],
|
piHole: [createPiHoleIntegrationAsync],
|
||||||
adGuardHome: AdGuardHomeIntegration,
|
adGuardHome: AdGuardHomeIntegration,
|
||||||
homeAssistant: HomeAssistantIntegration,
|
homeAssistant: HomeAssistantIntegration,
|
||||||
@@ -117,6 +120,7 @@ export const integrationCreators = {
|
|||||||
glances: GlancesIntegration,
|
glances: GlancesIntegration,
|
||||||
searchCh: SearchChIntegration,
|
searchCh: SearchChIntegration,
|
||||||
immich: ImmichIntegration,
|
immich: ImmichIntegration,
|
||||||
|
speedtestTracker: SpeedtestTrackerIntegration,
|
||||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||||
|
|
||||||
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
|
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// General integrations
|
// General integrations
|
||||||
export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration";
|
export { AdGuardHomeIntegration } from "./adguard-home/adguard-home-integration";
|
||||||
|
export { AnchorIntegration } from "./anchor/anchor-integration";
|
||||||
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
|
export { Aria2Integration } from "./download-client/aria2/aria2-integration";
|
||||||
export { DelugeIntegration } from "./download-client/deluge/deluge-integration";
|
export { DelugeIntegration } from "./download-client/deluge/deluge-integration";
|
||||||
export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
|
export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
|
||||||
@@ -31,6 +32,7 @@ export { ICalIntegration } from "./ical/ical-integration";
|
|||||||
export { CoolifyIntegration } from "./coolify/coolify-integration";
|
export { CoolifyIntegration } from "./coolify/coolify-integration";
|
||||||
export { ImmichIntegration } from "./immich/immich-integration";
|
export { ImmichIntegration } from "./immich/immich-integration";
|
||||||
export { TracearrIntegration } from "./tracearr/tracearr-integration";
|
export { TracearrIntegration } from "./tracearr/tracearr-integration";
|
||||||
|
export { SpeedtestTrackerIntegration } from "./speedtest-tracker/speedtest-tracker-integration";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type { IntegrationInput } from "./base/integration";
|
export type { IntegrationInput } from "./base/integration";
|
||||||
@@ -57,9 +59,19 @@ export type {
|
|||||||
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
|
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
|
||||||
export type { Notification } from "./interfaces/notifications/notification-types";
|
export type { Notification } from "./interfaces/notifications/notification-types";
|
||||||
export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration";
|
export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration";
|
||||||
|
export type {
|
||||||
|
AnchorNote,
|
||||||
|
AnchorNotePermission,
|
||||||
|
AnchorNoteUpdateInput,
|
||||||
|
AnchorNotesListInput,
|
||||||
|
AnchorNoteSummary,
|
||||||
|
} from "./anchor/anchor-types";
|
||||||
export type { TracearrDashboardData } from "./tracearr/tracearr-types";
|
export type { TracearrDashboardData } from "./tracearr/tracearr-types";
|
||||||
|
export type { SpeedtestTrackerDashboardData } from "./speedtest-tracker/speedtest-tracker-types";
|
||||||
|
|
||||||
// Schemas
|
// Schemas
|
||||||
|
export { anchorNotesListInputSchema } from "./anchor/anchor-types";
|
||||||
|
export { anchorNoteUpdateInputSchema } from "./anchor/anchor-types";
|
||||||
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";
|
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
|||||||
// next actually returns undefined when there are no more occurrences
|
// next actually returns undefined when there are no more occurrences
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
while ((next = iterator.next())) {
|
while ((next = iterator.next())) {
|
||||||
const nextStartDate = next.convertToZone(ICAL.Timezone.utcTimezone).toJSDate();
|
const nextStartDate = next.isDate
|
||||||
|
? next.toJSDate()
|
||||||
|
: next.convertToZone(ICAL.Timezone.utcTimezone).toJSDate();
|
||||||
if (nextStartDate > end) break;
|
if (nextStartDate > end) break;
|
||||||
const nextEndDate = new Date(nextStartDate.getTime() + durationMs);
|
const nextEndDate = new Date(nextStartDate.getTime() + durationMs);
|
||||||
if (nextEndDate < start) continue;
|
if (nextEndDate < start) continue;
|
||||||
@@ -121,7 +123,11 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
|||||||
startDates.push(nextStartDate);
|
startDates.push(nextStartDate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startDates = [veventObject.startDate.convertToZone(ICAL.Timezone.utcTimezone).toJSDate()];
|
startDates = [
|
||||||
|
veventObject.startDate.isDate
|
||||||
|
? veventObject.startDate.toJSDate()
|
||||||
|
: veventObject.startDate.convertToZone(ICAL.Timezone.utcTimezone).toJSDate(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return startDates.map((startDate) => {
|
return startDates.map((startDate) => {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { ResponseError } from "@homarr/common/server";
|
||||||
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||||
|
|
||||||
|
import type { IntegrationTestingInput } from "../base/integration";
|
||||||
|
import { Integration } from "../base/integration";
|
||||||
|
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||||
|
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||||
|
import {
|
||||||
|
speedtestTrackerLatestResultEnvelopeSchema,
|
||||||
|
speedtestTrackerResultsCollectionSchema,
|
||||||
|
speedtestTrackerStatsEnvelopeSchema,
|
||||||
|
} from "./speedtest-tracker-types";
|
||||||
|
import type {
|
||||||
|
SpeedtestTrackerDashboardData,
|
||||||
|
SpeedtestTrackerResult,
|
||||||
|
SpeedtestTrackerResultsCollection,
|
||||||
|
SpeedtestTrackerStats,
|
||||||
|
} from "./speedtest-tracker-types";
|
||||||
|
|
||||||
|
export class SpeedtestTrackerIntegration extends Integration {
|
||||||
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||||
|
const url = this.url("/api/v1/results/latest");
|
||||||
|
const response = await input.fetchAsync(url, {
|
||||||
|
headers: this.getAuthHeaders(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return TestConnectionError.StatusResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLatestResultAsync(): Promise<SpeedtestTrackerResult | null> {
|
||||||
|
const response = await this.getAsync("/api/v1/results/latest");
|
||||||
|
|
||||||
|
// Speedtest Tracker returns 404 when no tests have been run yet (fresh install)
|
||||||
|
if (response.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ResponseError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return speedtestTrackerLatestResultEnvelopeSchema.parse(await response.json()).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStatsAsync(): Promise<SpeedtestTrackerStats | null> {
|
||||||
|
const response = await this.getAsync("/api/v1/stats");
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ResponseError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return speedtestTrackerStatsEnvelopeSchema.parse(await response.json()).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRecentResultsAsync(perPage = 10): Promise<SpeedtestTrackerResultsCollection> {
|
||||||
|
const response = await this.getAsync("/api/v1/results", {
|
||||||
|
result_count: String(perPage),
|
||||||
|
sort: "-created_at",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ResponseError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return speedtestTrackerResultsCollectionSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDashboardDataAsync(): Promise<SpeedtestTrackerDashboardData> {
|
||||||
|
const [latestResult, stats, recentCollection] = await Promise.all([
|
||||||
|
this.getLatestResultAsync(),
|
||||||
|
this.getStatsAsync(),
|
||||||
|
this.getRecentResultsAsync(30),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
latestResult,
|
||||||
|
stats,
|
||||||
|
recentResults: recentCollection.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAsync(path: `/api/v1/${string}`, queryParams?: Record<string, string>) {
|
||||||
|
return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), {
|
||||||
|
headers: this.getAuthHeaders(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAuthHeaders(): Record<string, string> {
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${this.getSecretValue("apiKey")}`,
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
const createEnvelopeSchema = <T extends z.ZodType>(dataSchema: T) => z.object({ data: dataSchema });
|
||||||
|
|
||||||
|
const parseTimestamp = (timestamp: string): Date => new Date(`${timestamp.replace(" ", "T")}Z`);
|
||||||
|
|
||||||
|
export const speedtestTrackerResultSchema = z.object({
|
||||||
|
id: z.number().int(),
|
||||||
|
ping: z.number().nullable(),
|
||||||
|
download_bits: z.number().int().nullish(),
|
||||||
|
upload_bits: z.number().int().nullish(),
|
||||||
|
healthy: z.boolean().nullable(),
|
||||||
|
created_at: z.string().transform(parseTimestamp),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SpeedtestTrackerResult = z.infer<typeof speedtestTrackerResultSchema>;
|
||||||
|
|
||||||
|
export const speedtestTrackerLatestResultEnvelopeSchema = createEnvelopeSchema(speedtestTrackerResultSchema);
|
||||||
|
|
||||||
|
export const speedtestTrackerStatsBandwidthSchema = z.object({
|
||||||
|
avg: z.number(),
|
||||||
|
avg_bits: z.number().optional(),
|
||||||
|
min: z.number(),
|
||||||
|
max: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const speedtestTrackerStatsSchema = z.object({
|
||||||
|
ping: z.object({
|
||||||
|
avg: z.number(),
|
||||||
|
min: z.number(),
|
||||||
|
max: z.number(),
|
||||||
|
}),
|
||||||
|
download: speedtestTrackerStatsBandwidthSchema,
|
||||||
|
upload: speedtestTrackerStatsBandwidthSchema,
|
||||||
|
total_results: z.number().int(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SpeedtestTrackerStats = z.infer<typeof speedtestTrackerStatsSchema>;
|
||||||
|
|
||||||
|
export const speedtestTrackerStatsEnvelopeSchema = createEnvelopeSchema(speedtestTrackerStatsSchema);
|
||||||
|
|
||||||
|
export const speedtestTrackerResultsCollectionSchema = z.object({
|
||||||
|
data: z.array(speedtestTrackerResultSchema),
|
||||||
|
meta: z.object({
|
||||||
|
current_page: z.number().int(),
|
||||||
|
last_page: z.number().int(),
|
||||||
|
total: z.number().int(),
|
||||||
|
per_page: z.number().int().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SpeedtestTrackerResultsCollection = z.infer<typeof speedtestTrackerResultsCollectionSchema>;
|
||||||
|
|
||||||
|
export interface SpeedtestTrackerDashboardData {
|
||||||
|
latestResult: SpeedtestTrackerResult | null;
|
||||||
|
stats: SpeedtestTrackerStats | null;
|
||||||
|
recentResults: SpeedtestTrackerResult[];
|
||||||
|
}
|
||||||
@@ -12,5 +12,7 @@ export * from "./unifi-controller/unifi-controller-types";
|
|||||||
export * from "./opnsense/opnsense-types";
|
export * from "./opnsense/opnsense-types";
|
||||||
export * from "./interfaces/media-releases";
|
export * from "./interfaces/media-releases";
|
||||||
export * from "./coolify/coolify-types";
|
export * from "./coolify/coolify-types";
|
||||||
|
export * from "./anchor/anchor-types";
|
||||||
export * from "./interfaces/timetable/timetable-types";
|
export * from "./interfaces/timetable/timetable-types";
|
||||||
export * from "./tracearr/tracearr-types";
|
export * from "./tracearr/tracearr-types";
|
||||||
|
export * from "./speedtest-tracker/speedtest-tracker-types";
|
||||||
@@ -33,19 +33,19 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@tabler/icons-react": "^3.41.0",
|
"@tabler/icons-react": "catalog:",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,15 +24,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/hooks": "^8.3.18",
|
"@mantine/hooks": "catalog:",
|
||||||
"react": "19.2.4"
|
"react": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,14 +24,14 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/notifications": "^8.3.18",
|
"@mantine/notifications": "catalog:",
|
||||||
"@tabler/icons-react": "^3.41.0"
|
"@tabler/icons-react": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,22 +37,22 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/hooks": "^8.3.18",
|
"@mantine/hooks": "catalog:",
|
||||||
"adm-zip": "0.5.16",
|
"adm-zip": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"zod": "^4.3.6",
|
"zod": "catalog:",
|
||||||
"zod-form-data": "^3.0.1"
|
"zod-form-data": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/adm-zip": "0.5.8",
|
"@types/adm-zip": "catalog:",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ export const mapAndDecryptUsers = (importUsers: OldmarrImportUser[], encryptionT
|
|||||||
({
|
({
|
||||||
id,
|
id,
|
||||||
password,
|
password,
|
||||||
salt,
|
salt: _saltIsNotNeededAsPartOfPassword,
|
||||||
settings,
|
settings,
|
||||||
...user
|
...user
|
||||||
}): InferInsertModel<typeof users> & { oldId: string; isAdmin: boolean } => ({
|
}): InferInsertModel<typeof users> & { oldId: string; isAdmin: boolean } => ({
|
||||||
@@ -30,7 +30,6 @@ export const mapAndDecryptUsers = (importUsers: OldmarrImportUser[], encryptionT
|
|||||||
pingIconsEnabled: settings?.replacePingWithIcons,
|
pingIconsEnabled: settings?.replacePingWithIcons,
|
||||||
isAdmin: user.isAdmin || user.isOwner,
|
isAdmin: user.isAdmin || user.isOwner,
|
||||||
password: decryptSecretWithKey(password, key),
|
password: decryptSecretWithKey(password, key),
|
||||||
salt: decryptSecretWithKey(salt, key),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -122,6 +122,7 @@ const optionMapping: OptionMapping = {
|
|||||||
maximumAmountPosts: (oldOptions) => oldOptions.maximumAmountOfPosts,
|
maximumAmountPosts: (oldOptions) => oldOptions.maximumAmountOfPosts,
|
||||||
textLinesClamp: (oldOptions) => oldOptions.textLinesClamp,
|
textLinesClamp: (oldOptions) => oldOptions.textLinesClamp,
|
||||||
hideDescription: () => undefined,
|
hideDescription: () => undefined,
|
||||||
|
showPosterImage: () => true, // This was the default in oldmarr
|
||||||
},
|
},
|
||||||
notebook: {
|
notebook: {
|
||||||
allowReadOnlyCheck: (oldOptions) => oldOptions.allowReadOnlyCheck,
|
allowReadOnlyCheck: (oldOptions) => oldOptions.allowReadOnlyCheck,
|
||||||
|
|||||||
@@ -23,13 +23,13 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,14 +26,14 @@
|
|||||||
"@homarr/core": "workspace:^",
|
"@homarr/core": "workspace:^",
|
||||||
"@homarr/db": "workspace:^",
|
"@homarr/db": "workspace:^",
|
||||||
"@homarr/definitions": "workspace:^",
|
"@homarr/definitions": "workspace:^",
|
||||||
"ioredis": "5.10.1",
|
"ioredis": "catalog:",
|
||||||
"superjson": "2.2.6"
|
"superjson": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@extractus/feed-extractor": "7.1.7",
|
"@extractus/feed-extractor": "catalog:",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
@@ -30,17 +30,17 @@
|
|||||||
"@homarr/docker": "workspace:^0.1.0",
|
"@homarr/docker": "workspace:^0.1.0",
|
||||||
"@homarr/integrations": "workspace:^0.1.0",
|
"@homarr/integrations": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"octokit": "^5.0.5",
|
"octokit": "catalog:",
|
||||||
"superjson": "2.2.6",
|
"superjson": "catalog:",
|
||||||
"undici": "7.24.6",
|
"undici": "catalog:",
|
||||||
"zod": "^4.3.6"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary } from "@homarr/integrations";
|
||||||
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
|
|
||||||
|
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||||
|
|
||||||
|
export const anchorNotesListRequestHandler = createCachedIntegrationRequestHandler<
|
||||||
|
AnchorNoteSummary[],
|
||||||
|
"anchor",
|
||||||
|
AnchorNotesListInput
|
||||||
|
>({
|
||||||
|
queryKey: "anchorNotesList",
|
||||||
|
cacheDuration: dayjs.duration(15, "seconds"),
|
||||||
|
async requestAsync(integration, input) {
|
||||||
|
const instance = await createIntegrationAsync(integration);
|
||||||
|
return instance.listNotesAsync(input);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const anchorNoteRequestHandler = createCachedIntegrationRequestHandler<AnchorNote, "anchor", { noteId: string }>(
|
||||||
|
{
|
||||||
|
queryKey: "anchorNote",
|
||||||
|
cacheDuration: dayjs.duration(15, "seconds"),
|
||||||
|
async requestAsync(integration, input) {
|
||||||
|
const instance = await createIntegrationAsync(integration);
|
||||||
|
return instance.getNoteAsync(input.noteId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -28,7 +28,7 @@ export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
entries: result.entries?.slice(0, input.count) ?? [],
|
entries: result.entries?.map((entry) => ({ ...entry, feedUrl: input.url })).slice(0, input.count) ?? [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
cacheDuration: dayjs.duration(5, "minutes"),
|
cacheDuration: dayjs.duration(5, "minutes"),
|
||||||
@@ -109,6 +109,7 @@ const getFirstMediaProperty = (feedObject: object) => {
|
|||||||
* This interface adds properties on top of the default ones.
|
* This interface adds properties on top of the default ones.
|
||||||
*/
|
*/
|
||||||
export interface ExtendedFeedEntry extends FeedEntry {
|
export interface ExtendedFeedEntry extends FeedEntry {
|
||||||
|
feedUrl: string;
|
||||||
enclosure?: string;
|
enclosure?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
|
import type { SpeedtestTrackerDashboardData } from "@homarr/integrations/types";
|
||||||
|
|
||||||
|
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||||
|
|
||||||
|
export const speedtestTrackerRequestHandler = createCachedIntegrationRequestHandler<
|
||||||
|
SpeedtestTrackerDashboardData,
|
||||||
|
"speedtestTracker",
|
||||||
|
Record<string, never>
|
||||||
|
>({
|
||||||
|
async requestAsync(integration, _input) {
|
||||||
|
const integrationInstance = await createIntegrationAsync(integration);
|
||||||
|
return await integrationInstance.getDashboardDataAsync();
|
||||||
|
},
|
||||||
|
cacheDuration: dayjs.duration(10, "minutes"),
|
||||||
|
queryKey: "speedtestTrackerDashboard",
|
||||||
|
});
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,16 +25,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/dates": "^8.3.18",
|
"@mantine/dates": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,21 +33,21 @@
|
|||||||
"@homarr/settings": "workspace:^0.1.0",
|
"@homarr/settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.18",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/hooks": "^8.3.18",
|
"@mantine/hooks": "catalog:",
|
||||||
"@mantine/spotlight": "^8.3.18",
|
"@mantine/spotlight": "catalog:",
|
||||||
"@tabler/icons-react": "^3.41.0",
|
"@tabler/icons-react": "catalog:",
|
||||||
"jotai": "^2.19.0",
|
"jotai": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "catalog:",
|
||||||
"use-deep-compare-effect": "^1.8.1"
|
"use-deep-compare-effect": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,19 +29,19 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "catalog:",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "catalog:",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "catalog:",
|
||||||
"next": "16.2.1",
|
"next": "catalog:",
|
||||||
"next-intl": "4.8.3",
|
"next-intl": "catalog:",
|
||||||
"react": "19.2.4",
|
"react": "catalog:",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "catalog:",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": ""
|
"offline": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"title": "",
|
||||||
|
"noStation": "",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "",
|
||||||
|
"emptyContent": "",
|
||||||
|
"notFound": "",
|
||||||
|
"noNotes": "",
|
||||||
|
"integrationRequired": "",
|
||||||
|
"updatedAt": "",
|
||||||
|
"untitled": "",
|
||||||
|
"edit": "",
|
||||||
|
"save": "",
|
||||||
|
"cancel": "",
|
||||||
|
"readOnlyViewer": "",
|
||||||
|
"saveForbidden": "",
|
||||||
|
"saveFailed": ""
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": ""
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "",
|
"noItems": "",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "",
|
||||||
|
"averages": "",
|
||||||
|
"tests": "",
|
||||||
|
"recentResults": "",
|
||||||
|
"recentResultsSpeed": "",
|
||||||
|
"recentResultsPing": "",
|
||||||
|
"download": "",
|
||||||
|
"upload": "",
|
||||||
|
"ping": "",
|
||||||
|
"status": "",
|
||||||
|
"avgDownload": "",
|
||||||
|
"avgUpload": "",
|
||||||
|
"avgPing": "",
|
||||||
|
"healthy": "",
|
||||||
|
"unhealthy": "",
|
||||||
|
"ok": "",
|
||||||
|
"fail": "",
|
||||||
|
"noSectionsEnabled": "",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": "离线"
|
"offline": "离线"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"title": "",
|
||||||
|
"noStation": "",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "笔记",
|
"name": "笔记",
|
||||||
"description": "支持 Markdown 的简单笔记本小部件",
|
"description": "支持 Markdown 的简单笔记本小部件",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "",
|
||||||
|
"emptyContent": "",
|
||||||
|
"notFound": "",
|
||||||
|
"noNotes": "",
|
||||||
|
"integrationRequired": "",
|
||||||
|
"updatedAt": "",
|
||||||
|
"untitled": "",
|
||||||
|
"edit": "",
|
||||||
|
"save": "",
|
||||||
|
"cancel": "",
|
||||||
|
"readOnlyViewer": "",
|
||||||
|
"saveForbidden": "",
|
||||||
|
"saveFailed": ""
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "iFrame",
|
"name": "iFrame",
|
||||||
"description": "嵌入互联网上的任何内容。某些网站可能限制访问。",
|
"description": "嵌入互联网上的任何内容。某些网站可能限制访问。",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": "隐藏描述"
|
"label": "隐藏描述"
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "没有可显示的通知。",
|
"noItems": "没有可显示的通知。",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "",
|
||||||
|
"averages": "",
|
||||||
|
"tests": "",
|
||||||
|
"recentResults": "",
|
||||||
|
"recentResultsSpeed": "",
|
||||||
|
"recentResultsPing": "",
|
||||||
|
"download": "",
|
||||||
|
"upload": "",
|
||||||
|
"ping": "",
|
||||||
|
"status": "",
|
||||||
|
"avgDownload": "",
|
||||||
|
"avgUpload": "",
|
||||||
|
"avgPing": "",
|
||||||
|
"healthy": "",
|
||||||
|
"unhealthy": "",
|
||||||
|
"ok": "",
|
||||||
|
"fail": "",
|
||||||
|
"noSectionsEnabled": "",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "系统资源",
|
"name": "系统资源",
|
||||||
"description": "您系统的 CPU、内存、磁盘及其他硬件使用情况",
|
"description": "您系统的 CPU、内存、磁盘及其他硬件使用情况",
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": "Minecraft 服务状态"
|
"label": "Minecraft 服务状态"
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": "图标更新"
|
"label": "图标更新"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": "crwdns2494:0crwdne2494:0"
|
"offline": "crwdns2494:0crwdne2494:0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "crwdns4000:0crwdne4000:0",
|
||||||
|
"description": "crwdns4002:0crwdne4002:0",
|
||||||
|
"title": "crwdns4004:0{station}crwdne4004:0",
|
||||||
|
"noStation": "crwdns4006:0crwdne4006:0",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": "crwdns4008:0crwdne4008:0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "crwdns860:0crwdne860:0",
|
"name": "crwdns860:0crwdne860:0",
|
||||||
"description": "crwdns862:0crwdne862:0",
|
"description": "crwdns862:0crwdne862:0",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "crwdns4064:0crwdne4064:0",
|
||||||
|
"description": "crwdns4066:0crwdne4066:0",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": "crwdns4068:0crwdne4068:0"
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": "crwdns4070:0crwdne4070:0"
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": "crwdns4072:0crwdne4072:0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "crwdns4074:0crwdne4074:0",
|
||||||
|
"emptyContent": "crwdns4076:0crwdne4076:0",
|
||||||
|
"notFound": "crwdns4078:0crwdne4078:0",
|
||||||
|
"noNotes": "crwdns4080:0crwdne4080:0",
|
||||||
|
"integrationRequired": "crwdns4082:0crwdne4082:0",
|
||||||
|
"updatedAt": "crwdns4084:0{date}crwdne4084:0",
|
||||||
|
"untitled": "crwdns4086:0crwdne4086:0",
|
||||||
|
"edit": "crwdns4088:0crwdne4088:0",
|
||||||
|
"save": "crwdns4090:0crwdne4090:0",
|
||||||
|
"cancel": "crwdns4092:0crwdne4092:0",
|
||||||
|
"readOnlyViewer": "crwdns4094:0crwdne4094:0",
|
||||||
|
"saveForbidden": "crwdns4096:0crwdne4096:0",
|
||||||
|
"saveFailed": "crwdns4098:0crwdne4098:0"
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "crwdns950:0crwdne950:0",
|
"name": "crwdns950:0crwdne950:0",
|
||||||
"description": "crwdns952:0crwdne952:0",
|
"description": "crwdns952:0crwdne952:0",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": "crwdns3674:0crwdne3674:0"
|
"label": "crwdns3674:0crwdne3674:0"
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": "crwdns4012:0crwdne4012:0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "crwdns3480:0crwdne3480:0",
|
"noItems": "crwdns3480:0crwdne3480:0",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "crwdns4014:0crwdne4014:0",
|
||||||
|
"description": "crwdns4016:0crwdne4016:0",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": "crwdns4018:0crwdne4018:0"
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": "crwdns4020:0crwdne4020:0"
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": "crwdns4022:0crwdne4022:0"
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": "crwdns4024:0crwdne4024:0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "crwdns4026:0crwdne4026:0",
|
||||||
|
"averages": "crwdns4028:0crwdne4028:0",
|
||||||
|
"tests": "crwdns4030:0{count}crwdne4030:0",
|
||||||
|
"recentResults": "crwdns4032:0crwdne4032:0",
|
||||||
|
"recentResultsSpeed": "crwdns4034:0crwdne4034:0",
|
||||||
|
"recentResultsPing": "crwdns4036:0crwdne4036:0",
|
||||||
|
"download": "crwdns4038:0crwdne4038:0",
|
||||||
|
"upload": "crwdns4040:0crwdne4040:0",
|
||||||
|
"ping": "crwdns4042:0crwdne4042:0",
|
||||||
|
"status": "crwdns4044:0crwdne4044:0",
|
||||||
|
"avgDownload": "crwdns4046:0crwdne4046:0",
|
||||||
|
"avgUpload": "crwdns4048:0crwdne4048:0",
|
||||||
|
"avgPing": "crwdns4050:0crwdne4050:0",
|
||||||
|
"healthy": "crwdns4052:0crwdne4052:0",
|
||||||
|
"unhealthy": "crwdns4054:0crwdne4054:0",
|
||||||
|
"ok": "crwdns4056:0crwdne4056:0",
|
||||||
|
"fail": "crwdns4058:0crwdne4058:0",
|
||||||
|
"noSectionsEnabled": "crwdns4060:0crwdne4060:0",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": "crwdns4062:0crwdne4062:0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "crwdns3600:0crwdne3600:0",
|
"name": "crwdns3600:0crwdne3600:0",
|
||||||
"description": "crwdns3602:0crwdne3602:0",
|
"description": "crwdns3602:0crwdne3602:0",
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": "crwdns2496:0crwdne2496:0"
|
"label": "crwdns2496:0crwdne2496:0"
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": "crwdns4010:0crwdne4010:0"
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": "crwdns1828:0crwdne1828:0"
|
"label": "crwdns1828:0crwdne1828:0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": ""
|
"offline": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"title": "",
|
||||||
|
"noStation": "",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "Zápisník",
|
"name": "Zápisník",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "",
|
||||||
|
"emptyContent": "",
|
||||||
|
"notFound": "",
|
||||||
|
"noNotes": "",
|
||||||
|
"integrationRequired": "",
|
||||||
|
"updatedAt": "",
|
||||||
|
"untitled": "",
|
||||||
|
"edit": "",
|
||||||
|
"save": "",
|
||||||
|
"cancel": "",
|
||||||
|
"readOnlyViewer": "",
|
||||||
|
"saveForbidden": "",
|
||||||
|
"saveFailed": ""
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "iFrame",
|
"name": "iFrame",
|
||||||
"description": "Vložte jakýkoli obsah z internetu. Některé webové stránky mohou omezit přístup.",
|
"description": "Vložte jakýkoli obsah z internetu. Některé webové stránky mohou omezit přístup.",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": ""
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "",
|
"noItems": "",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "",
|
||||||
|
"averages": "",
|
||||||
|
"tests": "",
|
||||||
|
"recentResults": "",
|
||||||
|
"recentResultsSpeed": "",
|
||||||
|
"recentResultsPing": "",
|
||||||
|
"download": "",
|
||||||
|
"upload": "",
|
||||||
|
"ping": "",
|
||||||
|
"status": "",
|
||||||
|
"avgDownload": "",
|
||||||
|
"avgUpload": "",
|
||||||
|
"avgPing": "",
|
||||||
|
"healthy": "",
|
||||||
|
"unhealthy": "",
|
||||||
|
"ok": "",
|
||||||
|
"fail": "",
|
||||||
|
"noSectionsEnabled": "",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1138,9 +1138,9 @@
|
|||||||
"dangerZone": "Farezone",
|
"dangerZone": "Farezone",
|
||||||
"noResults": "Ingen resultater fundet",
|
"noResults": "Ingen resultater fundet",
|
||||||
"unsavedChanges": "Du har ikke gemte ændringer!",
|
"unsavedChanges": "Du har ikke gemte ændringer!",
|
||||||
"servers": "",
|
"servers": "Servere",
|
||||||
"applications": "",
|
"applications": "Apps",
|
||||||
"services": "",
|
"services": "Services",
|
||||||
"preview": {
|
"preview": {
|
||||||
"show": "Vis forhåndsvisning",
|
"show": "Vis forhåndsvisning",
|
||||||
"hide": "Skjul forhåndsvisning"
|
"hide": "Skjul forhåndsvisning"
|
||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": "Offline"
|
"offline": "Offline"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "Tidsskema",
|
||||||
|
"description": "Viser afgangstider for en station",
|
||||||
|
"title": "Afgange {station}",
|
||||||
|
"noStation": "Ingen station valgt",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": "Station"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "Notesbog",
|
"name": "Notesbog",
|
||||||
"description": "En simpel notebook widget, der understøtter markdown",
|
"description": "En simpel notebook widget, der understøtter markdown",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "Anchor note",
|
||||||
|
"description": "Viser en valgt note fra Anchor",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": "Note"
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": "Vis titel"
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": "Vis senest opdateret"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "Vælg en note der skal vises",
|
||||||
|
"emptyContent": "Denne note har intet indhold",
|
||||||
|
"notFound": "Note ikke fundet",
|
||||||
|
"noNotes": "Ingen noter fundet",
|
||||||
|
"integrationRequired": "Vælg først en Anchorintegration",
|
||||||
|
"updatedAt": "Opdateret {date}",
|
||||||
|
"untitled": "Unavngivet",
|
||||||
|
"edit": "Rediger",
|
||||||
|
"save": "Gem",
|
||||||
|
"cancel": "Annullér",
|
||||||
|
"readOnlyViewer": "Du har læseadgang til denne note. Redigering er deaktiveret.",
|
||||||
|
"saveForbidden": "Du har ikke adgang til at redigere denne note.",
|
||||||
|
"saveFailed": "Kunne ikke gemme denne note. Prøv igen."
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "indlejret dokument (iframe)",
|
"name": "indlejret dokument (iframe)",
|
||||||
"description": "Indlejr ethvert indhold fra internettet. Nogle websteder kan begrænse adgang.",
|
"description": "Indlejr ethvert indhold fra internettet. Nogle websteder kan begrænse adgang.",
|
||||||
@@ -1627,7 +1666,7 @@
|
|||||||
"label": "Tillad geolokalisering"
|
"label": "Tillad geolokalisering"
|
||||||
},
|
},
|
||||||
"allowModals": {
|
"allowModals": {
|
||||||
"label": ""
|
"label": "Tillad modaler"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -1862,7 +1901,7 @@
|
|||||||
"label": "Vis hukommelsesoplysninger"
|
"label": "Vis hukommelsesoplysninger"
|
||||||
},
|
},
|
||||||
"gpu": {
|
"gpu": {
|
||||||
"label": ""
|
"label": "Vis GPU info"
|
||||||
},
|
},
|
||||||
"showUptime": {
|
"showUptime": {
|
||||||
"label": "Vis Oppetid"
|
"label": "Vis Oppetid"
|
||||||
@@ -1950,19 +1989,19 @@
|
|||||||
"description": "Statistik for dine containere (Denne widget kan kun tilføjes med administratorrettigheder)",
|
"description": "Statistik for dine containere (Denne widget kan kun tilføjes med administratorrettigheder)",
|
||||||
"option": {
|
"option": {
|
||||||
"enableRowSorting": {
|
"enableRowSorting": {
|
||||||
"label": ""
|
"label": "Aktiver sortering"
|
||||||
},
|
},
|
||||||
"defaultSort": {
|
"defaultSort": {
|
||||||
"label": "",
|
"label": "Kolonne brugt til sortering som standard",
|
||||||
"option": {
|
"option": {
|
||||||
"name": "",
|
"name": "Navn",
|
||||||
"state": "",
|
"state": "Tilstand",
|
||||||
"cpuUsage": "",
|
"cpuUsage": "CPU-forbrug",
|
||||||
"memoryUsage": ""
|
"memoryUsage": "Hukommelsesforbrug"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"descendingDefaultSort": {
|
"descendingDefaultSort": {
|
||||||
"label": ""
|
"label": "Inverter sortering"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -1970,91 +2009,91 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"name": "",
|
"name": "Coolify",
|
||||||
"description": "",
|
"description": "Oversigt over din Coolify instans med servere og applikationer",
|
||||||
"option": {
|
"option": {
|
||||||
"showServers": {
|
"showServers": {
|
||||||
"label": ""
|
"label": "Vis servere"
|
||||||
},
|
},
|
||||||
"showApplications": {
|
"showApplications": {
|
||||||
"label": ""
|
"label": "Vis applikationer"
|
||||||
},
|
},
|
||||||
"showServices": {
|
"showServices": {
|
||||||
"label": ""
|
"label": "Vis services"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"servers": "",
|
"servers": "Ingen servere fundet",
|
||||||
"applications": "",
|
"applications": "Ingen applikationer fundet",
|
||||||
"services": ""
|
"services": "Ingen services fundet"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"updated": ""
|
"updated": "Opdateret {when}"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"noIntegration": "",
|
"noIntegration": "Ingen Coolify integration konfigureret",
|
||||||
"internalServerError": ""
|
"internalServerError": "Kunne ikke hente Coolify data"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"buildServer": ""
|
"buildServer": "Byg"
|
||||||
},
|
},
|
||||||
"instances": {
|
"instances": {
|
||||||
"title": ""
|
"title": "Instanser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tracearr": {
|
"tracearr": {
|
||||||
"name": "",
|
"name": "Tracearr",
|
||||||
"description": "",
|
"description": "Forbedret overvågning af medieserver med detaljerede stream og omkodningsdata",
|
||||||
"option": {
|
"option": {
|
||||||
"showStreams": {
|
"showStreams": {
|
||||||
"label": ""
|
"label": "Vis aktive streams"
|
||||||
},
|
},
|
||||||
"showStats": {
|
"showStats": {
|
||||||
"label": ""
|
"label": "Vis statistikoversigt"
|
||||||
},
|
},
|
||||||
"showRecentActivity": {
|
"showRecentActivity": {
|
||||||
"label": ""
|
"label": "Vis seneste aktivitet"
|
||||||
},
|
},
|
||||||
"showViolations": {
|
"showViolations": {
|
||||||
"label": ""
|
"label": "Vis overtrædelser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"activeStreams": "",
|
"activeStreams": "Aktive streams",
|
||||||
"totalUsers": "",
|
"totalUsers": "Antal brugere i alt",
|
||||||
"transcodes": "",
|
"transcodes": "Transkodninger",
|
||||||
"bandwidth": ""
|
"bandwidth": "Båndbredde"
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
"empty": "",
|
"empty": "Ingen aktive streams",
|
||||||
"user": "",
|
"user": "Bruger",
|
||||||
"media": "",
|
"media": "Medie",
|
||||||
"state": "",
|
"state": "Tilstand",
|
||||||
"quality": "",
|
"quality": "Kvalitet",
|
||||||
"device": "",
|
"device": "Enhed",
|
||||||
"progress": "",
|
"progress": "Fremskridt",
|
||||||
"videoDecision": {
|
"videoDecision": {
|
||||||
"directPlay": "",
|
"directPlay": "Direkte afspilning",
|
||||||
"transcode": "",
|
"transcode": "Transkod",
|
||||||
"directStream": ""
|
"directStream": "Direkte stream"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recentActivity": {
|
"recentActivity": {
|
||||||
"title": "",
|
"title": "Seneste aktivitet",
|
||||||
"empty": "",
|
"empty": "Ingen nylig aktivitet",
|
||||||
"watched": "",
|
"watched": "Sete",
|
||||||
"partial": ""
|
"partial": "Delvis"
|
||||||
},
|
},
|
||||||
"violations": {
|
"violations": {
|
||||||
"title": "",
|
"title": "Overtrædelser",
|
||||||
"empty": "",
|
"empty": "Ingen nylige overtrædelser",
|
||||||
"rule": "",
|
"rule": "Regel",
|
||||||
"user": ""
|
"user": "Bruger"
|
||||||
},
|
},
|
||||||
"noSectionsEnabled": "",
|
"noSectionsEnabled": "Ingen sektioner aktiveret",
|
||||||
"error": {
|
"error": {
|
||||||
"noIntegration": "",
|
"noIntegration": "Ingen Tracearr integration konfigureret",
|
||||||
"internalServerError": ""
|
"internalServerError": "Kunne ikke hente Tracearr data"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@@ -2153,50 +2192,50 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"immich-serverStats": {
|
"immich-serverStats": {
|
||||||
"name": "",
|
"name": "Immich Server Statistik",
|
||||||
"description": "",
|
"description": "Information om din Immich instans",
|
||||||
"option": {
|
"option": {
|
||||||
"showUsers": {
|
"showUsers": {
|
||||||
"label": "",
|
"label": "Vis brugere",
|
||||||
"description": ""
|
"description": "Vis antal brugere på din instans"
|
||||||
},
|
},
|
||||||
"showPhotos": {
|
"showPhotos": {
|
||||||
"label": "",
|
"label": "Vis billeder",
|
||||||
"description": ""
|
"description": "Vis antal billeder på din instans"
|
||||||
},
|
},
|
||||||
"showVideos": {
|
"showVideos": {
|
||||||
"label": "",
|
"label": "Vis videoer",
|
||||||
"description": ""
|
"description": "Vis antal videoer på din instans"
|
||||||
},
|
},
|
||||||
"showStorage": {
|
"showStorage": {
|
||||||
"label": "",
|
"label": "Vis lagerplads",
|
||||||
"description": ""
|
"description": "Vis den samlede mængde brugte lagerplads"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"users": "",
|
"users": "Brugere",
|
||||||
"photos": "",
|
"photos": "Billeder",
|
||||||
"videos": "",
|
"videos": "Videoer",
|
||||||
"storage": ""
|
"storage": "Brugt lagerplads"
|
||||||
},
|
},
|
||||||
"immich-albumCarousel": {
|
"immich-albumCarousel": {
|
||||||
"name": "",
|
"name": "Immich album",
|
||||||
"option": {
|
"option": {
|
||||||
"albumId": {
|
"albumId": {
|
||||||
"label": "",
|
"label": "Album ID",
|
||||||
"description": ""
|
"description": "Dette er et unikt ID som refererer til dit Immich album. Du kan finde dette id ved at åbne et album og kopiere den unikke streng fra URL, for eksempel 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
|
||||||
},
|
},
|
||||||
"rotationIntervalSeconds": {
|
"rotationIntervalSeconds": {
|
||||||
"label": "",
|
"label": "Interval i sekunder",
|
||||||
"description": ""
|
"description": "Skift mellem billeder i albummet hvert X sekunder. Skal være mindst 1. Skal være under 3600."
|
||||||
},
|
},
|
||||||
"showPhotoInfo": {
|
"showPhotoInfo": {
|
||||||
"label": "",
|
"label": "Vis billedinfo",
|
||||||
"description": ""
|
"description": "Vis dato for billedet og det aktuelle indeks i bunden af diasshowet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "",
|
"description": "Diasshow af billeder i dit Immich album",
|
||||||
"noAlbumSelected": "",
|
"noAlbumSelected": "Intet album valgt, rediger widget og vælg et album",
|
||||||
"noPhotos": ""
|
"noPhotos": "Ingen fotos i dette album"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"name": "Download Klient",
|
"name": "Download Klient",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": "Skjul beskrivelse"
|
"label": "Skjul beskrivelse"
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": "Vis poster billede"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "Ingen notifikationer at vise.",
|
"noItems": "Ingen notifikationer at vise.",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "Speedtest Tracker",
|
||||||
|
"description": "Viser testresultater for hastighed fra din Speedtest Tracker instans.",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": "Vis seneste resultat"
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": "Vis gennemsnit"
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": "Vis seneste resultater"
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": "Vis ping-graf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "Seneste resultat",
|
||||||
|
"averages": "Gennemsnit",
|
||||||
|
"tests": "{count} tests",
|
||||||
|
"recentResults": "Seneste resultater",
|
||||||
|
"recentResultsSpeed": "Hastighed",
|
||||||
|
"recentResultsPing": "Ping",
|
||||||
|
"download": "Download",
|
||||||
|
"upload": "Upload",
|
||||||
|
"ping": "Ping",
|
||||||
|
"status": "Status",
|
||||||
|
"avgDownload": "Gns Download",
|
||||||
|
"avgUpload": "Gns Upload",
|
||||||
|
"avgPing": "Gns Ping",
|
||||||
|
"healthy": "Sund",
|
||||||
|
"unhealthy": "Usund",
|
||||||
|
"ok": "OK",
|
||||||
|
"fail": "Fejl",
|
||||||
|
"noSectionsEnabled": "Aktivér mindst én sektion i widget-indstillinger.",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": "Kunne ikke hente hastighedstestdata"
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "Systemressourcer",
|
"name": "Systemressourcer",
|
||||||
"description": "CPU, Hukommelse, Disk og andet hardwareforbrug af dit system",
|
"description": "CPU, Hukommelse, Disk og andet hardwareforbrug af dit system",
|
||||||
@@ -2731,7 +2812,7 @@
|
|||||||
"option": {
|
"option": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"memory": "Hukommelse",
|
"memory": "Hukommelse",
|
||||||
"gpu": "",
|
"gpu": "GPU",
|
||||||
"network": "Netværk"
|
"network": "Netværk"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2748,39 +2829,39 @@
|
|||||||
"card": {
|
"card": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"memory": "MEM",
|
"memory": "MEM",
|
||||||
"gpu": "",
|
"gpu": "GPU",
|
||||||
"network": "NET",
|
"network": "NET",
|
||||||
"up": "OP",
|
"up": "OP",
|
||||||
"down": "NED"
|
"down": "NED"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systemDisks": {
|
"systemDisks": {
|
||||||
"name": "",
|
"name": "System diske",
|
||||||
"description": "",
|
"description": "Diskforbrug af dit system",
|
||||||
"option": {
|
"option": {
|
||||||
"showTemperatureIfAvailable": {
|
"showTemperatureIfAvailable": {
|
||||||
"label": ""
|
"label": "Vis temperatur, hvis muligt"
|
||||||
},
|
},
|
||||||
"displayMode": {
|
"displayMode": {
|
||||||
"label": "",
|
"label": "Visningstilstand",
|
||||||
"option": {
|
"option": {
|
||||||
"percentage": {
|
"percentage": {
|
||||||
"label": ""
|
"label": "Procentdel (f.eks. 76 %)"
|
||||||
},
|
},
|
||||||
"absolute": {
|
"absolute": {
|
||||||
"label": ""
|
"label": "Absolutte værdier (f.eks. 800GB / 1TB)"
|
||||||
},
|
},
|
||||||
"free": {
|
"free": {
|
||||||
"label": ""
|
"label": "Fri pladsprocent (fx, 24% fri)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"showBackgroundBar": {
|
"showBackgroundBar": {
|
||||||
"label": ""
|
"label": "Vis forbrugslinje i baggrunden"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"unhealthy": ""
|
"unhealthy": "Usund"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3469,10 +3550,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"title": "",
|
"title": "Brugere",
|
||||||
"enableGravatar": {
|
"enableGravatar": {
|
||||||
"label": "",
|
"label": "Aktiver Gravatar",
|
||||||
"description": ""
|
"description": "Falder tilbage til brugernes avatarer fra Libravatar/Gravatar, når ingen brugerdefineret avatar er valgt og en e-mail er konfigureret"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": "Status for Minecraft server"
|
"label": "Status for Minecraft server"
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": "Tidsskema"
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": "Opdatering Af Ikoner"
|
"label": "Opdatering Af Ikoner"
|
||||||
},
|
},
|
||||||
@@ -3583,7 +3667,7 @@
|
|||||||
"label": "Vejr"
|
"label": "Vejr"
|
||||||
},
|
},
|
||||||
"tracearr": {
|
"tracearr": {
|
||||||
"label": ""
|
"label": "Tracearr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interval": {
|
"interval": {
|
||||||
|
|||||||
@@ -1525,6 +1525,17 @@
|
|||||||
"offline": ""
|
"offline": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"title": "",
|
||||||
|
"noStation": "",
|
||||||
|
"option": {
|
||||||
|
"station": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"name": "Notizbuch",
|
"name": "Notizbuch",
|
||||||
"description": "Ein einfaches Notizbuch Widget, das Markdown unterstützt",
|
"description": "Ein einfaches Notizbuch Widget, das Markdown unterstützt",
|
||||||
@@ -1595,6 +1606,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"anchorNote": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"noteId": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showTitle": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showUpdatedAt": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty": "",
|
||||||
|
"emptyContent": "",
|
||||||
|
"notFound": "",
|
||||||
|
"noNotes": "",
|
||||||
|
"integrationRequired": "",
|
||||||
|
"updatedAt": "",
|
||||||
|
"untitled": "",
|
||||||
|
"edit": "",
|
||||||
|
"save": "",
|
||||||
|
"cancel": "",
|
||||||
|
"readOnlyViewer": "",
|
||||||
|
"saveForbidden": "",
|
||||||
|
"saveFailed": ""
|
||||||
|
},
|
||||||
"iframe": {
|
"iframe": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "Einbetten von Inhalten aus dem Internet. Einige Websites können den Zugriff einschränken.",
|
"description": "Einbetten von Inhalten aus dem Internet. Einige Websites können den Zugriff einschränken.",
|
||||||
@@ -2505,6 +2544,9 @@
|
|||||||
},
|
},
|
||||||
"hideDescription": {
|
"hideDescription": {
|
||||||
"label": ""
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPosterImage": {
|
||||||
|
"label": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2718,6 +2760,45 @@
|
|||||||
"noItems": "",
|
"noItems": "",
|
||||||
"option": {}
|
"option": {}
|
||||||
},
|
},
|
||||||
|
"speedtestTracker": {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"option": {
|
||||||
|
"showLatestResult": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showStats": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showRecentResults": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
|
"showPingGraph": {
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latestResult": "",
|
||||||
|
"averages": "",
|
||||||
|
"tests": "",
|
||||||
|
"recentResults": "",
|
||||||
|
"recentResultsSpeed": "",
|
||||||
|
"recentResultsPing": "",
|
||||||
|
"download": "",
|
||||||
|
"upload": "",
|
||||||
|
"ping": "",
|
||||||
|
"status": "",
|
||||||
|
"avgDownload": "",
|
||||||
|
"avgUpload": "",
|
||||||
|
"avgPing": "",
|
||||||
|
"healthy": "",
|
||||||
|
"unhealthy": "",
|
||||||
|
"ok": "",
|
||||||
|
"fail": "",
|
||||||
|
"noSectionsEnabled": "",
|
||||||
|
"error": {
|
||||||
|
"internalServerError": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"systemResources": {
|
"systemResources": {
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
@@ -3513,6 +3594,9 @@
|
|||||||
"minecraftServerStatus": {
|
"minecraftServerStatus": {
|
||||||
"label": "Status des Minecraft Servers"
|
"label": "Status des Minecraft Servers"
|
||||||
},
|
},
|
||||||
|
"timetable": {
|
||||||
|
"label": ""
|
||||||
|
},
|
||||||
"iconsUpdater": {
|
"iconsUpdater": {
|
||||||
"label": "Symbol Updater"
|
"label": "Symbol Updater"
|
||||||
},
|
},
|
||||||
|
|||||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user