Compare commits

..
Author SHA1 Message Date
Releases Homarr f5f7f3e433 chore(release): 1.58.0 [skip ci] 2026-04-03 19:38:14 +00:00
homarr-releases[bot] b1b3b79cd9 chore(release): automatic release v1.58.0 2026-04-03 19:37:17 +00:00
homarr-update-contributors[bot] 35d40d0dea chore: update integrations list readme (#5397)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-03 18:28:41 +00:00
Meier Lukas abb2dfc92a feat(integrations): add search.ch integration with timetable widget (#5180) 2026-04-03 20:23:15 +02:00
homarr-renovate[bot] 279d830b79 chore(deps): update dependency conventional-changelog-conventionalcommits to ^9.3.1 (#5396)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-03 17:25:35 +00:00
homarr-renovate[bot] 72a93e681f fix(deps): update trpc monorepo to ^11.16.0 (#5392)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-03 16:48:45 +00:00
Meier Lukas c977b84e2d fix: node-ical prevents many trpc queries from loading (#5395) 2026-04-03 18:26:52 +02:00
homarr-update-contributors[bot] e92c407beb chore: update contributors (#5394)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-04-03 13:24:06 +00:00
homarr-crowdin[bot]andMeier Lukas 05af82bc02 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-03 01:17:29 +00:00
homarr-crowdin[bot]andMeier Lukas 531bfd6fcf chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-02 18:53:29 +00:00
homarr-renovate[bot] fa40b6b647 fix(deps): update turbo monorepo to ^2.8.21 (#5391)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-02 18:50:09 +00:00
homarr-renovate[bot] c363cf8457 chore(deps): update dependency axios@&gt;&#x3d;1.0.0 &lt;1.8.2 to >=1.14.0 (#5389)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 19:09:32 +00:00
homarr-renovate[bot] 0a78e3f12a fix(deps): update dependency drizzle-orm to ^0.45.2 (#5388)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 19:02:54 +00:00
homarr-crowdin[bot]andMeier Lukas f380b383a3 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-04-01 18:13:33 +02:00
homarr-renovate[bot] b30dbafbbb fix(deps): update tiptap monorepo to v3.21.0 (#5386)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-04-01 15:13:38 +00:00
homarr-renovate[bot] a9bcc9d04b fix(deps): update dependency @tabler/icons-react to ^3.41.0 (#5384)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 23:42:53 +00:00
homarr-renovate[bot] 451e68ab57 fix(deps): update trpc monorepo to ^11.15.1 (#5381)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 20:49:58 +00:00
Meier Lukas a7ed7a4592 ci(renovate): update prebuilt-debian lockfile automatically (#5379) 2026-03-31 22:11:08 +02:00
homarr-renovate[bot] 2dd3c906c4 fix(deps): update dependency @immich/sdk to ^2.6.3 (#5380)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 19:30:29 +00:00
Meier Lukas 6a200c39af fix(boards): unable to click on links at settings page (#5230) 2026-03-31 19:08:02 +02:00
homarr-renovate[bot] e88e3d17c0 fix(deps): update dependency @ctrl/deluge to v8 (#5367)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 18:52:38 +02:00
homarr-renovate[bot]andMeier Lukas 46db8b6ab3 fix(deps): update dependency better-sqlite3 to ^12.8.0 (#5266)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-31 18:43:59 +02:00
homarr-renovate[bot] 545dba0c8f chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.3 (#5377)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 16:07:34 +00:00
homarr-renovate[bot] cbb85159d6 chore(deps): update vitest monorepo to ^4.1.2 (#5378)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 16:06:32 +00:00
homarr-renovate[bot] ff07658212 chore(deps): update node.js (#5364)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-31 11:57:00 +00:00
homarr-renovate[bot] a9582f04ed fix(deps): update dependency zod to ^4.3.6 (#5376)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 21:50:50 +00:00
homarr-renovate[bot]andMeier Lukas 2b4c83dcca fix(deps): update dependency zod to ^4.3.5 - abandoned (#4803)
* fix(deps): update dependency zod to ^4.3.5

* fix: omit can not be used with refine

* fix: convert intersection to object due to trpc-openapi issue

* fix: remove omit after refine

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 23:11:14 +02:00
homarr-renovate[bot] 336c55b3e9 chore(deps): update docker/setup-buildx-action action to v4 (#5200)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:47 +02:00
homarr-renovate[bot] eb5dd3628a chore(deps): update docker/metadata-action action to v6 (#5207)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:37 +02:00
homarr-renovate[bot] c19766e53f chore(deps): update docker/build-push-action action to v7 (#5208)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:58:28 +02:00
homarr-renovate[bot] cc153fff10 chore(deps): update dependency jsdom to v29 (#5274)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:53:20 +02:00
homarr-renovate[bot] b71718be09 chore(deps): update dependency proxmox-api&gt;undici to v7.24.6 (#5373)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 20:52:12 +00:00
homarr-renovate[bot]andMeier Lukas b07c545f17 fix(deps): update dependency node-ical to ^0.23.1 - abandoned (#4856)
* fix(deps): update dependency node-ical to ^0.23.1

* fix: date from node-ical represents utc value

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 22:43:58 +02:00
homarr-renovate[bot] 4aff9819e5 chore(deps): update actions/create-github-app-token action to v3 (#5267)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:24:09 +02:00
homarr-renovate[bot] 9e0c3c0117 chore(deps): update pnpm to v10.33.0 (#5372)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:23:46 +02:00
homarr-crowdin[bot] 107ec7737a chore(lang): update translations from crowdin (#5350)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-03-30 22:23:03 +02:00
homarr-renovate[bot] d4177afc18 chore(deps): update pnpm/action-setup action to v5 (#5294)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 22:22:44 +02:00
Sascha BuehrleandMeier Lukas 7679553f16 fix: honor trusted certificates in Immich integration requests (#5275)
* fix: use trusted-cert fetch for immich sdk requests (fixes #5242)

* fix: use testing fetch for Immich connection check

* fix: typecheck issues

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
2026-03-30 22:21:11 +02:00
homarr-renovate[bot] 4f2cc9af2a fix(deps): update dependency undici to v7.24.6 (#5374)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-30 17:06:11 +00:00
homarr-renovate[bot] 6820f8afea fix(deps): update tiptap monorepo to v3.20.5 (#5371)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 16:43:28 +00:00
homarr-renovate[bot] 4a5bafa790 fix(deps): update dependency @immich/sdk to ^2.6.2 (#5370)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 14:44:39 +00:00
homarr-renovate[bot] 8d1ff4065d fix(deps): update dependency isomorphic-dompurify to ^3.7.1 (#5369)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 10:41:25 +00:00
homarr-renovate[bot] 14d87178b5 fix(deps): update dependency jotai to ^2.19.0 (#5368)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 09:45:23 +00:00
homarr-renovate[bot] c2b54ddfaa fix(deps): update dependency isomorphic-dompurify to ^3.7.0 (#5366)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 09:40:05 +00:00
homarr-renovate[bot] cefbf0e271 fix(deps): update dependency typescript-eslint to ^8.57.2 (#5360)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 07:13:39 +00:00
homarr-renovate[bot] 189806b5a2 fix(deps): update dependency fast-xml-parser to ^5.5.9 (#5359)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 07:12:11 +00:00
homarr-renovate[bot] 9c134c5ceb fix(deps): update dependency @ctrl/deluge to ^7.5.3 (#5365)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-29 05:17:45 +00:00
homarr-renovate[bot] 682099189b fix(deps): update tanstack-query monorepo to ^5.95.2 (#5357)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 16:41:43 +00:00
homarr-renovate[bot] 32218beb7e chore(deps): update vitest monorepo to ^4.1.1 (#5356)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 15:40:16 +00:00
homarr-renovate[bot] 332b6b4f42 fix(deps): update tanstack-query monorepo to ^5.95.1 (#5355)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 14:39:28 +00:00
homarr-renovate[bot] aca893254c chore(deps): update dependency brace-expansion@&gt;&#x3d;2.0.0 &lt;&#x3d;2.0.1 to >=5.0.5 [security] (#5341)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 14:34:27 +00:00
homarr-renovate[bot] 2fb2981d2f fix(deps): update trpc monorepo to ^11.15.0 (#5354)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:06:24 +00:00
homarr-renovate[bot] 5d84888a33 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=8.0.2 (#5353)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:04:53 +00:00
homarr-renovate[bot] 728f7e489a fix(deps): update dependency fastify to ^5.8.4 (#5351)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 13:04:35 +00:00
homarr-renovate[bot] 0e3ac069d9 chore(deps): update dependency hono@&lt;4.6.5 to >=4.12.9 (#5352)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-28 11:35:24 +00:00
homarr-renovate[bot] 505a6b3a89 chore(deps): update dependency brace-expansion@&gt;&#x3d;1.0.0 &lt;&#x3d;1.1.11 to >=5.0.5 [security] (#5340)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 23:35:56 +00:00
homarr-releases[bot] 7c8adab061 chore: update bug report template (#5349)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-27 23:06:11 +01:00
homarr-renovate[bot] b11bf367d0 fix(deps): update tanstack-query monorepo to ^5.95.0 (#5325)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-03-27 21:43:25 +00:00
Releases Homarr 15c2037230 chore(release): 1.57.1 [skip ci] 2026-03-27 21:34:11 +00:00
homarr-releases[bot] 3735c998e4 chore(release): automatic release v1.57.1 2026-03-27 21:32:58 +00:00
Manuel 192a43a031 fix: dashdot gpu (#5347) 2026-03-27 22:32:13 +01:00
homarr-releases[bot] e9c908b317 chore: update bug report template (#5346)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-03-27 21:45:48 +01:00
103 changed files with 2754 additions and 1540 deletions

No files matched your search

+2
View File
@@ -33,6 +33,8 @@ 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.57.1
- 1.57.0
- 1.56.1 - 1.56.1
- 1.56.0 - 1.56.0
- 1.55.0 - 1.55.0
+2 -2
View File
@@ -15,11 +15,11 @@ runs:
with: with:
persist-credentials: false persist-credentials: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build docker image for ${{ inputs.architecture }} - name: Build docker image for ${{ inputs.architecture }}
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
push: false push: false
load: true load: true
+6
View File
@@ -31,6 +31,12 @@
automerge: false, automerge: false,
}, },
], ],
// Updates lockfile for prebuilt-debian after if package.json is updated, otherwise only root lockfile is updated
postUpgradeTasks: {
commands: ["cd deployments/prebuilt-debian && pnpm install --ignore-workspace --lockfile-only"],
fileFilters: ["deployments/prebuilt-debian/pnpm-lock.yaml"],
executionMode: "update",
},
updateInternalDeps: true, updateInternalDeps: true,
rangeStrategy: "bump", rangeStrategy: "bump",
automerge: false, automerge: false,
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }} app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
+1 -1
View File
@@ -89,7 +89,7 @@ jobs:
uses: ./tooling/github/setup uses: ./tooling/github/setup
- name: Build docker image - name: Build docker image
id: build-docker-image id: build-docker-image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
platforms: linux/amd64 platforms: linux/amd64
context: . context: .
@@ -19,7 +19,7 @@ jobs:
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }} private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
app-id: ${{ secrets.CROWDIN_APP_ID }} app-id: ${{ secrets.CROWDIN_APP_ID }}
+10 -10
View File
@@ -50,7 +50,7 @@ jobs:
- name: Obtain token - name: Obtain token
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
@@ -60,12 +60,12 @@ jobs:
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
with: with:
persist-credentials: false persist-credentials: false
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
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'
with: with:
node-version: 24.14.0 node-version: 24.14.1
cache: "pnpm" cache: "pnpm"
- run: npm i -g pnpm - run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
@@ -123,7 +123,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: "${{ env.GHCR_REPO }}" images: "${{ env.GHCR_REPO }}"
@@ -135,11 +135,11 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
network: host network: host
@@ -162,7 +162,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v6
with: with:
images: "${{ env.GHCR_REPO }}" images: "${{ env.GHCR_REPO }}"
@@ -174,11 +174,11 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
network: host network: host
@@ -231,7 +231,7 @@ jobs:
- name: Obtain token - name: Obtain token
if: needs.release.outputs.skipped == 'false' if: needs.release.outputs.skipped == 'false'
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
@@ -50,7 +50,7 @@ jobs:
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})" args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
- name: Obtain token - name: Obtain token
id: obtainApprovalToken id: obtainApprovalToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }} app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
@@ -62,7 +62,7 @@ jobs:
gh pr review --approve --body "Automatically approved by GitHub Action" gh pr review --approve --body "Automatically approved by GitHub Action"
- name: Obtain token - name: Obtain token
id: obtainMergeToken id: obtainMergeToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }} private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }} app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
@@ -45,7 +45,7 @@ jobs:
steps: steps:
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }} private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
steps: steps:
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }} private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }} app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
@@ -33,7 +33,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v5
- 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:
@@ -21,7 +21,7 @@ jobs:
steps: steps:
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v2 uses: actions/create-github-app-token@v3
with: with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }} private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }} app-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
+1 -1
View File
@@ -1 +1 @@
24.14.0 24.14.1
+47
View File
@@ -1,3 +1,50 @@
## [1.58.0](https://github.com/homarr-labs/homarr/compare/v1.57.1...v1.58.0) (2026-04-03)
### Features
* **integrations:** add search.ch integration with timetable widget ([#5180](https://github.com/homarr-labs/homarr/issues/5180)) ([abb2dfc](https://github.com/homarr-labs/homarr/commit/abb2dfc92ab51b16e7172374cfee8b9c0c752454))
### Bug Fixes
* **boards:** unable to click on links at settings page ([#5230](https://github.com/homarr-labs/homarr/issues/5230)) ([6a200c3](https://github.com/homarr-labs/homarr/commit/6a200c39afaa1a9a1057643612065a40ad1cdd82))
* **deps:** update dependency @ctrl/deluge to ^7.5.3 ([#5365](https://github.com/homarr-labs/homarr/issues/5365)) ([9c134c5](https://github.com/homarr-labs/homarr/commit/9c134c5ceb742b21b021ef3b49de8cc0d526d460))
* **deps:** update dependency @ctrl/deluge to v8 ([#5367](https://github.com/homarr-labs/homarr/issues/5367)) ([e88e3d1](https://github.com/homarr-labs/homarr/commit/e88e3d17c01dab8836bec11d2ac01195f06cdc2e))
* **deps:** update dependency @immich/sdk to ^2.6.2 ([#5370](https://github.com/homarr-labs/homarr/issues/5370)) ([4a5bafa](https://github.com/homarr-labs/homarr/commit/4a5bafa79092b209e94a56f72362011f416bf115))
* **deps:** update dependency @immich/sdk to ^2.6.3 ([#5380](https://github.com/homarr-labs/homarr/issues/5380)) ([2dd3c90](https://github.com/homarr-labs/homarr/commit/2dd3c906c4215b55e23f4b15ba4b423e23be8376))
* **deps:** update dependency @tabler/icons-react to ^3.41.0 ([#5384](https://github.com/homarr-labs/homarr/issues/5384)) ([a9bcc9d](https://github.com/homarr-labs/homarr/commit/a9bcc9d04bdfcf28ea561cd590ca2a4bd775593c))
* **deps:** update dependency better-sqlite3 to ^12.8.0 ([#5266](https://github.com/homarr-labs/homarr/issues/5266)) ([46db8b6](https://github.com/homarr-labs/homarr/commit/46db8b6ab32406fb4ff7a7a9938692954778a070))
* **deps:** update dependency drizzle-orm to ^0.45.2 ([#5388](https://github.com/homarr-labs/homarr/issues/5388)) ([0a78e3f](https://github.com/homarr-labs/homarr/commit/0a78e3f12aa99509f42d7af647a478c5abaf5c96))
* **deps:** update dependency fast-xml-parser to ^5.5.9 ([#5359](https://github.com/homarr-labs/homarr/issues/5359)) ([189806b](https://github.com/homarr-labs/homarr/commit/189806b5a2b8e67f925cc8def6bc8ddefdc5bfc4))
* **deps:** update dependency fastify to ^5.8.4 ([#5351](https://github.com/homarr-labs/homarr/issues/5351)) ([728f7e4](https://github.com/homarr-labs/homarr/commit/728f7e489aa499bcd7e97c7ec8571e29e7e2cab3))
* **deps:** update dependency isomorphic-dompurify to ^3.7.0 ([#5366](https://github.com/homarr-labs/homarr/issues/5366)) ([c2b54dd](https://github.com/homarr-labs/homarr/commit/c2b54ddfaab93daa4e65c0c38bf5c27e4402a492))
* **deps:** update dependency isomorphic-dompurify to ^3.7.1 ([#5369](https://github.com/homarr-labs/homarr/issues/5369)) ([8d1ff40](https://github.com/homarr-labs/homarr/commit/8d1ff4065d669268f0e6a03f2b68c1ba728a756a))
* **deps:** update dependency jotai to ^2.19.0 ([#5368](https://github.com/homarr-labs/homarr/issues/5368)) ([14d8717](https://github.com/homarr-labs/homarr/commit/14d87178b5a93bdc6fb74793feed95d014814d0b))
* **deps:** update dependency node-ical to ^0.23.1 - abandoned ([#4856](https://github.com/homarr-labs/homarr/issues/4856)) ([b07c545](https://github.com/homarr-labs/homarr/commit/b07c545f17f4dafa62167047baa9eed4b7ee4395))
* **deps:** update dependency typescript-eslint to ^8.57.2 ([#5360](https://github.com/homarr-labs/homarr/issues/5360)) ([cefbf0e](https://github.com/homarr-labs/homarr/commit/cefbf0e2715e9adbc72cdb0b83d84c309e3257d6))
* **deps:** update dependency undici to v7.24.6 ([#5374](https://github.com/homarr-labs/homarr/issues/5374)) ([4f2cc9a](https://github.com/homarr-labs/homarr/commit/4f2cc9af2a42e19624def3bdea60fcb00334c483))
* **deps:** update dependency zod to ^4.3.5 - abandoned ([#4803](https://github.com/homarr-labs/homarr/issues/4803)) ([2b4c83d](https://github.com/homarr-labs/homarr/commit/2b4c83dccac168ffa6f9b6e184ed004659373cf7))
* **deps:** update dependency zod to ^4.3.6 ([#5376](https://github.com/homarr-labs/homarr/issues/5376)) ([a9582f0](https://github.com/homarr-labs/homarr/commit/a9582f04edb0e161d1fab6e64b13d0ce463ef341))
* **deps:** update tanstack-query monorepo to ^5.95.0 ([#5325](https://github.com/homarr-labs/homarr/issues/5325)) ([b11bf36](https://github.com/homarr-labs/homarr/commit/b11bf367d0567e4c8b9068804442ebd200dab01c))
* **deps:** update tanstack-query monorepo to ^5.95.1 ([#5355](https://github.com/homarr-labs/homarr/issues/5355)) ([332b6b4](https://github.com/homarr-labs/homarr/commit/332b6b4f42e7c90570cd4c25a6e6a331a76c660f))
* **deps:** update tanstack-query monorepo to ^5.95.2 ([#5357](https://github.com/homarr-labs/homarr/issues/5357)) ([6820991](https://github.com/homarr-labs/homarr/commit/682099189b689ea17e16af41fd280a72ed68016f))
* **deps:** update tiptap monorepo to v3.20.5 ([#5371](https://github.com/homarr-labs/homarr/issues/5371)) ([6820f8a](https://github.com/homarr-labs/homarr/commit/6820f8afea622587c1ee18b13d13a041ca0572ee))
* **deps:** update tiptap monorepo to v3.21.0 ([#5386](https://github.com/homarr-labs/homarr/issues/5386)) ([b30dbaf](https://github.com/homarr-labs/homarr/commit/b30dbafbbbad468ff37ff11237a64ec29df62345))
* **deps:** update trpc monorepo to ^11.15.0 ([#5354](https://github.com/homarr-labs/homarr/issues/5354)) ([2fb2981](https://github.com/homarr-labs/homarr/commit/2fb2981d2fb79ca2ca6a9bc5c08d91c8fa5710e7))
* **deps:** update trpc monorepo to ^11.15.1 ([#5381](https://github.com/homarr-labs/homarr/issues/5381)) ([451e68a](https://github.com/homarr-labs/homarr/commit/451e68ab57551e23dd7ae772b7483cc56d57ebbb))
* **deps:** update trpc monorepo to ^11.16.0 ([#5392](https://github.com/homarr-labs/homarr/issues/5392)) ([72a93e6](https://github.com/homarr-labs/homarr/commit/72a93e681f3701459176692857220730c279f80b))
* **deps:** update turbo monorepo to ^2.8.21 ([#5391](https://github.com/homarr-labs/homarr/issues/5391)) ([fa40b6b](https://github.com/homarr-labs/homarr/commit/fa40b6b647b884a7f10c268a22330e0e68b50d4d))
* honor trusted certificates in Immich integration requests ([#5275](https://github.com/homarr-labs/homarr/issues/5275)) ([7679553](https://github.com/homarr-labs/homarr/commit/7679553f16ff2945a3eb6cb668ca3a701d13d5ce)), closes [#5242](https://github.com/homarr-labs/homarr/issues/5242)
* node-ical prevents many trpc queries from loading ([#5395](https://github.com/homarr-labs/homarr/issues/5395)) ([c977b84](https://github.com/homarr-labs/homarr/commit/c977b84e2d84d5724219f5ff5f99bc550438e168))
## [1.57.1](https://github.com/homarr-labs/homarr/compare/v1.57.0...v1.57.1) (2026-03-27)
### Bug Fixes
* dashdot gpu ([#5347](https://github.com/homarr-labs/homarr/issues/5347)) ([192a43a](https://github.com/homarr-labs/homarr/commit/192a43a031eb9431da2c11395eda69a6c7236b9b))
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.11 ([#5345](https://github.com/homarr-labs/homarr/issues/5345)) ([49d9394](https://github.com/homarr-labs/homarr/commit/49d9394cd78b2a1146c8dc00dd982215a2f9a580))
* **deps:** update dependency dockerode to ^4.0.10 ([#5327](https://github.com/homarr-labs/homarr/issues/5327)) ([42a3791](https://github.com/homarr-labs/homarr/commit/42a3791e6b5664a0f28d3ee4d29d5257ef3765f6))
* **deps:** update dependency fast-xml-parser to ^5.5.8 ([#5324](https://github.com/homarr-labs/homarr/issues/5324)) ([7982938](https://github.com/homarr-labs/homarr/commit/798293813cf82e38d7059069823f927b7cdfb3b8))
## [1.57.0](https://github.com/homarr-labs/homarr/compare/v1.56.1...v1.57.0) (2026-03-27) ## [1.57.0](https://github.com/homarr-labs/homarr/compare/v1.56.1...v1.57.0) (2026-03-27)
### Features ### Features
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24.14.0-alpine AS base FROM node:24.14.1-alpine AS base
FROM base AS builder FROM base AS builder
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
+11 -11
View File
@@ -55,14 +55,14 @@
"@mantine/modals": "^8.3.18", "@mantine/modals": "^8.3.18",
"@mantine/tiptap": "^8.3.18", "@mantine/tiptap": "^8.3.18",
"@million/lint": "1.0.14", "@million/lint": "1.0.14",
"@tabler/icons-react": "^3.40.0", "@tabler/icons-react": "^3.41.0",
"@tanstack/react-query": "^5.91.2", "@tanstack/react-query": "^5.95.2",
"@tanstack/react-query-devtools": "^5.91.3", "@tanstack/react-query-devtools": "^5.95.2",
"@tanstack/react-query-next-experimental": "^5.91.0", "@tanstack/react-query-next-experimental": "^5.95.2",
"@trpc/client": "^11.14.1", "@trpc/client": "^11.16.0",
"@trpc/next": "^11.14.1", "@trpc/next": "^11.16.0",
"@trpc/react-query": "^11.14.1", "@trpc/react-query": "^11.16.0",
"@trpc/server": "^11.14.1", "@trpc/server": "^11.16.0",
"@xterm/addon-canvas": "^0.7.0", "@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.11.0", "@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
@@ -73,8 +73,8 @@
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"flag-icons": "^7.5.0", "flag-icons": "^7.5.0",
"glob": "^13.0.6", "glob": "^13.0.6",
"isomorphic-dompurify": "^3.6.0", "isomorphic-dompurify": "^3.7.1",
"jotai": "^2.18.1", "jotai": "^2.19.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.2.1", "next": "16.2.1",
"postcss-preset-mantine": "^1.18.0", "postcss-preset-mantine": "^1.18.0",
@@ -87,7 +87,7 @@
"superjson": "2.2.6", "superjson": "2.2.6",
"swagger-ui-react": "^5.32.1", "swagger-ui-react": "^5.32.1",
"use-deep-compare-effect": "^1.8.1", "use-deep-compare-effect": "^1.8.1",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
@@ -2,7 +2,7 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Button, Grid, Group, Loader, Stack, TextInput } from "@mantine/core"; import { Button, Grid, Group, Loader, Stack, TextInput } from "@mantine/core";
import { useDebouncedValue, useDocumentTitle, useFavicon } from "@mantine/hooks"; import { useDebouncedValue, useDocumentTitle } from "@mantine/hooks";
import { useUpdateBoard } from "@homarr/boards/updater"; import { useUpdateBoard } from "@homarr/boards/updater";
import { useZodForm } from "@homarr/form"; import { useZodForm } from "@homarr/form";
@@ -10,6 +10,7 @@ import { IconPicker } from "@homarr/forms-collection";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { boardSavePartialSettingsSchema } from "@homarr/validation/board"; import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
import { createMetaTitle } from "~/metadata"; import { createMetaTitle } from "~/metadata";
import type { Board } from "../../_types"; import type { Board } from "../../_types";
import { useSavePartialSettingsMutation } from "./_shared"; import { useSavePartialSettingsMutation } from "./_shared";
@@ -53,7 +54,6 @@ export const GeneralSettingsContent = ({ board }: Props) => {
); );
useLogoPreview(form.values.logoImageUrl); useLogoPreview(form.values.logoImageUrl);
useFaviconPreview(form.values.faviconImageUrl);
const metaTitleStatus = useMetaTitlePreview(form.values.metaTitle); const metaTitleStatus = useMetaTitlePreview(form.values.metaTitle);
// Cleanup for not applied changes of the page title and logo image URL // Cleanup for not applied changes of the page title and logo image URL
@@ -75,6 +75,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
pageTitle: values.pageTitle, pageTitle: values.pageTitle,
logoImageUrl: values.logoImageUrl, logoImageUrl: values.logoImageUrl,
}; };
updateFavicon(values.faviconImageUrl ?? homarrLogoPath);
savePartialSettings({ savePartialSettings({
id: board.id, id: board.id,
...values, ...values,
@@ -146,7 +147,13 @@ const useMetaTitlePreview = (title: string | null) => {
}; };
}; };
const useFaviconPreview = (url: string | null) => { // Previously we used the useFavicon hook from Mantine
const [faviconDebounced] = useDebouncedValue(url ?? "", 500); // However if the user tried to click on a link to a different page
useFavicon(faviconDebounced); // it caused an error "Cannot read properties of null (reading 'removeChild')"
// Probably because the head element was null (https://github.com/mantinedev/mantine/blob/b90d9b81031f45f5d15e75f25138ed6477f65bce/packages/%40mantine/hooks/src/use-favicon/use-favicon.ts#L21)
// See https://github.com/homarr-labs/homarr/issues/4905
const updateFavicon = (url: string) => {
const link: HTMLLinkElement | null = document.querySelector('link[rel="icon"]');
if (!link) return;
link.href = url;
}; };
+2 -2
View File
@@ -39,9 +39,9 @@
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"fastify": "^5.8.2", "fastify": "^5.8.4",
"superjson": "2.2.6", "superjson": "2.2.6",
"undici": "7.24.5" "undici": "7.24.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:24.14.0-trixie AS base FROM node:24.14.1-trixie AS base
WORKDIR /app WORKDIR /app
COPY package.json . COPY package.json .
COPY pnpm-lock.yaml . COPY pnpm-lock.yaml .
+4 -4
View File
@@ -2,12 +2,12 @@
"name": "homarr-prebuilt-debian", "name": "homarr-prebuilt-debian",
"private": true, "private": true,
"dependencies": { "dependencies": {
"better-sqlite3": "^12.6.2" "better-sqlite3": "^12.8.0"
}, },
"packageManager": "pnpm@10.32.1", "packageManager": "pnpm@10.33.0",
"engines": { "engines": {
"node": ">=24.14.0", "node": ">=24.14.1",
"pnpm": ">=10.32.1" "pnpm": ">=10.33.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
+6 -5
View File
@@ -9,16 +9,16 @@ importers:
.: .:
dependencies: dependencies:
better-sqlite3: better-sqlite3:
specifier: ^12.6.2 specifier: ^12.8.0
version: 12.6.2 version: 12.8.0
packages: packages:
base64-js@1.5.1: base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
better-sqlite3@12.6.2: better-sqlite3@12.8.0:
resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} resolution: {integrity: sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==}
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
bindings@1.5.0: bindings@1.5.0:
@@ -93,6 +93,7 @@ packages:
prebuild-install@7.1.3: prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'} engines: {node: '>=10'}
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
hasBin: true hasBin: true
pump@3.0.3: pump@3.0.3:
@@ -147,7 +148,7 @@ snapshots:
base64-js@1.5.1: {} base64-js@1.5.1: {}
better-sqlite3@12.6.2: better-sqlite3@12.8.0:
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
prebuild-install: 7.1.3 prebuild-install: 7.1.3
+11 -4
View File
@@ -302,6 +302,13 @@
</a> </a>
</td></tr> </td></tr>
<tr><td align="center"> <tr><td align="center">
<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" />
<br/>
<p align="center">Search.ch</p>
</a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/seerr" target="_blank" rel="noreferrer noopener"> <a href="https://homarr.dev/docs/integrations/seerr" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/seerr.svg" alt="Seerr" width="90" height="90" /> <img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/seerr.svg" alt="Seerr" width="90" height="90" />
<br/> <br/>
@@ -342,15 +349,15 @@
<br/> <br/>
<p align="center">Transmission</p> <p align="center">Transmission</p>
</a> </a>
</td> </td></tr>
<td align="center"> <tr><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/>
<p align="center">TrueNAS</p> <p align="center">TrueNAS</p>
</a> </a>
</td></tr> </td>
<tr><td align="center"> <td align="center">
<a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener"> <a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png" alt="Unifi Controller" width="90" height="90" /> <img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png" alt="Unifi Controller" width="90" height="90" />
<br/> <br/>
+17 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "homarr", "name": "homarr",
"version": "1.57.0", "version": "1.58.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "cross-env CI=true turbo build", "build": "cross-env CI=true turbo build",
@@ -43,26 +43,26 @@
"@semantic-release/npm": "^13.1.5", "@semantic-release/npm": "^13.1.5",
"@semantic-release/release-notes-generator": "^14.1.0", "@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.13.0", "@testcontainers/redis": "^11.13.0",
"@turbo/gen": "^2.8.20", "@turbo/gen": "^2.8.21",
"@vitejs/plugin-react": "^5.2.0", "@vitejs/plugin-react": "^5.2.0",
"@vitest/coverage-v8": "^4.1.0", "@vitest/coverage-v8": "^4.1.2",
"@vitest/ui": "^4.1.0", "@vitest/ui": "^4.1.2",
"conventional-changelog-conventionalcommits": "^9.3.0", "conventional-changelog-conventionalcommits": "^9.3.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"jsdom": "^28.1.0", "jsdom": "^29.0.1",
"json5": "^2.2.3", "json5": "^2.2.3",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"semantic-release": "^25.0.3", "semantic-release": "^25.0.3",
"testcontainers": "^11.13.0", "testcontainers": "^11.13.0",
"turbo": "^2.8.20", "turbo": "^2.8.21",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite-tsconfig-paths": "^6.1.1", "vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.1.0" "vitest": "^4.1.2"
}, },
"packageManager": "pnpm@10.32.1", "packageManager": "pnpm@10.33.0",
"engines": { "engines": {
"node": ">=24.14.0", "node": ">=24.14.1",
"pnpm": ">=10.32.1" "pnpm": ">=10.33.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
@@ -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.13.6", "axios@>=1.0.0 <1.8.2": ">=1.14.0",
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.4", "brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.5",
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.4", "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.5",
"esbuild@<=0.24.2": ">=0.27.4", "esbuild@<=0.24.2": ">=0.27.4",
"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.8", "hono@<4.6.5": ">=4.12.9",
"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.5", "proxmox-api>undici": "7.24.6",
"react-is": "^19.2.4", "react-is": "^19.2.4",
"rollup@>=4.0.0 <4.22.4": ">=4.60.0", "rollup@>=4.0.0 <4.22.4": ">=4.60.0",
"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.1" "vite@>=5.0.0 <=5.4.18": ">=8.0.3"
}, },
"patchedDependencies": { "patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch", "@types/node-unifi": "patches/@types__node-unifi.patch",
+6 -6
View File
@@ -41,18 +41,18 @@
"@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": "^1.4.0",
"@tanstack/react-query": "^5.91.2", "@tanstack/react-query": "^5.95.2",
"@trpc/client": "^11.14.1", "@trpc/client": "^11.16.0",
"@trpc/react-query": "^11.14.1", "@trpc/react-query": "^11.16.0",
"@trpc/server": "^11.14.1", "@trpc/server": "^11.16.0",
"@trpc/tanstack-react-query": "^11.14.1", "@trpc/tanstack-react-query": "^11.16.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"next": "16.2.1", "next": "16.2.1",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"superjson": "2.2.6", "superjson": "2.2.6",
"trpc-to-openapi": "^3.2.0", "trpc-to-openapi": "^3.2.0",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -145,7 +145,7 @@ export const userRouter = createTRPCRouter({
create: permissionRequiredProcedure create: permissionRequiredProcedure
.requiresPermission("admin") .requiresPermission("admin")
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } }) .meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
.input(userCreateSchema) .input(convertIntersectionToZodObject(userCreateSchema))
.output(z.void()) .output(z.void())
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled(); throwIfCredentialsDisabled();
@@ -376,7 +376,7 @@ export const userRouter = createTRPCRouter({
await ctx.db.delete(users).where(eq(users.id, input.userId)); await ctx.db.delete(users).where(eq(users.id, input.userId));
}), }),
changePassword: protectedProcedure changePassword: protectedProcedure
.input(userChangePasswordApiSchema) .input(convertIntersectionToZodObject(userChangePasswordApiSchema))
.output(z.void()) .output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } }) .meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
+1
View File
@@ -26,5 +26,6 @@ export const widgetRouter = createTRPCRouter({
networkController: lazy(() => import("./network-controller").then((mod) => mod.networkControllerRouter)), networkController: lazy(() => import("./network-controller").then((mod) => mod.networkControllerRouter)),
firewall: lazy(() => import("./firewall").then((mod) => mod.firewallRouter)), firewall: lazy(() => import("./firewall").then((mod) => mod.firewallRouter)),
notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)), notifications: lazy(() => import("./notifications").then((mod) => mod.notificationsRouter)),
timetable: lazy(() => import("./timetable").then((mod) => mod.timetableRouter)),
tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)), tracearr: lazy(() => import("./tracearr").then((mod) => mod.tracearrRouter)),
}); });
@@ -0,0 +1,43 @@
import z from "zod";
import { getIntegrationKindsByCategory } from "@homarr/definitions";
import { timetableOptionsSchema } from "@homarr/integrations/types";
import {
timetableGetTimetableRequestHandler,
timetableSearchStationsRequestHandler,
} from "@homarr/request-handler/timetable";
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
import { createTRPCRouter, publicProcedure } from "../../trpc";
export const timetableRouter = createTRPCRouter({
getTimetable: publicProcedure
.concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("timetable")))
.input(timetableOptionsSchema)
.query(async ({ input, ctx }) => {
const innerHandler = timetableGetTimetableRequestHandler.handler(ctx.integration, {
stationId: input.stationId,
limit: input.limit,
});
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
}),
searchStations: publicProcedure
.concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("timetable")))
.input(
z.object({
query: z.string(),
}),
)
.query(async ({ input, ctx }) => {
const innerHandler = timetableSearchStationsRequestHandler.handler(ctx.integration, {
query: input.query,
});
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
return data;
}),
});
+1 -1
View File
@@ -38,7 +38,7 @@
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -34,8 +34,8 @@
"octokit": "^5.0.5", "octokit": "^5.0.5",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"undici": "7.24.5", "undici": "7.24.6",
"zod": "^4.2.1", "zod": "^4.3.6",
"zod-validation-error": "^5.0.0" "zod-validation-error": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
+3 -3
View File
@@ -38,15 +38,15 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@t3-oss/env-nextjs": "^0.13.11", "@t3-oss/env-nextjs": "^0.13.11",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.8.0",
"dns-caching": "^0.2.9", "dns-caching": "^0.2.9",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.2",
"ioredis": "5.10.1", "ioredis": "5.10.1",
"mysql2": "3.20.0", "mysql2": "3.20.0",
"pg": "^8.20.0", "pg": "^8.20.0",
"superjson": "2.2.6", "superjson": "2.2.6",
"winston": "3.19.0", "winston": "3.19.0",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+5 -5
View File
@@ -28,13 +28,13 @@
"@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.91.2", "@tanstack/react-query": "^5.95.2",
"@trpc/client": "^11.14.1", "@trpc/client": "^11.16.0",
"@trpc/server": "^11.14.1", "@trpc/server": "^11.16.0",
"@trpc/tanstack-react-query": "^11.14.1", "@trpc/tanstack-react-query": "^11.16.0",
"node-cron": "^4.2.1", "node-cron": "^4.2.1",
"react": "19.2.4", "react": "19.2.4",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2
View File
@@ -18,6 +18,7 @@ import { mediaServerJob } from "./jobs/integrations/media-server";
import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding"; import { mediaTranscodingJob } from "./jobs/integrations/media-transcoding";
import { networkControllerJob } from "./jobs/integrations/network-controller"; import { networkControllerJob } from "./jobs/integrations/network-controller";
import { refreshNotificationsJob } from "./jobs/integrations/notifications"; import { refreshNotificationsJob } from "./jobs/integrations/notifications";
import { timetableJob } from "./jobs/integrations/timetable";
import { tracearrJob } from "./jobs/integrations/tracearr"; import { tracearrJob } from "./jobs/integrations/tracearr";
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status"; import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
import { pingJob } from "./jobs/ping"; import { pingJob } from "./jobs/ping";
@@ -51,6 +52,7 @@ export const jobGroup = createCronJobGroup({
firewallInterfaces: firewallInterfacesJob, firewallInterfaces: firewallInterfacesJob,
refreshNotifications: refreshNotificationsJob, refreshNotifications: refreshNotificationsJob,
weather: weatherJob, weather: weatherJob,
timetable: timetableJob,
tracearr: tracearrJob, tracearr: tracearrJob,
}); });
@@ -0,0 +1,20 @@
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { createRequestIntegrationJobHandler } from "@homarr/request-handler/lib/cached-request-integration-job-handler";
import { timetableGetTimetableRequestHandler } from "@homarr/request-handler/timetable";
import { createCronJob } from "../../lib";
export const timetableJob = createCronJob("timetable", EVERY_5_MINUTES).withCallback(
createRequestIntegrationJobHandler(timetableGetTimetableRequestHandler.handler, {
widgetKinds: ["timetable"],
getInput: {
timetable: (options) =>
options.station
? {
stationId: options.station.value,
limit: 10,
}
: [],
},
}),
);
+2 -2
View File
@@ -51,10 +51,10 @@
"@paralleldrive/cuid2": "^3.3.0", "@paralleldrive/cuid2": "^3.3.0",
"@testcontainers/mysql": "^11.13.0", "@testcontainers/mysql": "^11.13.0",
"@testcontainers/postgresql": "^11.13.0", "@testcontainers/postgresql": "^11.13.0",
"better-sqlite3": "^12.6.2", "better-sqlite3": "^12.8.0",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"drizzle-kit": "^0.31.10", "drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3", "drizzle-zod": "^0.8.3",
"mysql2": "3.20.0", "mysql2": "3.20.0",
"pg": "^8.20.0", "pg": "^8.20.0",
+2 -2
View File
@@ -24,8 +24,8 @@
"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.8", "fast-xml-parser": "^5.5.9",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+10
View File
@@ -334,6 +334,15 @@ export const integrationDefs = {
// @ts-expect-error - docs page will be created when integration is merged // @ts-expect-error - docs page will be created when integration is merged
documentationUrl: createDocumentationLink("/docs/integrations/coolify"), documentationUrl: createDocumentationLink("/docs/integrations/coolify"),
}, },
searchCh: {
name: "Search.ch",
secretKinds: [[]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png",
defaultUrl: "https://search.ch",
category: ["timetable"],
// @ts-expect-error - docs page will be created when integration is merged
documentationUrl: createDocumentationLink("/docs/integrations/search-ch"),
},
immich: { immich: {
name: "Immich", name: "Immich",
secretKinds: [["apiKey"]], secretKinds: [["apiKey"]],
@@ -434,6 +443,7 @@ export const integrationCategories = [
"releasesProvider", "releasesProvider",
"notifications", "notifications",
"firewall", "firewall",
"timetable",
"photoService", "photoService",
"mediaMonitoring", "mediaMonitoring",
] as const; ] as const;
+1
View File
@@ -31,6 +31,7 @@ export const widgetKinds = [
"systemResources", "systemResources",
"coolify", "coolify",
"systemDisks", "systemDisks",
"timetable",
"immich-serverStats", "immich-serverStats",
"immich-albumCarousel", "immich-albumCarousel",
"tracearr", "tracearr",
+1 -1
View File
@@ -28,7 +28,7 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.18", "@mantine/form": "^8.3.18",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.18", "@mantine/core": "^8.3.18",
"react": "19.2.4", "react": "19.2.4",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+5 -5
View File
@@ -25,7 +25,7 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@ctrl/deluge": "^7.5.2", "@ctrl/deluge": "^8.0.0",
"@ctrl/qbittorrent": "^9.11.2", "@ctrl/qbittorrent": "^9.11.2",
"@ctrl/transmission": "^7.4.0", "@ctrl/transmission": "^7.4.0",
"@gitbeaker/rest": "^43.8.0", "@gitbeaker/rest": "^43.8.0",
@@ -38,18 +38,18 @@
"@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.1", "@immich/sdk": "^2.6.3",
"@jellyfin/sdk": "^0.13.0", "@jellyfin/sdk": "^0.13.0",
"@octokit/auth-app": "^8.2.0", "@octokit/auth-app": "^8.2.0",
"ical.js": "^2.2.1", "ical.js": "^2.2.1",
"maria2": "^0.4.1", "maria2": "^0.4.1",
"node-ical": "^0.22.1",
"octokit": "^5.0.5", "octokit": "^5.0.5",
"proxmox-api": "1.1.1", "proxmox-api": "1.1.1",
"timezones-ical-library": "^2.1.3",
"tsdav": "^2.1.8", "tsdav": "^2.1.8",
"undici": "7.24.5", "undici": "7.24.6",
"xml2js": "^0.6.2", "xml2js": "^0.6.2",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
@@ -40,6 +40,7 @@ import { PlexIntegration } from "../plex/plex-integration";
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration"; import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
import { ProxmoxIntegration } from "../proxmox/proxmox-integration"; 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 { SeerrIntegration } from "../seerr/seerr-integration"; import { SeerrIntegration } from "../seerr/seerr-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";
@@ -114,6 +115,7 @@ export const integrationCreators = {
coolify: CoolifyIntegration, coolify: CoolifyIntegration,
tracearr: TracearrIntegration, tracearr: TracearrIntegration,
glances: GlancesIntegration, glances: GlancesIntegration,
searchCh: SearchChIntegration,
immich: ImmichIntegration, immich: ImmichIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>; } satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
@@ -1,5 +1,6 @@
import { getAlbumInfo, getAllAlbums, getMyUser, getServerStatistics, init, searchUsers } from "@immich/sdk"; import { getAlbumInfo, getAllAlbums, getMyUser, getServerStatistics, init, searchUsers } from "@immich/sdk";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { ImageProxy } from "@homarr/image-proxy"; import { ImageProxy } from "@homarr/image-proxy";
@@ -41,7 +42,10 @@ export class ImmichIntegration extends Integration {
public async getServerStatsAsync(): Promise<ImmichServerStats> { public async getServerStatsAsync(): Promise<ImmichServerStats> {
this.initClient(); this.initClient();
const [statistics, users] = await Promise.all([getServerStatistics(), searchUsers()]); const [statistics, users] = await Promise.all([
getServerStatistics(this.getRequestOptions()),
searchUsers(this.getRequestOptions()),
]);
return { return {
photoCount: statistics.photos, photoCount: statistics.photos,
userCount: users.length, userCount: users.length,
@@ -52,7 +56,7 @@ export class ImmichIntegration extends Integration {
public async getAlbumAsync(albumId: string): Promise<ImmichAlbum> { public async getAlbumAsync(albumId: string): Promise<ImmichAlbum> {
this.initClient(); this.initClient();
const album = await getAlbumInfo({ id: albumId }); const album = await getAlbumInfo({ id: albumId }, this.getRequestOptions());
const imageProxy = new ImageProxy(); const imageProxy = new ImageProxy();
return { return {
albumName: album.albumName, albumName: album.albumName,
@@ -89,17 +93,24 @@ export class ImmichIntegration extends Integration {
}[] }[]
> { > {
this.initClient(); this.initClient();
const albums = await getAllAlbums({}); const albums = await getAllAlbums({}, this.getRequestOptions());
return albums.map((album) => ({ id: album.id, albumName: album.albumName, assetCount: album.assetCount })); return albums.map((album) => ({ id: album.id, albumName: album.albumName, assetCount: album.assetCount }));
} }
protected async testingAsync(_: IntegrationTestingInput): Promise<TestingResult> { protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
this.initClient(); this.initClient();
const user = await getMyUser(); const user = await getMyUser(this.getRequestOptions(input.fetchAsync));
logger.debug(`Logged in as ${user.name} (${user.id})`); logger.debug(`Logged in as ${user.name} (${user.id})`);
return { success: true }; return { success: true };
} }
private getRequestOptions(fetchAsync = fetchWithTrustedCertificatesAsync) {
return {
// Undici and node types are not the same
fetch: fetchAsync as unknown as typeof fetch,
};
}
/** /**
* Sets the credentials and prepares the client for calls. * Sets the credentials and prepares the client for calls.
* Must be called before any other functions. * Must be called before any other functions.
@@ -0,0 +1,6 @@
import type { Station, Timetable, TimetableOptions } from "./timetable-types";
export interface TimetableIntegration {
searchStationsAsync(query: string): Promise<Station[]>;
getTimetableAsync(options: TimetableOptions): Promise<Timetable>;
}
@@ -0,0 +1,33 @@
import z from "zod";
export interface Station {
id: string;
name: string;
}
export const timetableOptionsSchema = z.object({
stationId: z.string(),
limit: z.number().default(10),
});
export type TimetableOptions = z.infer<typeof timetableOptionsSchema>;
export interface TimetableEntry {
timestamp: Date;
delay: number;
line: {
name: string;
color: string | null;
} | null;
location: string;
platform: {
name: string;
hasChanged: boolean;
} | null;
}
export interface Timetable {
stationId: string;
timestamp: Date;
entries: TimetableEntry[];
}
@@ -2,8 +2,9 @@ import type { Agent } from "https";
import dayjs from "dayjs"; import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import ICAL from "ical.js";
import type { RequestInit as NodeFetchRequestInit } from "node-fetch"; import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
import * as ical from "node-ical"; import { tzlib_get_ical_block, tzlib_get_timezones } from "timezones-ical-library";
import { DAVClient } from "tsdav"; import { DAVClient } from "tsdav";
import { z } from "zod"; import { z } from "zod";
@@ -21,6 +22,20 @@ import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
import type { Notification } from "../interfaces/notifications/notification-types"; import type { Notification } from "../interfaces/notifications/notification-types";
import type { INotificationsIntegration } from "../interfaces/notifications/notifications-integration"; import type { INotificationsIntegration } from "../interfaces/notifications/notifications-integration";
// Register all existing timezones
if (ICAL.TimezoneService.count === 0) {
const timezones = tzlib_get_timezones() as string[];
for (const tzid of timezones) {
ICAL.TimezoneService.register(
new ICAL.Timezone({
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-non-null-assertion
component: new ICAL.Component(ICAL.parse((tzlib_get_ical_block(tzid) as string[])[0]!)),
tzid,
}),
);
}
}
const notificationSchema = z.object({ const notificationSchema = z.object({
ocs: z.object({ ocs: z.object({
data: z.array( data: z.array(
@@ -68,42 +83,64 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
return calendarEvents return calendarEvents
.map((event) => { .map((event) => {
// @ts-expect-error the typescript definitions for this package are wrong if (typeof event.data !== "string") {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access return [];
const icalData = ical.default.parseICS(event.data) as ical.CalendarResponse;
const veventObject = Object.values(icalData).find((data) => data.type === "VEVENT");
if (!veventObject) {
throw new Error(`Invalid event data object: ${JSON.stringify(event.data)}. Unable to process the calendar.`);
} }
const comp = ICAL.Component.fromString(event.data);
const veventComponent = comp.getFirstSubcomponent("vevent");
if (!veventComponent) {
return [];
}
const veventObject = new ICAL.Event(veventComponent);
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`); logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
const eventUrlWithoutHost = new URL(event.url).pathname; const eventUrlWithoutHost = new URL(event.url).pathname;
const eventSlug = Buffer.from(eventUrlWithoutHost).toString("base64url"); const eventSlug = Buffer.from(eventUrlWithoutHost).toString("base64url");
const startDates = veventObject.rrule ? veventObject.rrule.between(start, end) : [veventObject.start]; const durationMs = veventObject.duration.toSeconds() * 1000;
const durationMs = veventObject.end.getTime() - veventObject.start.getTime(); // Handle recurring events
let startDates: Date[];
if (veventObject.isRecurring()) {
const iterator = veventObject.iterator();
startDates = [];
// Iterate through occurrences within the time range
let next: ICAL.Time | undefined;
// next actually returns undefined when there are no more occurrences
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while ((next = iterator.next())) {
const nextStartDate = next.convertToZone(ICAL.Timezone.utcTimezone).toJSDate();
if (nextStartDate > end) break;
const nextEndDate = new Date(nextStartDate.getTime() + durationMs);
if (nextEndDate < start) continue;
startDates.push(nextStartDate);
}
} else {
startDates = [veventObject.startDate.convertToZone(ICAL.Timezone.utcTimezone).toJSDate()];
}
return startDates.map((startDate) => { return startDates.map((startDate) => {
const timezoneOffsetMinutes = veventObject.rrule?.origOptions.tzid const endDate = new Date(startDate.getTime() + durationMs);
? dayjs(startDate).tz(veventObject.rrule.origOptions.tzid).utcOffset() const dateInMillis = startDate.valueOf();
: 0;
const utcStartDate = new Date(startDate.getTime() - timezoneOffsetMinutes * 60 * 1000); // Get color property from the component
const endDate = new Date(utcStartDate.getTime() + durationMs); const colorProperty = veventComponent.getFirstProperty("color");
const dateInMillis = utcStartDate.valueOf(); const color = colorProperty?.getFirstValue() as string | undefined;
return { return {
title: veventObject.summary, title: veventObject.summary,
subTitle: null, subTitle: null,
description: veventObject.description, description: veventObject.description || null,
startDate: utcStartDate, startDate,
endDate, endDate,
image: null, image: null,
location: veventObject.location || null, location: veventObject.location || null,
indicatorColor: indicatorColor: color ?? "#ff8600",
"color" in veventObject && typeof veventObject.color === "string" ? veventObject.color : "#ff8600",
links: [ links: [
{ {
href: this.externalUrl( href: this.externalUrl(
@@ -0,0 +1,141 @@
import z from "zod";
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 type { TimetableIntegration } from "../interfaces/timetable/timetable-integration";
import type { Station, Timetable, TimetableOptions } from "../interfaces/timetable/timetable-types";
const supportedStationTypes = ["bus", "tram", "train", "ship", "cablecar", "funicular", "chairlift"];
export class SearchChIntegration extends Integration implements TimetableIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
const response = await input.fetchAsync(this.url("/timetable/api/completion.json", { term: "Zürich" }));
if (!response.ok) {
return TestConnectionError.StatusResult(response);
}
return {
success: true,
};
}
/**
* Searches for stations matching the given query.
* @param query The search query for station names.
* @returns A list of stations matching the search query.
* @docs https://search.ch/fahrplan/api/help
*/
public async searchStationsAsync(query: string): Promise<Station[]> {
const response = await fetchWithTrustedCertificatesAsync(
this.url("/timetable/api/completion.json", {
term: query,
show_ids: 1,
nofavorites: 1,
}),
);
if (!response.ok) {
throw new ResponseError(response);
}
const data = await searchSchema.parseAsync(await response.json());
return (
data
// Remove completions like addresses, we only want stations.
.filter((item) => supportedStationTypes.some((type) => item.iconclass.endsWith(type)))
.map((item) => (item.id !== undefined ? { id: item.id, name: item.label } : null))
.filter((item) => item !== null)
);
}
/**
* Gets the departure timetable for a given station.
* @param options Timetable options including station ID and limit.
* @returns timetable for the given station.
* @docs https://search.ch/fahrplan/api/help
*/
public async getTimetableAsync(options: TimetableOptions): Promise<Timetable> {
const now = new Date();
// The following format is used [01.01.2000, 23:59:00]. The API allows usage of DD.MM.YYYY format and HH:mm format
const [date, time] = now.toLocaleString("en-CH", { timeZone: "Europe/Zurich" }).split(", ") as [string, string];
const response = await fetchWithTrustedCertificatesAsync(
this.url("/timetable/api/stationboard.json", {
stop: options.stationId,
limit: options.limit,
show_delays: 1,
show_tracks: 1,
date,
time: time.substring(0, 5), // Only keep the HH:mm part of the time, as that's what the API expects
}),
);
if (!response.ok) {
throw new ResponseError(response);
}
const data = await timetableSchema.parseAsync(await response.json());
return {
stationId: options.stationId,
timestamp: now,
entries: data.connections.map((connection) => {
// Color is in format A35~F10~ for foreground and background color. Some only have ~~ and therefore no color
const color = connection.color.split("~")[0];
return {
timestamp: connection.time,
line: connection.line
? {
name: connection.line,
color: color && color.length >= 1 ? `#${color}` : null,
}
: null,
location: connection.terminal.name,
delay: connection.dep_delay,
platform: connection.track
? {
// If the track name contains "!", it indicates a change in the track
// so we remove the "!" from the name and set hasChanged to true
name: connection.track.replace("!", ""),
hasChanged: connection.track.includes("!"),
}
: null,
};
}),
};
}
}
const timetableSchema = z.object({
connections: z.array(
z.object({
time: z.string().transform((str) => new Date(str)),
line: z.string().optional(),
color: z.string(),
terminal: z.object({
name: z.string(),
}),
track: z.string().optional(),
dep_delay: z
.string()
.optional()
.transform((str) => (str ? parseInt(str) : 0)),
}),
),
});
const searchSchema = z.array(
z.object({
id: z.string().optional(),
label: z.string(),
iconclass: z.string(), // This is in format sl-icon-type-{type}
}),
);
+1
View File
@@ -12,4 +12,5 @@ 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 "./interfaces/timetable/timetable-types";
export * from "./tracearr/tracearr-types"; export * from "./tracearr/tracearr-types";
@@ -0,0 +1,454 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { StartedTestContainer } from "testcontainers";
import { GenericContainer, getContainerRuntimeClient, ImageName, Wait } from "testcontainers";
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
import { createId } from "@homarr/common";
import { createDb } from "@homarr/db/test";
import { NextcloudIntegration } from "../src";
// Mock the database
vi.mock("@homarr/db", async (importActual) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = await importActual<typeof import("@homarr/db")>();
return {
...actual,
db: createDb(),
};
});
// Mock certificate verification
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = await importActual<typeof import("@homarr/core/infrastructure/certificates")>();
return {
...actual,
getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => {
return Promise.resolve([]);
}),
};
});
const IMAGE_NAME = "nextcloud:latest";
const DEFAULT_USERNAME = "admin";
const DEFAULT_PASSWORD = "admin123";
interface CalendarTestEvent {
summary: string;
description?: string;
location?: string;
color?: string;
dtstart: string;
dtend: string;
rrule?: string;
timezone?: string;
}
describe("Nextcloud integration", () => {
let startedContainer: StartedTestContainer | null = null;
let nextcloudIntegration: NextcloudIntegration | null = null;
beforeAll(async () => {
const containerRuntimeClient = await getContainerRuntimeClient();
await containerRuntimeClient.image.pull(ImageName.fromString(IMAGE_NAME));
startedContainer = await prepareNextcloudContainerAsync();
nextcloudIntegration = createNextcloudIntegration(startedContainer);
}, 100_000);
afterAll(async () => {
if (startedContainer) {
await startedContainer.stop();
startedContainer = null;
}
});
describe("Test connection", () => {
test("Test connection > Should work with valid credentials", async () => {
// Act
const result = await nextcloudIntegration!.testConnectionAsync();
// Assert
expect(result.success).toBe(true);
}, 20_000);
test("Test connection > Should fail with invalid credentials", async () => {
// Arrange
const invalidIntegration = createNextcloudIntegration(startedContainer!, "wrong-user", "wrong-pass");
// Act
const result = await invalidIntegration.testConnectionAsync();
// Assert
expect(result.success).toBe(false);
}, 20_000);
});
describe("Retrieve calendar events", () => {
test("Retrieve calendar events > Should retrieve all fields correctly", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Test Meeting",
description: "This is a test meeting",
location: "Conference Room A",
color: "#FF5733",
dtstart: "20260415T140000Z",
dtend: "20260415T150000Z",
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
const retrievedEvent = relevantEvents[0];
expect(retrievedEvent).toMatchObject({
title: testEvent.summary,
description: testEvent.description,
location: testEvent.location,
indicatorColor: testEvent.color,
});
expect(retrievedEvent!.startDate.toISOString()).toBe("2026-04-15T14:00:00.000Z");
expect(retrievedEvent!.endDate?.toISOString()).toBe("2026-04-15T15:00:00.000Z");
}, 20_000);
test("Retrieve calendar events > Should work with only required fields", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Simple Event",
dtstart: "20260415T140000Z",
dtend: "20260415T150000Z",
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
const retrievedEvent = relevantEvents[0];
expect(retrievedEvent?.location).toBeNull();
expect(retrievedEvent?.description).toBeNull();
expect(retrievedEvent?.indicatorColor).toBe("#ff8600"); // Default color
}, 20_000);
test("Retrieve calendar events > Should work with timezone specified", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Timezone Event",
dtstart: "20260415T140000",
dtend: "20260415T150000",
timezone: "Asia/Tokyo",
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
const retrievedEvent = relevantEvents[0];
expect(retrievedEvent!.startDate.toISOString()).toBe("2026-04-15T05:00:00.000Z"); // 14:00 in Tokyo is 05:00 UTC
expect(retrievedEvent!.endDate?.toISOString()).toBe("2026-04-15T06:00:00.000Z"); // 15:00 in Tokyo is 06:00 UTC
}, 120_000);
test("Retrieve calendar events > Should work with event that starts before the range but ends within the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Early Meeting",
dtstart: "20260401T230000Z", // Starts before the range
dtend: "20260402T010000Z", // Ends within the range
};
const start = new Date("2026-04-02T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
}, 20_000);
test("Retrieve calendar events > Should work with event that starts within the range but ends after the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Late Meeting",
dtstart: "20260430T230000Z", // Starts within the range
dtend: "20260501T010000Z", // Ends after the range
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
}, 20_000);
test("Retrieve calendar events > Should not retrieve events outside the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Out of Range Meeting",
dtstart: "20260301T140000Z", // Starts before the range
dtend: "20260301T150000Z", // Ends before the range
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(0);
}, 20_000);
test("Retrieve calendar events > Should work with events that begin before the range and end after the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Spanning Meeting",
dtstart: "20260330T230000Z", // Starts before the range
dtend: "20260501T010000Z", // Ends after the range
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(1);
}, 20_000);
test("Retrieve calendar events > Should work with recurring events first occurrence before the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Recurring Meeting",
dtstart: "20260330T140000Z", // First occurrence starts before the range
dtend: "20260330T150000Z",
rrule: "FREQ=DAILY;COUNT=5", // Daily for 5 occurrences
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(3);
}, 20_000);
test("Retrieve calendar events > Should work with recurring events that go beyond the range", async () => {
// Arrange
const calendarName = createId();
const testEvent: CalendarTestEvent = {
summary: "Working Week",
dtstart: "20260302T090000Z",
dtend: "20260306T170000Z",
rrule: "FREQ=WEEKLY;COUNT=10",
};
const start = new Date("2026-04-01T00:00:00Z");
const end = new Date("2026-04-30T23:59:59Z");
await createCalendarAsync(startedContainer!, calendarName);
await createCalendarEventAsync(startedContainer!, calendarName, testEvent);
// Act
const events = await nextcloudIntegration!.getCalendarEventsAsync(start, end);
// Assert
const relevantEvents = events.filter((event) => event.title === testEvent.summary);
expect(relevantEvents).toHaveLength(5);
});
});
});
/**
* Prepares a Nextcloud container with proper configuration
*/
async function prepareNextcloudContainerAsync(): Promise<StartedTestContainer> {
const container = createNextcloudContainer();
const startedContainer = await container.start();
return startedContainer;
}
/**
* Creates a Nextcloud container with proper configuration
*/
function createNextcloudContainer() {
return new GenericContainer(IMAGE_NAME)
.withEnvironment({
NEXTCLOUD_ADMIN_USER: DEFAULT_USERNAME,
NEXTCLOUD_ADMIN_PASSWORD: DEFAULT_PASSWORD,
NEXTCLOUD_TRUSTED_DOMAINS: "*",
SQLITE_DATABASE: "nextcloud", // Use SQLite for faster startup
})
.withExposedPorts(80)
.withWaitStrategy(Wait.forHttp("/status.php", 80).forStatusCode(200))
.withStartupTimeout(120_000);
}
/**
* Creates a Nextcloud integration instance
*/
function createNextcloudIntegration(
container: StartedTestContainer,
username?: string,
password?: string,
): NextcloudIntegration {
return new NextcloudIntegration({
id: "1",
decryptedSecrets: [
{
kind: "username",
value: username ?? DEFAULT_USERNAME,
},
{
kind: "password",
value: password ?? DEFAULT_PASSWORD,
},
],
name: "Nextcloud",
url: `http://${container.getHost()}:${container.getMappedPort(80)}`,
externalUrl: null,
});
}
/**
* Creates a calendar in Nextcloud via OCS API
*/
async function createCalendarAsync(container: StartedTestContainer, calendarName: string): Promise<void> {
const host = container.getHost();
const port = container.getMappedPort(80);
const url = `http://${host}:${port}/remote.php/dav/calendars/${DEFAULT_USERNAME}/${calendarName}`;
const auth = Buffer.from(`${DEFAULT_USERNAME}:${DEFAULT_PASSWORD}`).toString("base64");
const response = await fetch(url, {
method: "MKCALENDAR",
headers: {
Authorization: `Basic ${auth}`,
"Content-Type": "application/xml",
},
body: `<?xml version="1.0" encoding="UTF-8"?>
<c:mkcalendar xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:">
<d:set>
<d:prop>
<d:displayname>${calendarName}</d:displayname>
</d:prop>
</d:set>
</c:mkcalendar>`,
});
if (!response.ok && response.status !== 201) {
throw new Error(`Failed to create calendar: ${response.status} ${response.statusText}`);
}
}
/**
* Creates a calendar event in Nextcloud via CalDAV
*/
async function createCalendarEventAsync(
container: StartedTestContainer,
calendarName: string,
event: CalendarTestEvent,
): Promise<void> {
const host = container.getHost();
const port = container.getMappedPort(80);
const id = createId();
const url = `http://${host}:${port}/remote.php/dav/calendars/${DEFAULT_USERNAME}/${calendarName}/${id}.ics`;
const auth = Buffer.from(`${DEFAULT_USERNAME}:${DEFAULT_PASSWORD}`).toString("base64");
// Build iCalendar format
const icalData = buildICalEvent(id, event);
const response = await fetch(url, {
method: "PUT",
headers: {
Authorization: `Basic ${auth}`,
"Content-Type": "text/calendar",
},
body: icalData,
});
if (!response.ok && response.status !== 201) {
throw new Error(`Failed to create event: ${response.status} ${response.statusText}`);
}
}
/**
* Builds an iCalendar format string from event data
*/
function buildICalEvent(uid: string, event: CalendarTestEvent): string {
const lines = [
"BEGIN:VCALENDAR",
"VERSION:2.0",
"PRODID:-//Homarr Tests//EN",
"BEGIN:VEVENT",
`UID:${uid}`,
`DTSTAMP:${new Date().toISOString().replace(/[-:]/g, "").split(".")[0]}Z`,
`DTSTART${event.timezone ? `;TZID=${event.timezone}` : ""}:${event.dtstart}`,
`DTEND${event.timezone ? `;TZID=${event.timezone}` : ""}:${event.dtend}`,
`SUMMARY:${event.summary}`,
];
if (event.description) {
lines.push(`DESCRIPTION:${event.description}`);
}
if (event.location) {
lines.push(`LOCATION:${event.location}`);
}
if (event.color) {
lines.push(`COLOR:${event.color}`);
}
if (event.rrule) {
lines.push(`RRULE:${event.rrule}`);
}
lines.push("END:VEVENT", "END:VCALENDAR");
return lines.join("\r\n");
}
+2 -2
View File
@@ -34,12 +34,12 @@
"@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": "^8.3.18",
"@tabler/icons-react": "^3.40.0", "@tabler/icons-react": "^3.41.0",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"next": "16.2.1", "next": "16.2.1",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -25,7 +25,7 @@
"dependencies": { "dependencies": {
"@homarr/ui": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0",
"@mantine/notifications": "^8.3.18", "@mantine/notifications": "^8.3.18",
"@tabler/icons-react": "^3.40.0" "@tabler/icons-react": "^3.41.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -44,7 +44,7 @@
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"superjson": "2.2.6", "superjson": "2.2.6",
"zod": "^4.2.1", "zod": "^4.3.6",
"zod-form-data": "^3.0.1" "zod-form-data": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
+1 -1
View File
@@ -23,7 +23,7 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -33,8 +33,8 @@
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"octokit": "^5.0.5", "octokit": "^5.0.5",
"superjson": "2.2.6", "superjson": "2.2.6",
"undici": "7.24.5", "undici": "7.24.6",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+36
View File
@@ -0,0 +1,36 @@
import dayjs from "dayjs";
import type { IntegrationKindByCategory } from "@homarr/definitions";
import { createIntegrationAsync } from "@homarr/integrations";
import type { Station, Timetable } from "@homarr/integrations/types";
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
export const timetableSearchStationsRequestHandler = createCachedIntegrationRequestHandler<
Station[],
IntegrationKindByCategory<"timetable">,
{ query: string }
>({
async requestAsync(integration, input) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.searchStationsAsync(input.query);
},
queryKey: "timetableSearchStations",
cacheDuration: dayjs.duration(1, "day"),
});
export const timetableGetTimetableRequestHandler = createCachedIntegrationRequestHandler<
Timetable,
IntegrationKindByCategory<"timetable">,
{ stationId: string; limit: number }
>({
async requestAsync(integration, input) {
const integrationInstance = await createIntegrationAsync(integration);
return await integrationInstance.getTimetableAsync({
stationId: input.stationId,
limit: input.limit,
});
},
queryKey: "timetableGetTimetable",
cacheDuration: dayjs.duration(1, "minute"),
});
+2 -2
View File
@@ -36,8 +36,8 @@
"@mantine/core": "^8.3.18", "@mantine/core": "^8.3.18",
"@mantine/hooks": "^8.3.18", "@mantine/hooks": "^8.3.18",
"@mantine/spotlight": "^8.3.18", "@mantine/spotlight": "^8.3.18",
"@tabler/icons-react": "^3.40.0", "@tabler/icons-react": "^3.41.0",
"jotai": "^2.18.1", "jotai": "^2.19.0",
"next": "16.2.1", "next": "16.2.1",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "" "label": ""
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "允许地理位置" "label": "允许地理位置"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "显示内存信息" "label": "显示内存信息"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "显示运行时间" "label": "显示运行时间"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "内存", "memory": "内存",
"gpu": "",
"network": "网络" "network": "网络"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "内存", "memory": "内存",
"gpu": "",
"network": "网络", "network": "网络",
"up": "上传", "up": "上传",
"down": "下载" "down": "下载"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "crwdns970:0crwdne970:0" "label": "crwdns970:0crwdne970:0"
},
"allowModals": {
"label": "crwdns3992:0crwdne3992:0"
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "crwdns1080:0crwdne1080:0" "label": "crwdns1080:0crwdne1080:0"
}, },
"gpu": {
"label": "crwdns3994:0crwdne3994:0"
},
"showUptime": { "showUptime": {
"label": "crwdns3446:0crwdne3446:0" "label": "crwdns3446:0crwdne3446:0"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "crwdns3700:0crwdne3700:0", "cpu": "crwdns3700:0crwdne3700:0",
"memory": "crwdns3702:0crwdne3702:0", "memory": "crwdns3702:0crwdne3702:0",
"gpu": "crwdns3996:0crwdne3996:0",
"network": "crwdns3704:0crwdne3704:0" "network": "crwdns3704:0crwdne3704:0"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "crwdns3604:0crwdne3604:0", "cpu": "crwdns3604:0crwdne3604:0",
"memory": "crwdns3606:0crwdne3606:0", "memory": "crwdns3606:0crwdne3606:0",
"gpu": "crwdns3998:0crwdne3998:0",
"network": "crwdns3608:0crwdne3608:0", "network": "crwdns3608:0crwdne3608:0",
"up": "crwdns3610:0crwdne3610:0", "up": "crwdns3610:0crwdne3610:0",
"down": "crwdns3612:0crwdne3612:0" "down": "crwdns3612:0crwdne3612:0"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Povolit polohu" "label": "Povolit polohu"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Zobrazit informace o paměti" "label": "Zobrazit informace o paměti"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Tillad geolokalisering" "label": "Tillad geolokalisering"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Vis hukommelsesoplysninger" "label": "Vis hukommelsesoplysninger"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Vis Oppetid" "label": "Vis Oppetid"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Hukommelse", "memory": "Hukommelse",
"gpu": "",
"network": "Netværk" "network": "Netværk"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "",
"network": "NET", "network": "NET",
"up": "OP", "up": "OP",
"down": "NED" "down": "NED"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Geolokalisierung zulassen" "label": "Geolokalisierung zulassen"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Speicher-Info anzeigen" "label": "Speicher-Info anzeigen"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Geolokalisierung zulassen" "label": "Geolokalisierung zulassen"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Speicher-Info anzeigen" "label": "Speicher-Info anzeigen"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Laufzeit anzeigen" "label": "Laufzeit anzeigen"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Speicher", "memory": "Speicher",
"gpu": "",
"network": "Netzwerk" "network": "Netzwerk"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Arbeitsspeicher", "memory": "Arbeitsspeicher",
"gpu": "",
"network": "Netzwerk", "network": "Netzwerk",
"up": "OBEN", "up": "OBEN",
"down": "UNTEN" "down": "UNTEN"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Επιτρέπεται ο γεωεντοπισμός" "label": "Επιτρέπεται ο γεωεντοπισμός"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Εμφάνιση Πληροφοριών Μνήμης" "label": "Εμφάνιση Πληροφοριών Μνήμης"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+128 -120
View File
@@ -1125,17 +1125,17 @@
"userAvatar": { "userAvatar": {
"menu": { "menu": {
"switchToDarkMode": "Switch to dark mode", "switchToDarkMode": "Switch to dark mode",
"switchToLightMode": "Switch to light mode", "switchToLightMode": "التبديل إلى الوضع الفاتح",
"management": "Management", "management": "الإدارة",
"preferences": "Your preferences", "preferences": "تفضيلاتك",
"logout": "Log-out", "logout": "تسجيل الخروج",
"login": "Log-in", "login": "Log-in",
"homeBoard": "Your home board", "homeBoard": "لوحة منزلك",
"loggedOut": "Logged out", "loggedOut": "Logged out",
"updateAvailable": "New update available: {tag}" "updateAvailable": "{countUpdates} يتوفر تحديث جديد: {tag}"
} }
}, },
"dangerZone": "Danger zone", "dangerZone": "المنطقة الخطرة",
"noResults": "No results have been found", "noResults": "No results have been found",
"unsavedChanges": "You have unsaved changes!", "unsavedChanges": "You have unsaved changes!",
"servers": "Servers", "servers": "Servers",
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Allow geolocation" "label": "Allow geolocation"
},
"allowModals": {
"label": "Allow modals"
} }
}, },
"error": { "error": {
@@ -1856,107 +1859,110 @@
"label": "Show CPU Info" "label": "Show CPU Info"
}, },
"memory": { "memory": {
"label": "" "label": "Show Memory Info"
},
"gpu": {
"label": "Show GPU Info"
}, },
"showUptime": { "showUptime": {
"label": "" "label": "Show Uptime"
}, },
"fileSystem": { "fileSystem": {
"label": "" "label": "Show Filesystem Info"
}, },
"defaultTab": { "defaultTab": {
"label": "" "label": "Default tab"
}, },
"visibleClusterSections": { "visibleClusterSections": {
"label": "" "label": "Visible cluster sections"
}, },
"sectionIndicatorRequirement": { "sectionIndicatorRequirement": {
"label": "" "label": "Section indicator requirement"
} }
}, },
"popover": { "popover": {
"information": "", "information": "Information",
"processor": "", "processor": "Processor: {cpuModelName}",
"memory": "", "memory": "Memory: {memory}GiB",
"memoryAvailable": "", "memoryAvailable": "Available: {memoryAvailable}GiB ({percent}%)",
"version": "", "version": "Version: {version}",
"uptime": "", "uptime": "Uptime: {months} Months, {days} Days, {hours} Hours, {minutes} Minutes",
"loadAverage": "", "loadAverage": "Load average:",
"minute": "", "minute": "1 minute",
"minutes": "", "minutes": "{count} minutes",
"used": "", "used": "Used",
"available": "", "available": "Available",
"lastSeen": "" "lastSeen": "Last status update: {lastSeen}"
}, },
"memory": {}, "memory": {},
"error": { "error": {
"internalServerError": "" "internalServerError": "Failed to fetch health status"
}, },
"cluster": { "cluster": {
"summary": { "summary": {
"cpu": "", "cpu": "CPU",
"memory": "" "memory": "RAM"
}, },
"resource": { "resource": {
"node": { "node": {
"name": "" "name": "Nodes"
}, },
"qemu": { "qemu": {
"name": "" "name": "VMs"
}, },
"lxc": { "lxc": {
"name": "" "name": "LXCs"
}, },
"storage": { "storage": {
"name": "" "name": "Storage"
} }
}, },
"popover": { "popover": {
"rightSection": { "rightSection": {
"node": "", "node": "Node",
"vmId": "", "vmId": "VM ID",
"plugin": "" "plugin": "Plugin"
}, },
"detail": { "detail": {
"cpu": "", "cpu": "Cores",
"memory": "", "memory": "Memory",
"storage": "", "storage": "Storage",
"uptime": "", "uptime": "Uptime",
"haState": "", "haState": "HA State",
"storageType": { "storageType": {
"local": "", "local": "Local storage",
"shared": "" "shared": "Shared storage"
} }
} }
}, },
"table": { "table": {
"header": { "header": {
"name": "", "name": "Name",
"cpu": "", "cpu": "CPU",
"memory": "", "memory": "RAM",
"node": "" "node": "Node"
} }
} }
} }
}, },
"dockerContainers": { "dockerContainers": {
"name": "", "name": "Docker stats",
"description": "", "description": "Stats of your containers (This widget can only be added with administrator privileges)",
"option": { "option": {
"enableRowSorting": { "enableRowSorting": {
"label": "" "label": "Enable items sorting"
}, },
"defaultSort": { "defaultSort": {
"label": "", "label": "Column used for sorting by default",
"option": { "option": {
"name": "", "name": "Name",
"state": "", "state": "State",
"cpuUsage": "", "cpuUsage": "CPU usage",
"memoryUsage": "" "memoryUsage": "Memory usage"
} }
}, },
"descendingDefaultSort": { "descendingDefaultSort": {
"label": "" "label": "Invert sorting"
} }
}, },
"error": { "error": {
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
@@ -3149,98 +3157,98 @@
}, },
"navbar": { "navbar": {
"items": { "items": {
"home": "", "home": "الصفحة الرئسية",
"boards": "", "boards": "الألواح",
"apps": "", "apps": "التطبيقات",
"integrations": "", "integrations": "التكاملات",
"searchEngies": "", "searchEngies": "محركات البحث",
"medias": "", "medias": "الوسائط",
"users": { "users": {
"label": "", "label": "المستخدمين",
"items": { "items": {
"manage": "", "manage": "إدارة المستخدمين",
"invites": "", "invites": "الدعوات",
"groups": "" "groups": "المجموعات"
} }
}, },
"tools": { "tools": {
"label": "", "label": "الأدوات",
"items": { "items": {
"docker": "", "docker": "الحاويات",
"kubernetes": "", "kubernetes": "",
"logs": "", "logs": "السجلات",
"api": "", "api": "API",
"certificates": "", "certificates": "الشهادات",
"tasks": "" "tasks": "المهام"
} }
}, },
"settings": "", "settings": "الإعدادات",
"help": { "help": {
"label": "", "label": "المساعدة",
"items": { "items": {
"documentation": "", "documentation": "المستندات",
"submitIssue": "", "submitIssue": "إبلاغ عن مشكلة",
"discord": "", "discord": "مجتمع ديسكورد",
"sourceCode": "" "sourceCode": "كود المصدر"
} }
}, },
"about": "" "about": "حول"
} }
}, },
"page": { "page": {
"home": { "home": {
"statistic": { "statistic": {
"board": "", "board": "الألواح",
"user": "", "user": "مستخدمين",
"invite": "", "invite": "دعوات",
"integration": "", "integration": "التكاملات",
"app": "", "app": "البرامج",
"group": "", "group": "المجموعات",
"searchEngine": "", "searchEngine": "محركات البحث",
"media": "" "media": "الوسائط"
}, },
"statisticLabel": { "statisticLabel": {
"boards": "", "boards": "اللوحات",
"resources": "", "resources": "الموارد",
"authentication": "", "authentication": "المصادقة",
"authorization": "" "authorization": "الأذونات"
}, },
"heroBanner": { "heroBanner": {
"title": "", "title": "مرحبًا بعودتك إلى",
"subtitle": "" "subtitle": "{app} اللوحات"
} }
}, },
"board": { "board": {
"title": "", "title": "لوحاتك",
"action": { "action": {
"new": { "new": {
"label": "" "label": "لوحة جديدة"
}, },
"open": { "open": {
"label": "" "label": "فتح اللوحة"
}, },
"settings": { "settings": {
"label": "" "label": "الإعدادات"
}, },
"setHomeBoard": { "setHomeBoard": {
"label": "", "label": "تعيينه كلوحة رئيسية",
"badge": { "badge": {
"label": "", "label": "الرئيسية",
"tooltip": "" "tooltip": ""
} }
}, },
"setMobileHomeBoard": { "setMobileHomeBoard": {
"label": "", "label": "تعيينه كصفحتك الرئيسية للجوال",
"badge": { "badge": {
"label": "", "label": "الجوال",
"tooltip": "" "tooltip": ""
} }
}, },
"duplicate": { "duplicate": {
"label": "" "label": "تكرار اللوحة"
}, },
"delete": { "delete": {
"label": "", "label": "حذف نهائيًا",
"confirm": { "confirm": {
"title": "", "title": "",
"description": "" "description": ""
@@ -3329,10 +3337,10 @@
} }
}, },
"invite": { "invite": {
"title": "", "title": "إدارة دعوات المستخدمين",
"action": { "action": {
"new": { "new": {
"title": "", "title": "دعوة جديدة",
"description": "" "description": ""
}, },
"copy": { "copy": {
@@ -3348,13 +3356,13 @@
}, },
"field": { "field": {
"id": { "id": {
"label": "" "label": "المعرّف"
}, },
"creator": { "creator": {
"label": "" "label": "المنشئ"
}, },
"expirationDate": { "expirationDate": {
"label": "" "label": "انتهاء الصلاحية"
}, },
"token": { "token": {
"label": "" "label": ""
@@ -4121,7 +4129,7 @@
"manage": { "manage": {
"label": "", "label": "",
"boards": { "boards": {
"label": "" "label": "الألواح"
}, },
"integrations": { "integrations": {
"label": "", "label": "",
@@ -4154,7 +4162,7 @@
} }
}, },
"users": { "users": {
"label": "", "label": "المستخدمين",
"create": { "create": {
"label": "" "label": ""
}, },
@@ -4165,7 +4173,7 @@
"label": "" "label": ""
}, },
"invites": { "invites": {
"label": "" "label": "الدعوات"
} }
}, },
"tools": { "tools": {
@@ -4222,7 +4230,7 @@
} }
}, },
"search": { "search": {
"placeholder": "", "placeholder": "البحث عن",
"nothingFound": "", "nothingFound": "",
"error": { "error": {
"fetch": "" "fetch": ""
@@ -4382,13 +4390,13 @@
"title": "", "title": "",
"option": { "option": {
"documentation": { "documentation": {
"label": "" "label": "Documentation"
}, },
"submitIssue": { "submitIssue": {
"label": "" "label": "Submit an issue"
}, },
"discord": { "discord": {
"label": "" "label": "Community Discord"
} }
} }
} }
@@ -4397,20 +4405,20 @@
"home": { "home": {
"group": { "group": {
"search": { "search": {
"title": "", "title": "Search",
"option": { "option": {
"other": { "other": {
"label": "" "label": "Search with another search engine"
}, },
"no-default": { "no-default": {
"label": "", "label": "No default search engine",
"description": "" "description": "Set a default search engine in preferences"
}, },
"search": { "search": {
"label": "" "label": "Search for \"{query}\" with {name}"
}, },
"from-integration": { "from-integration": {
"description": "" "description": "Start typing to search"
} }
} }
}, },
+14
View File
@@ -1525,6 +1525,17 @@
"offline": "Offline" "offline": "Offline"
} }
}, },
"timetable": {
"name": "Timetable",
"description": "Displays departure times for a station",
"title": "Departures {station}",
"noStation": "No station selected",
"option": {
"station": {
"label": "Station"
}
}
},
"notebook": { "notebook": {
"name": "Notebook", "name": "Notebook",
"description": "A simple notebook widget that supports markdown", "description": "A simple notebook widget that supports markdown",
@@ -3513,6 +3524,9 @@
"minecraftServerStatus": { "minecraftServerStatus": {
"label": "Minecraft server status" "label": "Minecraft server status"
}, },
"timetable": {
"label": "Timetable"
},
"iconsUpdater": { "iconsUpdater": {
"label": "Icons Updater" "label": "Icons Updater"
}, },
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Permitir geolocalización" "label": "Permitir geolocalización"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Mostrar información de la memoria" "label": "Mostrar información de la memoria"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Mostrar tiempo de actividad" "label": "Mostrar tiempo de actividad"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Memoria", "memory": "Memoria",
"gpu": "",
"network": "Red" "network": "Red"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "",
"network": "RED", "network": "RED",
"up": "ENCENDIDO", "up": "ENCENDIDO",
"down": "APAGADO" "down": "APAGADO"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "" "label": ""
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+36 -28
View File
@@ -114,7 +114,7 @@
}, },
"invite": { "invite": {
"title": "Rejoignez Homarr", "title": "Rejoignez Homarr",
"subtitle": "Bienvenue sur Homarr! Veuillez créer votre compte", "subtitle": "Bienvenue sur Homarr ! Veuillez créer votre compte",
"description": "Vous avez été invité par {username}" "description": "Vous avez été invité par {username}"
}, },
"init": { "init": {
@@ -177,7 +177,7 @@
} }
}, },
"forgotPassword": { "forgotPassword": {
"label": "Mot de passe oublié?", "label": "Mot de passe oublié ?",
"description": "Un administrateur peut utiliser la commande suivante pour réinitialiser votre mot de passe :" "description": "Un administrateur peut utiliser la commande suivante pour réinitialiser votre mot de passe :"
} }
}, },
@@ -578,7 +578,7 @@
}, },
"delete": { "delete": {
"title": "Supprimer l'app", "title": "Supprimer l'app",
"message": "Êtes-vous sûr de vouloir supprimer l'app {name}?", "message": "Êtes-vous sûr de vouloir supprimer l'app {name} ?",
"notification": { "notification": {
"success": { "success": {
"title": "Suppression effectuée avec succès", "title": "Suppression effectuée avec succès",
@@ -684,7 +684,7 @@
}, },
"delete": { "delete": {
"title": "Supprimer l'intégration", "title": "Supprimer l'intégration",
"message": "Êtes-vous sûr(e) de vouloir supprimer l'intégration {name}?", "message": "Êtes-vous sûr(e) de vouloir supprimer l'intégration {name} ?",
"notification": { "notification": {
"success": { "success": {
"title": "Suppression réussie", "title": "Suppression réussie",
@@ -742,7 +742,7 @@
"description": "La réponse n'a pas pu être analysée. Veuillez vérifier que l'URL pointe vers l'URL de base du service." "description": "La réponse n'a pas pu être analysée. Veuillez vérifier que l'URL pointe vers l'URL de base du service."
}, },
"authorization": { "authorization": {
"title": "", "title": "Erreur d'autorisation",
"description": "La requête n'a pas été autorisée. Veuillez vérifier que les informations d'identification sont correctes et que vous les avez configurées avec suffisamment d'autorisations." "description": "La requête n'a pas été autorisée. Veuillez vérifier que les informations d'identification sont correctes et que vous les avez configurées avec suffisamment d'autorisations."
}, },
"statusCode": { "statusCode": {
@@ -794,7 +794,7 @@
"hostnameMismatch": { "hostnameMismatch": {
"confirm": { "confirm": {
"title": "Faire confiant à l'incohérence du nom d'hôte", "title": "Faire confiant à l'incohérence du nom d'hôte",
"message": "" "message": "Êtes-vous sûr de vouloir faire confiance ce certificat avec un nom d'hôte incohérent ?"
}, },
"notification": { "notification": {
"success": { "success": {
@@ -1164,8 +1164,8 @@
"number": "Ce champ doit être inférieur ou égal à {maximum}" "number": "Ce champ doit être inférieur ou égal à {maximum}"
}, },
"custom": { "custom": {
"passwordsDoNotMatch": "Les mots de passe ne correspondent pas.", "passwordsDoNotMatch": "Les mots de passe ne correspondent pas",
"passwordRequirements": "Le mot de passe ne respecte pas les exigences requises.", "passwordRequirements": "Le mot de passe ne respecte pas les exigences requises",
"boardAlreadyExists": "Un tableau avec ce nom existe déjà", "boardAlreadyExists": "Un tableau avec ce nom existe déjà",
"invalidFileType": "Type de fichier invalide, {expected} attendu", "invalidFileType": "Type de fichier invalide, {expected} attendu",
"invalidFileName": "Nom de fichier invalide", "invalidFileName": "Nom de fichier invalide",
@@ -1248,7 +1248,7 @@
"action": { "action": {
"create": "Nouvel élément", "create": "Nouvel élément",
"import": "Importer un élément", "import": "Importer un élément",
"edit": "Editer l'élément", "edit": "Éditer l'élément",
"moveResize": "Déplacer / redimensionner l'élément", "moveResize": "Déplacer / redimensionner l'élément",
"duplicate": "Dupliquer l'élément", "duplicate": "Dupliquer l'élément",
"remove": "Supprimer l'élément" "remove": "Supprimer l'élément"
@@ -1281,7 +1281,7 @@
} }
}, },
"edit": { "edit": {
"title": "Editer l'élément", "title": "Éditer l'élément",
"advancedOptions": { "advancedOptions": {
"label": "Options avancées", "label": "Options avancées",
"title": "Options avancées de l'élément" "title": "Options avancées de l'élément"
@@ -1309,7 +1309,7 @@
"widget": { "widget": {
"app": { "app": {
"name": "App", "name": "App",
"description": "Intégre une application dans le tableau.", "description": "Intègre une application dans le tableau.",
"option": { "option": {
"appId": { "appId": {
"label": "Choisir une application" "label": "Choisir une application"
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Autoriser la géolocalisation" "label": "Autoriser la géolocalisation"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1634,7 +1637,7 @@
} }
}, },
"smartHome-entityState": { "smartHome-entityState": {
"name": "Etat de l'entité", "name": "État de l'entité",
"description": "Affichez l'état d'une entité et optionnellement activez la", "description": "Affichez l'état d'une entité et optionnellement activez la",
"option": { "option": {
"entityId": { "entityId": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Afficher les infos de la mémoire" "label": "Afficher les infos de la mémoire"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Afficher le temps de disponibilité" "label": "Afficher le temps de disponibilité"
}, },
@@ -1877,16 +1883,16 @@
"popover": { "popover": {
"information": "Information", "information": "Information",
"processor": "Processeur : {cpuModelName}", "processor": "Processeur : {cpuModelName}",
"memory": "Mémoire: {memory}GiB", "memory": "Mémoire : {memory}GiB",
"memoryAvailable": "Disponible: {memoryAvailable}GiB ({percent}%)", "memoryAvailable": "Disponible : {memoryAvailable}GiB ({percent}%)",
"version": "Version : {version}", "version": "Version : {version}",
"uptime": "Disponibilité: {months} Mois, {days} Jours, {hours} Heures, {minutes} Minutes", "uptime": "Disponibilité : {months} Mois, {days} Jours, {hours} Heures, {minutes} Minutes",
"loadAverage": "Charge moyenne:", "loadAverage": "Charge moyenne :",
"minute": "1 minute", "minute": "1 minute",
"minutes": "{count} minutes", "minutes": "{count} minutes",
"used": "Utilisé ", "used": "Utilisé ",
"available": "Disponible", "available": "Disponible",
"lastSeen": "Dernière mise à jour de statut: {lastSeen}" "lastSeen": "Dernière mise à jour de statut : {lastSeen}"
}, },
"memory": {}, "memory": {},
"error": { "error": {
@@ -1922,7 +1928,7 @@
"memory": "Mémoire", "memory": "Mémoire",
"storage": "Stockage", "storage": "Stockage",
"uptime": "Disponibilité", "uptime": "Disponibilité",
"haState": "Etat HA", "haState": "État HA",
"storageType": { "storageType": {
"local": "Stockage local", "local": "Stockage local",
"shared": "Stockage partagé" "shared": "Stockage partagé"
@@ -2056,7 +2062,7 @@
"query": "Ville / Code Postal", "query": "Ville / Code Postal",
"latitude": "Latitude", "latitude": "Latitude",
"longitude": "Longitude", "longitude": "Longitude",
"disabledTooltip": "Veuillez saisir une ville ou un code postal.", "disabledTooltip": "Veuillez saisir une ville ou un code postal",
"unknownLocation": "Lieu inconnu", "unknownLocation": "Lieu inconnu",
"search": "Rechercher", "search": "Rechercher",
"table": { "table": {
@@ -2075,7 +2081,7 @@
} }
}, },
"integration": { "integration": {
"noData": "Aucune intégration trouvée.", "noData": "Aucune intégration trouvée",
"description": "Cliquez sur <here></here> pour créer une nouvelle intégration" "description": "Cliquez sur <here></here> pour créer une nouvelle intégration"
}, },
"app": { "app": {
@@ -2091,7 +2097,7 @@
}, },
"video": { "video": {
"name": "Flux vidéo", "name": "Flux vidéo",
"description": "Intégre un flux vidéo ou une vidéo provenant d'une caméra ou d'un site web", "description": "Intègre un flux vidéo ou une vidéo provenant d'une caméra ou d'un site web",
"option": { "option": {
"feedUrl": { "feedUrl": {
"label": "URL du flux" "label": "URL du flux"
@@ -2104,7 +2110,7 @@
"label": "Muet" "label": "Muet"
}, },
"hasControls": { "hasControls": {
"label": "Afficher les controles" "label": "Afficher les contrôles"
} }
}, },
"error": { "error": {
@@ -2330,7 +2336,7 @@
"pause": "Mettre en pause l'élément", "pause": "Mettre en pause l'élément",
"resume": "Reprendre l'élément", "resume": "Reprendre l'élément",
"delete": { "delete": {
"title": "Supprimer l'élement", "title": "Supprimer l'élément",
"modalTitle": "Êtes-vous sûr de vouloir supprimer cette tâche ?", "modalTitle": "Êtes-vous sûr de vouloir supprimer cette tâche ?",
"entry": "Supprimer l'entrée", "entry": "Supprimer l'entrée",
"entryAndFiles": "Supprimer l'entrée et le(s) fichier(s)" "entryAndFiles": "Supprimer l'entrée et le(s) fichier(s)"
@@ -2542,7 +2548,7 @@
"allImagesAlreadyImported": "Toutes les images déjà importées", "allImagesAlreadyImported": "Toutes les images déjà importées",
"onlyAdminCanImport": "Seuls les administrateurs peuvent importer depuis docker", "onlyAdminCanImport": "Seuls les administrateurs peuvent importer depuis docker",
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
"deselectAll": "Tout déselectionner" "deselectAll": "Tout désélectionner"
}, },
"provider": { "provider": {
"label": "Fournisseur" "label": "Fournisseur"
@@ -2667,7 +2673,7 @@
}, },
"error": { "error": {
"integrationsDisconnected": "Aucune donnée disponible, toutes les intégrations sont déconnectées", "integrationsDisconnected": "Aucune donnée disponible, toutes les intégrations sont déconnectées",
"unknownContentOption": "Option de contenu inconnue pour le widget d'état du réseau: " "unknownContentOption": "Option de contenu inconnue pour le widget d'état du réseau : "
}, },
"name": "État du réseau", "name": "État du réseau",
"description": "Afficher les périphériques connectés sur un réseau" "description": "Afficher les périphériques connectés sur un réseau"
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Mémoire", "memory": "Mémoire",
"gpu": "",
"network": "Réseau" "network": "Réseau"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "RAM", "memory": "RAM",
"gpu": "",
"network": "NET", "network": "NET",
"up": "UP", "up": "UP",
"down": "HORS SERVICE" "down": "HORS SERVICE"
@@ -2757,7 +2765,7 @@
"label": "Mode d'affichage", "label": "Mode d'affichage",
"option": { "option": {
"percentage": { "percentage": {
"label": "Pourcentage (ex : 70%)" "label": "Pourcentage (ex : 76%)"
}, },
"absolute": { "absolute": {
"label": "Valeurs absolues (ex. : 800 Go / 1 To)" "label": "Valeurs absolues (ex. : 800 Go / 1 To)"
@@ -2965,7 +2973,7 @@
}, },
"customCss": { "customCss": {
"label": "CSS personnalisé pour ce tableau", "label": "CSS personnalisé pour ce tableau",
"description": "En outre, vous pouvez personnaliser votre tableau de bord à l'aide de CSS. Réservé aux utilisateurs expérimentés.", "description": "De plus, personnalisez votre tableau de bord à l'aide de CSS, recommandé uniquement pour les utilisateurs expérimentés",
"customClassesAlert": { "customClassesAlert": {
"title": "Classes personnalisées", "title": "Classes personnalisées",
"description": "Vous pouvez ajouter des classes personnalisées aux éléments de votre tableau dans les options avancées de chaque élément et les utiliser dans le CSS personnalisé ci-dessus." "description": "Vous pouvez ajouter des classes personnalisées aux éléments de votre tableau dans les options avancées de chaque élément et les utiliser dans le CSS personnalisé ci-dessus."
@@ -3243,7 +3251,7 @@
"label": "Supprimer définitivement", "label": "Supprimer définitivement",
"confirm": { "confirm": {
"title": "Supprimer le tableau de bord", "title": "Supprimer le tableau de bord",
"description": "Êtes-vous sûr de vouloir supprimer le tableau {name}?" "description": "Êtes-vous sûr de vouloir supprimer le tableau {name} ?"
} }
} }
}, },
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "אפשר מיקום גיאוגרפי" "label": "אפשר מיקום גיאוגרפי"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "הצג מידע זיכרון" "label": "הצג מידע זיכרון"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "הצג זמן פעולה" "label": "הצג זמן פעולה"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "מעבד", "cpu": "מעבד",
"memory": "זיכרון", "memory": "זיכרון",
"gpu": "",
"network": "רשת" "network": "רשת"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "מעבד", "cpu": "מעבד",
"memory": "זיכרון", "memory": "זיכרון",
"gpu": "",
"network": "רשת", "network": "רשת",
"up": "העלאה", "up": "העלאה",
"down": "הורדה" "down": "הורדה"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Dopusti geolociranje" "label": "Dopusti geolociranje"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+98 -90
View File
@@ -51,7 +51,7 @@
"title": "Import token megadása", "title": "Import token megadása",
"field": { "field": {
"token": { "token": {
"label": "", "label": "Token",
"description": "Írja be a megjelenített import tokent az előző homarr példányból" "description": "Írja be a megjelenített import tokent az előző homarr példányból"
} }
}, },
@@ -153,10 +153,10 @@
"label": "Ikonok használata a pingekhez" "label": "Ikonok használata a pingekhez"
}, },
"defaultSearchEngine": { "defaultSearchEngine": {
"label": "" "label": "Alapértelmezett kereső"
}, },
"openSearchInNewTab": { "openSearchInNewTab": {
"label": "" "label": "Nyissa meg a keresési eredményeket új ablakban"
} }
}, },
"error": { "error": {
@@ -219,10 +219,10 @@
"changeSearchPreferences": { "changeSearchPreferences": {
"notification": { "notification": {
"success": { "success": {
"message": "" "message": "A keresési beállítások sikeresen megváltoztak"
}, },
"error": { "error": {
"message": "" "message": "A keresési beállításokat nem sikerült megváltoztatni"
} }
} }
}, },
@@ -307,12 +307,12 @@
"name": "Név", "name": "Név",
"members": "Tagok", "members": "Tagok",
"homeBoard": { "homeBoard": {
"label": "", "label": "Főoldal",
"description": "" "description": "Csak a csoport számára elérhető oldalak választhatók ki"
}, },
"mobileBoard": { "mobileBoard": {
"label": "", "label": "Mobiloldal",
"description": "" "description": "Csak a csoport számára elérhető oldalak választhatók ki"
} }
}, },
"permission": { "permission": {
@@ -514,12 +514,12 @@
"board": { "board": {
"notification": { "notification": {
"success": { "success": {
"title": "", "title": "Beállítások elmentve",
"message": "" "message": "Oldal beállítások sikeresen elmentve"
}, },
"error": { "error": {
"title": "", "title": "Nem sikerült menteni a beállításokat",
"message": "" "message": "Az oldalbeállítások nem menthetők"
} }
} }
} }
@@ -527,21 +527,21 @@
"changePosition": { "changePosition": {
"notification": { "notification": {
"success": { "success": {
"message": "" "message": "Pozíció sikeresen megváltozott"
}, },
"error": { "error": {
"message": "" "message": "Pozíció nem módosítható"
} }
} }
} }
}, },
"defaultGroup": { "defaultGroup": {
"name": "", "name": "Alapértelmezett csoport",
"description": "" "description": "{name} - Minden bejelentkezett felhasználó"
} }
}, },
"app": { "app": {
"search": "", "search": "Alkalmazás megtalálása",
"page": { "page": {
"list": { "list": {
"title": "Alkalmazások", "title": "Alkalmazások",
@@ -599,12 +599,12 @@
"label": "Leírás" "label": "Leírás"
}, },
"url": { "url": {
"label": "" "label": "Url"
}, },
"useDifferentUrlForPing": { "useDifferentUrlForPing": {
"checkbox": { "checkbox": {
"label": "", "label": "Más URL használata a ping-hez",
"description": "" "description": "Hasznos, ha a Homarr közvetlenül, egy belső hosztnév vagy hálózaton keresztül fér hozzá, hogy elkerülje az internetszolgáltató által használt sávszélesség terhelését"
} }
} }
}, },
@@ -612,17 +612,17 @@
"select": { "select": {
"label": "Alkalmazás kiválasztása", "label": "Alkalmazás kiválasztása",
"notFound": "Nem található alkalmazás", "notFound": "Nem található alkalmazás",
"search": "", "search": "Alkalmazás keresése",
"noResults": "", "noResults": "Nincs találat",
"action": "", "action": "Kiválasztás {app}",
"title": "" "title": "Válassz egy programot az oldalhoz adáshoz"
}, },
"create": { "create": {
"title": "", "title": "Új alkalmazás készítése",
"description": "", "description": "Új alkalmazás létrehozása ",
"action": "" "action": "Alkalmazás készítő megnyitása"
}, },
"add": "" "add": "Alkalmazás hozzáadása"
} }
}, },
"integration": { "integration": {
@@ -649,14 +649,14 @@
"app": { "app": {
"option": { "option": {
"existing": { "existing": {
"title": "", "title": "Meglévő",
"label": "" "label": "Meglévő alkalmazás kiválasztása"
}, },
"new": { "new": {
"title": "", "title": "Új",
"url": { "url": {
"label": "", "label": "Alkalmazás url",
"description": "" "description": "Az url, amelyre az alkalmazás a műszerfalról történő eléréskor irányít"
} }
} }
} }
@@ -676,9 +676,9 @@
}, },
"app": { "app": {
"action": { "action": {
"add": "", "add": "Alkalmazás megtalálása",
"remove": "", "remove": "Leválasztás",
"select": "" "select": "Válasszon ki egy alkalmazást a hivatkozáshoz"
} }
} }
}, },
@@ -702,21 +702,21 @@
"label": "Név" "label": "Név"
}, },
"url": { "url": {
"label": "" "label": "Url"
}, },
"attemptSearchEngineCreation": { "attemptSearchEngineCreation": {
"label": "", "label": "Keresőmotor létrehozása",
"description": "" "description": "A \"{kind}\" integráció használható a keresőmotorokkal. Jelölje be ezt a jelölőnégyzetet, ha automatikusan szeretné konfigurálni a keresőmotort."
}, },
"app": { "app": {
"sectionTitle": "" "sectionTitle": "Hivattkozott alkalmazás"
}, },
"createApp": { "createApp": {
"label": "", "label": "Alkalmazás létrehozása",
"description": "" "description": "Hozzon létre egy alkalmazást, amelynek ugyanaz a neve és ikonja van, mint az integrációnak. Hagyja üresen az alábbi beviteli mezőt, hogy az alkalmazás az integrációs URL-lel jöjjön létre."
}, },
"appHref": { "appHref": {
"placeholder": "" "placeholder": "Egyéni alkalmazás URL"
} }
}, },
"action": { "action": {
@@ -730,59 +730,59 @@
"error": { "error": {
"common": { "common": {
"cause": { "cause": {
"title": "" "title": "Részletesebb leírás a problémáról"
} }
}, },
"unknown": { "unknown": {
"title": "", "title": "Ismeretlen hiba",
"description": "" "description": "Egy ismeretlen hiba történt, nyissa meg a részleteket alul, hogy többet megtudjon"
}, },
"parse": { "parse": {
"title": "", "title": "Elemzési hiba",
"description": "" "description": "A válasz nem volt elemezhető. Kérjük, ellenőrizze, hogy az URL a szolgáltatás alap URL-jére mutat-e."
}, },
"authorization": { "authorization": {
"title": "", "title": "Hitelesítési hiba",
"description": "" "description": "A kérés nem volt engedélyezett. Kérjük, ellenőrizze, hogy a hitelesítő adatok helyesek-e, és hogy a megfelelő engedélyekkel vannak-e beállítva."
}, },
"statusCode": { "statusCode": {
"title": "", "title": "Válaszhiba",
"description": "", "description": "Váratlan {statusCode} ({reason}) választ kapott a <url></url> címen. Kérjük, ellenőrizze, hogy az URL az integráció alap URL-jére mutat-e.",
"otherDescription": "", "otherDescription": "Váratlan {statusCode} választ kapott a <url></url> címen. Kérjük, ellenőrizze, hogy az URL az integráció alap URL-jére mutat-e.",
"reason": { "reason": {
"badRequest": "", "badRequest": "Hibás kérés",
"notFound": "", "notFound": "Nem található",
"tooManyRequests": "", "tooManyRequests": "Túl sok kérés",
"internalServerError": "", "internalServerError": "Belső szerverhiba",
"serviceUnavailable": "", "serviceUnavailable": "A szolgáltatás nem elérhető",
"gatewayTimeout": "" "gatewayTimeout": "Átjáró időtúllépés"
} }
}, },
"certificate": { "certificate": {
"title": "", "title": "Tanúsítványhiba",
"description": { "description": {
"expired": "", "expired": "A tanúsítvány lejárt.",
"notYetValid": "", "notYetValid": "A tanúsítvány még nem érvényes.",
"untrusted": "", "untrusted": "A tanúsítvány nem megbízható.",
"hostnameMismatch": "" "hostnameMismatch": "A tanúsítványban szereplő hosztnév nem egyezik meg az URL-lel."
}, },
"alert": { "alert": {
"permission": { "permission": {
"title": "", "title": "Nincsenek megfelelő engedélyek",
"message": "" "message": "Önnek nincs joga tanúsítványokat megbízhatónak nyilvánítani vagy feltölteni. Kérjük, lépjen kapcsolatba a rendszergazdájával, hogy feltölthesse a szükséges gyökér tanúsítványt."
}, },
"hostnameMismatch": { "hostnameMismatch": {
"title": "", "title": "Állomásnév eltérés",
"message": "" "message": "A tanúsítványban szereplő hosztnév nem egyezik meg azzal a hosztnévvel, amelyhez kapcsolódik. Ez biztonsági kockázatot jelezhet, de mégis dönthet úgy, hogy megbízik ebben a tanúsítványban."
}, },
"extract": { "extract": {
"title": "", "title": "A tanúsító hatóság (CA) tanúsítványának kinyerése sikertelen",
"message": "" "message": "Csak a lánc nélküli, önaláírt tanúsítványok tölthetők le automatikusan. Ha önaláírt tanúsítványt használ, kérjük, feltétlenül töltse fel manuálisan a tanúsító hatóság (CA) tanúsítványát. Az ehhez szükséges lépések leírását itt találja: <docsLink></docsLink>."
} }
}, },
"action": { "action": {
"retry": { "retry": {
"label": "" "label": "Újrapróbál"
}, },
"trust": { "trust": {
"label": "" "label": ""
@@ -920,8 +920,8 @@
"message": "Az útvonal valószínűleg nem helyes" "message": "Az útvonal valószínűleg nem helyes"
}, },
"tooManyRequests": { "tooManyRequests": {
"title": "", "title": "Túl sok kérés adott idő alatt",
"message": "" "message": "Túl sok kérést küldött. Valószínűleg a célrendszer korlátozza vagy elutasítja a kéréseket"
} }
} }
}, },
@@ -955,36 +955,36 @@
"newLabel": "Új jelszó" "newLabel": "Új jelszó"
}, },
"tokenId": { "tokenId": {
"label": "", "label": "Token azonosító",
"newLabel": "" "newLabel": "Új token azonosító"
}, },
"realm": { "realm": {
"label": "", "label": "Tartomány",
"newLabel": "" "newLabel": "Új tartomány"
}, },
"personalAccessToken": { "personalAccessToken": {
"label": "", "label": "Személyes Hozzáférési Token",
"newLabel": "" "newLabel": "Új Személyes Hozzáférési Token"
}, },
"topic": { "topic": {
"label": "", "label": "Téma",
"newLabel": "" "newLabel": "Új téma"
}, },
"url": { "url": {
"label": "", "label": "Url",
"newLabel": "" "newLabel": "Új url"
}, },
"opnsenseApiKey": { "opnsenseApiKey": {
"label": "", "label": "API kulcs (Kulcs)",
"newLabel": "" "newLabel": "Új API Kulcs (Kulcs)"
}, },
"opnsenseApiSecret": { "opnsenseApiSecret": {
"label": "", "label": "Api Kulcs (Titkos)",
"newLabel": "" "newLabel": "Új API Kulcs (Titkos)"
}, },
"githubAppId": { "githubAppId": {
"label": "", "label": "App Azonosító",
"newLabel": "" "newLabel": "Új App Azonosító"
}, },
"githubInstallationId": { "githubInstallationId": {
"label": "", "label": "",
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Geolokáció engedélyezése" "label": "Geolokáció engedélyezése"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Memóriainformációk megjelenítése" "label": "Memóriainformációk megjelenítése"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Consenti geo-localizzazione" "label": "Consenti geo-localizzazione"
},
"allowModals": {
"label": "Consenti modali"
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Mostra Informazioni Memoria" "label": "Mostra Informazioni Memoria"
}, },
"gpu": {
"label": "Mostra informazioni sulla GPU"
},
"showUptime": { "showUptime": {
"label": "Mostra Uptime" "label": "Mostra Uptime"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Memoria", "memory": "Memoria",
"gpu": "GPU",
"network": "Rete" "network": "Rete"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "GPU",
"network": "NET", "network": "NET",
"up": "SU", "up": "SU",
"down": "DOWN" "down": "DOWN"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "位置情報を許可する" "label": "位置情報を許可する"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "メモリー情報を表示" "label": "メモリー情報を表示"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "稼働時間を表示" "label": "稼働時間を表示"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "メモリ", "memory": "メモリ",
"gpu": "",
"network": "ネットワーク" "network": "ネットワーク"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "",
"network": "NET", "network": "NET",
"up": "UP", "up": "UP",
"down": "DOWN" "down": "DOWN"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "지리적 위치 허용" "label": "지리적 위치 허용"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Leisti nustatyti geografinę buvimo vietą" "label": "Leisti nustatyti geografinę buvimo vietą"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Atļaut ģeogrāfiskās atrašanās vietas noteikšanu" "label": "Atļaut ģeogrāfiskās atrašanās vietas noteikšanu"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Rādīt atmiņas informāciju" "label": "Rādīt atmiņas informāciju"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Geolocatie toestaan" "label": "Geolocatie toestaan"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Werkgeheugen info weergeven" "label": "Werkgeheugen info weergeven"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Uptime weergeven" "label": "Uptime weergeven"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "Processor", "cpu": "Processor",
"memory": "Geheugen", "memory": "Geheugen",
"gpu": "",
"network": "Netwerk" "network": "Netwerk"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "",
"network": "NET", "network": "NET",
"up": "OMHOOG", "up": "OMHOOG",
"down": "OMLAAG" "down": "OMLAAG"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Tillat geolokalisering" "label": "Tillat geolokalisering"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Vis minneinfo" "label": "Vis minneinfo"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Vis oppetid" "label": "Vis oppetid"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Minne", "memory": "Minne",
"gpu": "",
"network": "Nettverk" "network": "Nettverk"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Minne", "memory": "Minne",
"gpu": "",
"network": "NETT", "network": "NETT",
"up": "OPP", "up": "OPP",
"down": "NED" "down": "NED"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Zezwalaj na geolokalizację" "label": "Zezwalaj na geolokalizację"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Pokaż informacje o pamięci" "label": "Pokaż informacje o pamięci"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Pokaż czas pracy" "label": "Pokaż czas pracy"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "Procesor", "cpu": "Procesor",
"memory": "Pamięć", "memory": "Pamięć",
"gpu": "",
"network": "Sieć" "network": "Sieć"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "Procesor", "cpu": "Procesor",
"memory": "Pamięć", "memory": "Pamięć",
"gpu": "",
"network": "Sieć", "network": "Sieć",
"up": "Aktywny", "up": "Aktywny",
"down": "Nieaktywny" "down": "Nieaktywny"
+64 -56
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Permitir geolocalização" "label": "Permitir geolocalização"
},
"allowModals": {
"label": "Permitir modais"
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Mostrar informações da memória" "label": "Mostrar informações da memória"
}, },
"gpu": {
"label": "Mostrar informações da GPU"
},
"showUptime": { "showUptime": {
"label": "Exibir tempo de atividade" "label": "Exibir tempo de atividade"
}, },
@@ -1997,58 +2003,58 @@
} }
}, },
"tracearr": { "tracearr": {
"name": "", "name": "Tracearr",
"description": "", "description": "Monitoramento avançado de servidor de mídia com dados detalhados de transmissão e transcodificação\n",
"option": { "option": {
"showStreams": { "showStreams": {
"label": "" "label": "Exibir transmissões ativas"
}, },
"showStats": { "showStats": {
"label": "" "label": "Exibir resumo de estatísticas"
}, },
"showRecentActivity": { "showRecentActivity": {
"label": "" "label": "Exibir Atividade Recente"
}, },
"showViolations": { "showViolations": {
"label": "" "label": "Mostrar Violações"
} }
}, },
"stats": { "stats": {
"activeStreams": "", "activeStreams": "Streams Ativas",
"totalUsers": "", "totalUsers": "Total de usuários",
"transcodes": "", "transcodes": "Transcodificações",
"bandwidth": "" "bandwidth": "Largura da banda"
}, },
"streams": { "streams": {
"empty": "", "empty": "Sem transmissões ativas",
"user": "", "user": "Usuário",
"media": "", "media": "Mídia",
"state": "", "state": "Estado",
"quality": "", "quality": "Qualidade",
"device": "", "device": "Dispositivo",
"progress": "", "progress": "Progresso",
"videoDecision": { "videoDecision": {
"directPlay": "", "directPlay": "Reprodução direta",
"transcode": "", "transcode": "Transcodificação",
"directStream": "" "directStream": "Transmissão direta"
} }
}, },
"recentActivity": { "recentActivity": {
"title": "", "title": "Atividade recente",
"empty": "", "empty": "Nenhuma atividade recente",
"watched": "", "watched": "Assistido",
"partial": "" "partial": "Parcial"
}, },
"violations": { "violations": {
"title": "", "title": "Violações",
"empty": "", "empty": "Não há violações recentes",
"rule": "", "rule": "Regra",
"user": "" "user": "Usuário"
}, },
"noSectionsEnabled": "", "noSectionsEnabled": "Nenhuma seção habilitada",
"error": { "error": {
"noIntegration": "", "noIntegration": "Nenhuma integração Coolify configurada",
"internalServerError": "" "internalServerError": "Falha ao buscar dados do Tracearr"
} }
}, },
"common": { "common": {
@@ -2147,50 +2153,50 @@
} }
}, },
"immich-serverStats": { "immich-serverStats": {
"name": "", "name": "Estatísticas do Servidor Immich",
"description": "", "description": "Informações sobre sua instância do Immich",
"option": { "option": {
"showUsers": { "showUsers": {
"label": "", "label": "Mostrar usuários",
"description": "" "description": "Mostrar a contagem de usuários na sua instância"
}, },
"showPhotos": { "showPhotos": {
"label": "", "label": "Mostrar Fotos",
"description": "" "description": "Mostrar a contagem de fotos na sua instância"
}, },
"showVideos": { "showVideos": {
"label": "", "label": "Mostrar vídeos",
"description": "" "description": "Mostrar contagem de vídeos na sua instância"
}, },
"showStorage": { "showStorage": {
"label": "", "label": "Mostrar armazenamento",
"description": "" "description": "Mostrar a quantidade total de armazenamento utilizado"
} }
}, },
"users": "", "users": "Usuários",
"photos": "", "photos": "Fotos",
"videos": "", "videos": "Vídeos",
"storage": "" "storage": "Armazenamento Utilizado"
}, },
"immich-albumCarousel": { "immich-albumCarousel": {
"name": "", "name": "Álbum do Immich",
"option": { "option": {
"albumId": { "albumId": {
"label": "", "label": "ID do Álbum",
"description": "" "description": "Esta é uma identificação única que se refere ao seu álbum Immich. Você pode encontrar essa identificação abrindo um álbum e copiando a string única da URL, por exemplo 'a21aca09-9f05-47dc-b1a1-7b53c547226e'"
}, },
"rotationIntervalSeconds": { "rotationIntervalSeconds": {
"label": "", "label": "Intervalo em segundos",
"description": "" "description": "Girar entre as imagens no álbum a cada X segundos. Deve ser pelo menos 1. Deve estar abaixo de 3600."
}, },
"showPhotoInfo": { "showPhotoInfo": {
"label": "", "label": "Mostrar informações da foto",
"description": "" "description": "Exibir a data da imagem e o índice atual na parte inferior da apresentação"
} }
}, },
"description": "", "description": "Apresentação de imagens em seu álbum Imich",
"noAlbumSelected": "", "noAlbumSelected": "Nenhum álbum selecionado, edite o widget e escolha um álbum",
"noPhotos": "" "noPhotos": "Nenhuma foto neste álbum"
}, },
"downloads": { "downloads": {
"name": "Cliente de Download", "name": "Cliente de Download",
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "CPU", "cpu": "CPU",
"memory": "Memória", "memory": "Memória",
"gpu": "GPU",
"network": "Rede" "network": "Rede"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "CPU", "cpu": "CPU",
"memory": "MEM", "memory": "MEM",
"gpu": "GPU",
"network": "REDE", "network": "REDE",
"up": "ENVIO", "up": "ENVIO",
"down": "RECEBIDO" "down": "RECEBIDO"
@@ -3575,7 +3583,7 @@
"label": "Clima" "label": "Clima"
}, },
"tracearr": { "tracearr": {
"label": "" "label": "Tracearr"
} }
}, },
"interval": { "interval": {
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Permiteți geolocalizarea" "label": "Permiteți geolocalizarea"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Afișare informații memorie" "label": "Afișare informații memorie"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Разрешить геолокацию" "label": "Разрешить геолокацию"
},
"allowModals": {
"label": "Разрешить модальные окна"
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Показать информацию о памяти" "label": "Показать информацию о памяти"
}, },
"gpu": {
"label": "Показать информацию о GPU"
},
"showUptime": { "showUptime": {
"label": "Показывать время работы" "label": "Показывать время работы"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "ЦП", "cpu": "ЦП",
"memory": "Память", "memory": "Память",
"gpu": "GPU",
"network": "Сеть" "network": "Сеть"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "ЦП", "cpu": "ЦП",
"memory": "ОЗУ", "memory": "ОЗУ",
"gpu": "GPU",
"network": "Сеть", "network": "Сеть",
"up": "Исх.", "up": "Исх.",
"down": "Вх." "down": "Вх."
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Povolenie geografickej lokalizácie" "label": "Povolenie geografickej lokalizácie"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Zobraziť informácie o pamäti" "label": "Zobraziť informácie o pamäti"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Zobraziť Uptime" "label": "Zobraziť Uptime"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Omogočanje zemljepisne lokacije" "label": "Omogočanje zemljepisne lokacije"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Tillåt geografisk lokalisering" "label": "Tillåt geografisk lokalisering"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Visa minnesinformation" "label": "Visa minnesinformation"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Coğrafi konuma izin ver (geolocation)" "label": "Coğrafi konuma izin ver (geolocation)"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Bellek Bilgilerini Göster" "label": "Bellek Bilgilerini Göster"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "Çalışma Süresini Göster" "label": "Çalışma Süresini Göster"
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "İşlemci", "cpu": "İşlemci",
"memory": "Bellek", "memory": "Bellek",
"gpu": "",
"network": "Ağ" "network": "Ağ"
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "İşlemci", "cpu": "İşlemci",
"memory": "BELLEK", "memory": "BELLEK",
"gpu": "",
"network": "AĞ", "network": "AĞ",
"up": "YÜKLEME", "up": "YÜKLEME",
"down": "İNDİRME" "down": "İNDİRME"
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Дозволити геолокацію" "label": "Дозволити геолокацію"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "Показати інформацію про пам'ять" "label": "Показати інформацію про пам'ять"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "Cho phép định vị" "label": "Cho phép định vị"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "" "label": ""
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+8
View File
@@ -1625,6 +1625,9 @@
}, },
"allowGeolocation": { "allowGeolocation": {
"label": "允許地址位置" "label": "允許地址位置"
},
"allowModals": {
"label": ""
} }
}, },
"error": { "error": {
@@ -1858,6 +1861,9 @@
"memory": { "memory": {
"label": "顯示記憶體訊息" "label": "顯示記憶體訊息"
}, },
"gpu": {
"label": ""
},
"showUptime": { "showUptime": {
"label": "" "label": ""
}, },
@@ -2725,6 +2731,7 @@
"option": { "option": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "" "network": ""
} }
}, },
@@ -2741,6 +2748,7 @@
"card": { "card": {
"cpu": "", "cpu": "",
"memory": "", "memory": "",
"gpu": "",
"network": "", "network": "",
"up": "", "up": "",
"down": "" "down": ""
+1 -1
View File
@@ -33,7 +33,7 @@
"@mantine/core": "^8.3.18", "@mantine/core": "^8.3.18",
"@mantine/dates": "^8.3.18", "@mantine/dates": "^8.3.18",
"@mantine/hooks": "^8.3.18", "@mantine/hooks": "^8.3.18",
"@tabler/icons-react": "^3.40.0", "@tabler/icons-react": "^3.41.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.2.1", "next": "16.2.1",
+1 -1
View File
@@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@homarr/definitions": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"zod": "^4.2.1", "zod": "^4.3.6",
"zod-form-data": "^3.0.1" "zod-form-data": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
+10 -6
View File
@@ -57,12 +57,13 @@ export const userBaseCreateSchema = z.object({
password: userPasswordSchema, password: userPasswordSchema,
confirmPassword: z.string(), confirmPassword: z.string(),
email: z.string().email().or(z.string().length(0)).optional(), email: z.string().email().or(z.string().length(0)).optional(),
groupIds: z.array(z.string()),
}); });
export const userCreateSchema = addConfirmPasswordRefinement(userBaseCreateSchema); export const userCreateSchema = addConfirmPasswordRefinement(userBaseCreateSchema).and(
z.object({ groupIds: z.array(z.string()) }),
);
export const userInitSchema = addConfirmPasswordRefinement(userBaseCreateSchema.omit({ groupIds: true })); export const userInitSchema = addConfirmPasswordRefinement(userBaseCreateSchema);
export const userSignInSchema = z.object({ export const userSignInSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
@@ -111,12 +112,15 @@ const baseChangePasswordSchema = z.object({
previousPassword: z.string().min(1), previousPassword: z.string().min(1),
password: userPasswordSchema, password: userPasswordSchema,
confirmPassword: z.string(), confirmPassword: z.string(),
userId: z.string(),
}); });
export const userChangePasswordSchema = addConfirmPasswordRefinement(baseChangePasswordSchema.omit({ userId: true })); export const userChangePasswordSchema = addConfirmPasswordRefinement(baseChangePasswordSchema);
export const userChangePasswordApiSchema = addConfirmPasswordRefinement(baseChangePasswordSchema); export const userChangePasswordApiSchema = addConfirmPasswordRefinement(baseChangePasswordSchema).and(
z.object({
userId: z.string(),
}),
);
export const userChangeHomeBoardsSchema = z.object({ export const userChangeHomeBoardsSchema = z.object({
homeBoardId: z.string().nullable(), homeBoardId: z.string().nullable(),
+18 -18
View File
@@ -51,23 +51,23 @@
"@mantine/charts": "^8.3.18", "@mantine/charts": "^8.3.18",
"@mantine/core": "^8.3.18", "@mantine/core": "^8.3.18",
"@mantine/hooks": "^8.3.18", "@mantine/hooks": "^8.3.18",
"@tabler/icons-react": "^3.40.0", "@tabler/icons-react": "^3.41.0",
"@tiptap/extension-color": "3.20.4", "@tiptap/extension-color": "3.21.0",
"@tiptap/extension-highlight": "3.20.4", "@tiptap/extension-highlight": "3.21.0",
"@tiptap/extension-image": "3.20.4", "@tiptap/extension-image": "3.21.0",
"@tiptap/extension-link": "^3.20.4", "@tiptap/extension-link": "^3.21.0",
"@tiptap/extension-placeholder": "^3.20.4", "@tiptap/extension-placeholder": "^3.21.0",
"@tiptap/extension-table": "3.20.4", "@tiptap/extension-table": "3.21.0",
"@tiptap/extension-table-cell": "3.20.4", "@tiptap/extension-table-cell": "3.21.0",
"@tiptap/extension-table-header": "3.20.4", "@tiptap/extension-table-header": "3.21.0",
"@tiptap/extension-table-row": "3.20.4", "@tiptap/extension-table-row": "3.21.0",
"@tiptap/extension-task-item": "3.20.4", "@tiptap/extension-task-item": "3.21.0",
"@tiptap/extension-task-list": "3.20.4", "@tiptap/extension-task-list": "3.21.0",
"@tiptap/extension-text-align": "3.20.4", "@tiptap/extension-text-align": "3.21.0",
"@tiptap/extension-text-style": "3.20.4", "@tiptap/extension-text-style": "3.21.0",
"@tiptap/extension-underline": "3.20.4", "@tiptap/extension-underline": "3.21.0",
"@tiptap/react": "^3.20.4", "@tiptap/react": "^3.21.0",
"@tiptap/starter-kit": "^3.20.4", "@tiptap/starter-kit": "^3.21.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
@@ -78,7 +78,7 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"recharts": "^2.15.4", "recharts": "^2.15.4",
"video.js": "^8.23.7", "video.js": "^8.23.7",
"zod": "^4.2.1" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2
View File
@@ -1,5 +1,6 @@
import type { WidgetOptionType } from "../options"; import type { WidgetOptionType } from "../options";
import { WidgetAppInput } from "./widget-app-input"; import { WidgetAppInput } from "./widget-app-input";
import { WidgetDynamicSelectInput } from "./widget-dynamic-select-input";
import { WidgetLocationInput } from "./widget-location-input"; import { WidgetLocationInput } from "./widget-location-input";
import { WidgetMultiTextInput } from "./widget-multi-text-input"; import { WidgetMultiTextInput } from "./widget-multi-text-input";
import { WidgetMultiReleasesRepositoriesInput } from "./widget-multiReleasesRepositories-input"; import { WidgetMultiReleasesRepositoriesInput } from "./widget-multiReleasesRepositories-input";
@@ -23,6 +24,7 @@ const mapping = {
app: WidgetAppInput, app: WidgetAppInput,
sortableItemList: WidgetSortedItemListInput, sortableItemList: WidgetSortedItemListInput,
multiReleasesRepositories: WidgetMultiReleasesRepositoriesInput, multiReleasesRepositories: WidgetMultiReleasesRepositoriesInput,
dynamicSelect: WidgetDynamicSelectInput,
} satisfies Record<WidgetOptionType, unknown>; } satisfies Record<WidgetOptionType, unknown>;
export const getInputForType = <TType extends WidgetOptionType>(type: TType) => { export const getInputForType = <TType extends WidgetOptionType>(type: TType) => {
@@ -0,0 +1,92 @@
"use client";
import { useState } from "react";
import { Group, Loader, Select } from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import { IconCheck } from "@tabler/icons-react";
import { translateIfNecessary } from "@homarr/translation";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
export interface DynamicSelectOption {
value: string;
label: string;
}
export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidgetInputProps<"dynamicSelect">) => {
const t = useI18n();
const tWidget = useWidgetInputTranslation(kind, property);
const form = useFormContext();
const inputProps = form.getInputProps(`options.${property}`);
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebouncedValue(search, 300);
const { isPending, options: selectOptions } = options.useOptions(debouncedSearch, form.values.integrationIds);
const value = inputProps.value as DynamicSelectOption | null;
const onChange = inputProps.onChange as (value: DynamicSelectOption | null) => void;
return (
<Select
label={tWidget("label")}
data={selectOptions
.map((option) =>
typeof option === "string"
? option
: {
value: option.value,
label: translateIfNecessary(t, option.label) ?? option.value,
},
)
.concat(
typeof value === "object" &&
value !== null &&
!selectOptions.some((option) =>
typeof option === "string" ? option === value.value : option.value === value.value,
)
? {
value: value.value,
label: value.label,
}
: [],
)}
searchValue={search}
onSearchChange={setSearch}
leftSection={isPending && <Loader size="xs" />}
renderOption={({ option, checked }) => {
return (
<Group flex="1" gap="xs">
{option.label}
{checked && (
<IconCheck
style={{ marginInlineStart: "auto" }}
color="currentColor"
opacity={0.6}
size={18}
stroke={1.5}
/>
)}
</Group>
);
}}
description={options.withDescription ? tWidget("description") : undefined}
searchable
{...inputProps}
value={value?.value ?? null}
onChange={(value) =>
onChange(
value
? {
value,
label:
selectOptions.filter((option) => typeof option === "object").find((option) => option.value === value)
?.label ?? value,
}
: null,
)
}
/>
);
};
+2
View File
@@ -42,6 +42,7 @@ import * as smartHomeExecuteAutomation from "./smart-home/execute-automation";
import * as stockPrice from "./stocks"; import * as stockPrice from "./stocks";
import * as systemDisks from "./system-disks"; import * as systemDisks from "./system-disks";
import * as systemResources from "./system-resources"; import * as systemResources from "./system-resources";
import * as timetable from "./timetable";
import * as tracearr from "./tracearr"; import * as tracearr from "./tracearr";
import * as video from "./video"; import * as video from "./video";
import * as weather from "./weather"; import * as weather from "./weather";
@@ -82,6 +83,7 @@ export const widgetImports = {
systemResources, systemResources,
coolify, coolify,
systemDisks, systemDisks,
timetable,
"immich-serverStats": immichServerStats, "immich-serverStats": immichServerStats,
"immich-albumCarousel": immichAlbumCarousel, "immich-albumCarousel": immichAlbumCarousel,
tracearr, tracearr,
+17
View File
@@ -6,6 +6,7 @@ import type { ZodType } from "zod/v4";
import type { IntegrationKind } from "@homarr/definitions"; import type { IntegrationKind } from "@homarr/definitions";
import type { DynamicSelectOption } from "./_inputs/widget-dynamic-select-input";
import type { inferSelectOptionValue, SelectOption } from "./_inputs/widget-select-input"; import type { inferSelectOptionValue, SelectOption } from "./_inputs/widget-select-input";
import type { ReleasesRepository } from "./releases/releases-repository"; import type { ReleasesRepository } from "./releases/releases-repository";
@@ -47,6 +48,16 @@ interface SelectInput<TOptions extends readonly SelectOption[]> extends CommonIn
searchable?: boolean; searchable?: boolean;
} }
interface DynamicSelectInput extends CommonInput<DynamicSelectOption | null> {
useOptions: (
query: string,
integrationIds: string[],
) => {
isPending: boolean;
options: DynamicSelectOption[];
};
}
interface NumberInput extends CommonInput<number> { interface NumberInput extends CommonInput<number> {
validate: z.ZodNumber; validate: z.ZodNumber;
step?: number; step?: number;
@@ -82,6 +93,12 @@ const optionsFactory = {
searchable: input.searchable ?? false, searchable: input.searchable ?? false,
withDescription: input.withDescription ?? false, withDescription: input.withDescription ?? false,
}), }),
dynamicSelect: (input: DynamicSelectInput) => ({
type: "dynamicSelect" as const,
defaultValue: input.defaultValue ?? null,
useOptions: input.useOptions,
withDescription: input.withDescription ?? false,
}),
select: <const TOptions extends SelectOption[]>(input: SelectInput<TOptions>) => ({ select: <const TOptions extends SelectOption[]>(input: SelectInput<TOptions>) => ({
type: "select" as const, type: "select" as const,
defaultValue: (input.defaultValue ?? input.options[0]) as inferSelectOptionValue<TOptions[number]>, defaultValue: (input.defaultValue ?? input.options[0]) as inferSelectOptionValue<TOptions[number]>,
@@ -0,0 +1,75 @@
import { Badge, Center, Group, Stack, Text } from "@mantine/core";
import dayjs from "dayjs";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
import type { DynamicSelectOption } from "../_inputs/widget-dynamic-select-input";
import type { WidgetComponentProps } from "../definition";
export default function TimetableWidget({ options, integrationIds }: WidgetComponentProps<"timetable">) {
// It will always have at least one integration as otherwise the NoIntegrationSelectedError would be thrown in item-content.tsx
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const integrationId = integrationIds[0]!;
const t = useScopedI18n("widget.timetable");
if (!options.station) {
return <Center h="100%">{t("noStation")}</Center>;
}
return <TimetableWidgetInner station={options.station} integrationId={integrationId} />;
}
interface TimetableWidgetInnerProps {
station: DynamicSelectOption;
integrationId: string;
}
const TimetableWidgetInner = ({ station, integrationId }: TimetableWidgetInnerProps) => {
const [timetable] = clientApi.widget.timetable.getTimetable.useSuspenseQuery({
integrationId,
stationId: station.value,
limit: 10,
});
const t = useScopedI18n("widget.timetable");
return (
<Stack w="100%" gap="xs" p="sm">
<Text fw="bold">{t("title", { station: station.label })}</Text>
{timetable.entries.map((entry) => (
<Group key={`${entry.timestamp.toISOString()}-${entry.location}`} justify="space-between" w="100%">
<Group gap="sm">
{entry.line && (
<Badge
size="sm"
color={entry.line.color ?? undefined}
variant={entry.line.color ? "filled" : "default"}
w={32}
p={0}
radius={0}
>
{entry.line.name}
</Badge>
)}
<Text size="sm">
{dayjs(entry.timestamp).format("HH:mm")}{" "}
{entry.delay >= 1 && (
<Text size="sm" span c="red">
+{entry.delay}&apos;
</Text>
)}
</Text>
<Text size="sm">{entry.location}</Text>
</Group>
{entry.platform && (
<Text size="sm" c={entry.platform.hasChanged ? "red" : undefined}>
{entry.platform.name}
</Text>
)}
</Group>
))}
</Stack>
);
};
+37
View File
@@ -0,0 +1,37 @@
import { IconBusStop } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
export const { componentLoader, definition } = createWidgetDefinition("timetable", {
icon: IconBusStop,
supportedIntegrations: ["searchCh"],
createOptions() {
return optionsBuilder.from((factory) => ({
station: factory.dynamicSelect({
useOptions(query, integrationIds) {
const { data: stations, isPending } = clientApi.widget.timetable.searchStations.useQuery(
{
integrationId: integrationIds[0] ?? "",
query,
},
{
enabled: integrationIds.length > 0,
},
);
return {
isPending,
options:
stations?.map((station) => ({
label: station.name,
value: station.id,
})) ?? [],
};
},
}),
}));
},
}).withDynamicImport(() => import("./component"));
+916 -1077
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated [{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more