Compare commits
No files matched your search
@@ -5,6 +5,7 @@ node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.turbo
|
||||
.git
|
||||
dev
|
||||
.build
|
||||
|
||||
@@ -33,6 +33,12 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.53.0
|
||||
- 1.52.0
|
||||
- 1.51.0
|
||||
- 1.50.1
|
||||
- 1.50.0
|
||||
- 1.49.1
|
||||
- 1.49.0
|
||||
- 1.48.0
|
||||
- 1.47.0
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 24.13.0
|
||||
node-version: 24.13.1
|
||||
cache: "pnpm"
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
@@ -228,6 +228,15 @@ jobs:
|
||||
DEPLOY_LATEST: ${{ github.ref_name == 'main' }}
|
||||
DEPLOY_BETA: ${{ github.ref_name == 'beta' }}
|
||||
steps:
|
||||
- name: Obtain token
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to publish the release
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -258,7 +267,7 @@ jobs:
|
||||
- name: Publish release
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ permissions: {}
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
trigger-docs-release:
|
||||
@@ -37,6 +37,7 @@ jobs:
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
update-bug-report-template:
|
||||
name: Update Bug Report Template
|
||||
runs-on: ubuntu-latest
|
||||
@@ -54,23 +55,20 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
ref: dev
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: pnpm run scripts:update-bug-report-template
|
||||
env:
|
||||
NEXT_VERSION: ${{ github.event.release.tag_name }}
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git config --global user.name "Releases Homarr"
|
||||
git checkout -b update-bug-report-template
|
||||
git add .
|
||||
git commit -m "chore: update bug report template"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
|
||||
commit-message: "chore: update bug report template"
|
||||
author: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-bug-report-template
|
||||
base: dev
|
||||
|
||||
+130
@@ -1,3 +1,133 @@
|
||||
## [1.54.0](https://github.com/homarr-labs/homarr/compare/v1.53.2...v1.54.0) (2026-02-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add glances integration ([#5102](https://github.com/homarr-labs/homarr/issues/5102)) ([91846bc](https://github.com/homarr-labs/homarr/commit/91846bc000b419b03925acc4b447efcc696ff009))
|
||||
* add slskd integration ([#5088](https://github.com/homarr-labs/homarr/issues/5088)) ([d999f37](https://github.com/homarr-labs/homarr/commit/d999f3797562a4c1d07a08b10ef4821c12390d2f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @ctrl/deluge to ^7.5.1 ([#5098](https://github.com/homarr-labs/homarr/issues/5098)) ([53d4625](https://github.com/homarr-labs/homarr/commit/53d46257ce2c257f2e083d0645178823afd45227))
|
||||
* **deps:** update dependency @ctrl/deluge to ^7.5.2 ([#5105](https://github.com/homarr-labs/homarr/issues/5105)) ([dacbcce](https://github.com/homarr-labs/homarr/commit/dacbcce179c21f8858f61633c4a72b248ec437b7))
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.11.1 ([#5097](https://github.com/homarr-labs/homarr/issues/5097)) ([193d4d2](https://github.com/homarr-labs/homarr/commit/193d4d2e2aabaffb019108c3150de3f92d84efe8))
|
||||
* **deps:** update dependency @ctrl/qbittorrent to ^9.11.2 ([#5106](https://github.com/homarr-labs/homarr/issues/5106)) ([ee62667](https://github.com/homarr-labs/homarr/commit/ee6266707f779e7a080b40b4ed3e0a8679bb2910))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.37.0 ([#5119](https://github.com/homarr-labs/homarr/issues/5119)) ([ba86019](https://github.com/homarr-labs/homarr/commit/ba860193d78f84fa9326a006caba4b3cc7c195c2))
|
||||
* **deps:** update dependency @tabler/icons-react to ^3.37.1 ([#5121](https://github.com/homarr-labs/homarr/issues/5121)) ([c3a4051](https://github.com/homarr-labs/homarr/commit/c3a40510075f56d7f156c877e1041b7bf36677b5))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.7 ([#5122](https://github.com/homarr-labs/homarr/issues/5122)) ([6861839](https://github.com/homarr-labs/homarr/commit/6861839b65fffb0c8030ae7a6fd24105fdad6b95))
|
||||
* **deps:** update dependency glob to ^13.0.4 ([#5107](https://github.com/homarr-labs/homarr/issues/5107)) ([ea0a411](https://github.com/homarr-labs/homarr/commit/ea0a411c813c8dfcb17493e6a41d2ab96077adb6))
|
||||
* **deps:** update dependency glob to ^13.0.5 ([#5109](https://github.com/homarr-labs/homarr/issues/5109)) ([02ec089](https://github.com/homarr-labs/homarr/commit/02ec0898ef32f8b6ac540a9c005130f3e7312e65))
|
||||
* **deps:** update dependency glob to ^13.0.6 ([#5117](https://github.com/homarr-labs/homarr/issues/5117)) ([908bced](https://github.com/homarr-labs/homarr/commit/908bced0dfb158a9da155c97dbf982c2d544fc20))
|
||||
* **deps:** update dependency isomorphic-dompurify to v3 ([#5130](https://github.com/homarr-labs/homarr/issues/5130)) ([cfaf82c](https://github.com/homarr-labs/homarr/commit/cfaf82cd04e4659db50e6074fce4cc739ee2aaf8))
|
||||
* **deps:** update dependency jotai to ^2.18.0 ([#5115](https://github.com/homarr-labs/homarr/issues/5115)) ([4aad087](https://github.com/homarr-labs/homarr/commit/4aad0873baec97167460bad1c69c770aecb38bd0))
|
||||
* **deps:** update dependency mysql2 to v3.17.2 ([#5104](https://github.com/homarr-labs/homarr/issues/5104)) ([15c7fc5](https://github.com/homarr-labs/homarr/commit/15c7fc5ae809b671c8d774d39b7a7fdf893db611))
|
||||
* **deps:** update dependency mysql2 to v3.17.3 ([#5116](https://github.com/homarr-labs/homarr/issues/5116)) ([42e5326](https://github.com/homarr-labs/homarr/commit/42e53261ef956a0f5ad60ac45c623d3acd3d9530))
|
||||
* **deps:** update dependency mysql2 to v3.17.4 ([#5123](https://github.com/homarr-labs/homarr/issues/5123)) ([7ca38c3](https://github.com/homarr-labs/homarr/commit/7ca38c3a781d54f91d01844e077528345a9b190b))
|
||||
* **deps:** update dependency next-intl to v4.8.3 ([#5100](https://github.com/homarr-labs/homarr/issues/5100)) ([d67c2db](https://github.com/homarr-labs/homarr/commit/d67c2db886b92e7d0973bf17ff5059b8328acdf6))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.31.1 ([#5101](https://github.com/homarr-labs/homarr/issues/5101)) ([210d46b](https://github.com/homarr-labs/homarr/commit/210d46b43ffeb956af3f983a82772609e42f08df))
|
||||
* **deps:** update dependency swagger-ui-react to ^5.31.2 ([#5124](https://github.com/homarr-labs/homarr/issues/5124)) ([51585c6](https://github.com/homarr-labs/homarr/commit/51585c6413ab3841f0bee1e736d726081912ab08))
|
||||
* **deps:** update dependency typescript-eslint to ^8.56.0 ([#5103](https://github.com/homarr-labs/homarr/issues/5103)) ([8bac6e1](https://github.com/homarr-labs/homarr/commit/8bac6e1c5a239108bc48fd877b3fc1ec8b445109))
|
||||
* **deps:** update mantine monorepo to ^8.3.15 ([#5099](https://github.com/homarr-labs/homarr/issues/5099)) ([b8f9b4c](https://github.com/homarr-labs/homarr/commit/b8f9b4cf1d870956806a68ad78a95af60c99975f))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.12.0 ([#5110](https://github.com/homarr-labs/homarr/issues/5110)) ([e3c8174](https://github.com/homarr-labs/homarr/commit/e3c8174bdcb95de8e731c79c2b4bebce98d6dbce))
|
||||
* **deps:** update tiptap monorepo to v3.20.0 ([#5112](https://github.com/homarr-labs/homarr/issues/5112)) ([3d2bc82](https://github.com/homarr-labs/homarr/commit/3d2bc82a33a88a8f2d60fd61112f1f712e956670))
|
||||
|
||||
## [1.53.2](https://github.com/homarr-labs/homarr/compare/v1.53.1...v1.53.2) (2026-02-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** restrict publishing to every week ([#5028](https://github.com/homarr-labs/homarr/issues/5028)) ([8c4be2f](https://github.com/homarr-labs/homarr/commit/8c4be2fbe80e42472b8b17542a4282ec26d1fefe))
|
||||
* **deps:** update dependency @tanstack/react-query to ^5.90.21 ([#5067](https://github.com/homarr-labs/homarr/issues/5067)) ([1b8dde9](https://github.com/homarr-labs/homarr/commit/1b8dde98d2518d3d82895a26a58db7f42abf2a6e))
|
||||
* **deps:** update dependency dotenv to ^17.3.0 ([#5079](https://github.com/homarr-labs/homarr/issues/5079)) ([1e822fb](https://github.com/homarr-labs/homarr/commit/1e822fbe0c8224e6ba5b99e6516ea4437c9f36bb))
|
||||
* **deps:** update dependency dotenv to ^17.3.1 ([#5081](https://github.com/homarr-labs/homarr/issues/5081)) ([46ba48f](https://github.com/homarr-labs/homarr/commit/46ba48fb20b69b2876fc42ee862a64717e6428f7))
|
||||
* **deps:** update dependency drizzle-kit to ^0.31.9 ([#5059](https://github.com/homarr-labs/homarr/issues/5059)) ([db14d98](https://github.com/homarr-labs/homarr/commit/db14d98d1966dada5d74507e64e90f34fb4eee60))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.6 ([#5089](https://github.com/homarr-labs/homarr/issues/5089)) ([ac5bc70](https://github.com/homarr-labs/homarr/commit/ac5bc70a5de9a4307c4165203ff26ee1a112ca81))
|
||||
* **deps:** update dependency glob to ^13.0.2 ([#5065](https://github.com/homarr-labs/homarr/issues/5065)) ([50601d5](https://github.com/homarr-labs/homarr/commit/50601d53792baae5a259adb4e4d3fefcc35d25df))
|
||||
* **deps:** update dependency glob to ^13.0.3 ([#5078](https://github.com/homarr-labs/homarr/issues/5078)) ([ba0ed77](https://github.com/homarr-labs/homarr/commit/ba0ed77bb036dd0e35db08ce9df98f133f835d53))
|
||||
* **deps:** update dependency ioredis to v5.9.3 ([#5076](https://github.com/homarr-labs/homarr/issues/5076)) ([4e1baa5](https://github.com/homarr-labs/homarr/commit/4e1baa570324be06eb442f2db08a8b152d9b416b))
|
||||
* **deps:** update dependency mysql2 to v3.17.0 ([#5066](https://github.com/homarr-labs/homarr/issues/5066)) ([fcb020c](https://github.com/homarr-labs/homarr/commit/fcb020c191b98a729508b7660fb484f29208c51c))
|
||||
* **deps:** update dependency mysql2 to v3.17.1 ([#5082](https://github.com/homarr-labs/homarr/issues/5082)) ([475f14d](https://github.com/homarr-labs/homarr/commit/475f14dc312c1b8cffbabe607c17be8b2d1d294c))
|
||||
* **deps:** update dependency react-error-boundary to ^6.1.1 ([#5086](https://github.com/homarr-labs/homarr/issues/5086)) ([bb9da30](https://github.com/homarr-labs/homarr/commit/bb9da3007e4f29fd7993bd9960e80e316a17bbf0))
|
||||
* **deps:** update dependency tsdav to ^2.1.8 ([#5090](https://github.com/homarr-labs/homarr/issues/5090)) ([840d110](https://github.com/homarr-labs/homarr/commit/840d1107ec3392b4581c931ef1efe2d9816ededd))
|
||||
* **deps:** update dependency typescript-eslint to ^8.55.0 ([#5061](https://github.com/homarr-labs/homarr/issues/5061)) ([56ecce2](https://github.com/homarr-labs/homarr/commit/56ecce254195c480eef9cefefd7bad98c49b9175))
|
||||
* **deps:** update trpc monorepo to ^11.10.0 ([#5062](https://github.com/homarr-labs/homarr/issues/5062)) ([bc7ec63](https://github.com/homarr-labs/homarr/commit/bc7ec63f8547e8f868ef5d56a1a8c5c893fda1e3))
|
||||
* **deps:** update turbo monorepo to ^2.8.5 ([#5064](https://github.com/homarr-labs/homarr/issues/5064)) ([c36e947](https://github.com/homarr-labs/homarr/commit/c36e947935246761cac26049ac24c56c8cc0990a))
|
||||
* **deps:** update turbo monorepo to ^2.8.7 ([#5071](https://github.com/homarr-labs/homarr/issues/5071)) ([c6c48ea](https://github.com/homarr-labs/homarr/commit/c6c48ea6e5f76303fd93022fd566a99af1ad829f))
|
||||
* **deps:** update turbo monorepo to ^2.8.8 ([#5084](https://github.com/homarr-labs/homarr/issues/5084)) ([dcae437](https://github.com/homarr-labs/homarr/commit/dcae437fc5cadc2e112037fa26f8351111c69869))
|
||||
* **deps:** update turbo monorepo to ^2.8.9 ([#5092](https://github.com/homarr-labs/homarr/issues/5092)) ([24560cb](https://github.com/homarr-labs/homarr/commit/24560cb93f17767b5ab8047c07f81a165e0fb769))
|
||||
|
||||
## [1.53.1](https://github.com/homarr-labs/homarr/compare/v1.53.0...v1.53.1) (2026-02-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.7.1 ([#5021](https://github.com/homarr-labs/homarr/issues/5021)) ([5946e6c](https://github.com/homarr-labs/homarr/commit/5946e6c7193eded77b98c76aedff71a7bb912574))
|
||||
* **deps:** update dependency @octokit/auth-app to ^8.2.0 ([#5009](https://github.com/homarr-labs/homarr/issues/5009)) ([4a86361](https://github.com/homarr-labs/homarr/commit/4a86361134da404462b1a9f8ff98fe3504fade0d))
|
||||
* **deps:** update dependency dotenv to ^17.2.4 ([#5029](https://github.com/homarr-labs/homarr/issues/5029)) ([e8c7f45](https://github.com/homarr-labs/homarr/commit/e8c7f454768044540ee37d75fc73654b74315390))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.5 ([#5050](https://github.com/homarr-labs/homarr/issues/5050)) ([30f561f](https://github.com/homarr-labs/homarr/commit/30f561f85ad2abb90ccb9fa3292155d51f1f379b))
|
||||
* **deps:** update dependency fastify to ^5.7.4 ([#5027](https://github.com/homarr-labs/homarr/issues/5027)) ([18e16d6](https://github.com/homarr-labs/homarr/commit/18e16d687f8137e5b5c55cf2385436b714a2d27b))
|
||||
* **deps:** update dependency glob to ^13.0.1 ([#5013](https://github.com/homarr-labs/homarr/issues/5013)) ([3b21c3e](https://github.com/homarr-labs/homarr/commit/3b21c3ef765add0f7cdc8f66ae8f1b7aac3cf51e))
|
||||
* **deps:** update dependency isomorphic-dompurify to ^2.36.0 ([#5041](https://github.com/homarr-labs/homarr/issues/5041)) ([e561fc5](https://github.com/homarr-labs/homarr/commit/e561fc550fab2207212ae108a5177a248795cf66))
|
||||
* **deps:** update dependency jotai to ^2.17.1 ([#5019](https://github.com/homarr-labs/homarr/issues/5019)) ([da96566](https://github.com/homarr-labs/homarr/commit/da965668d021ebebd8c8a7e2a4560577a6b0828b))
|
||||
* **deps:** update dependency ldapts to v8.1.5 ([#5004](https://github.com/homarr-labs/homarr/issues/5004)) ([c3932b1](https://github.com/homarr-labs/homarr/commit/c3932b18f03b86e8ee049c14ace63119fd2ab0d6))
|
||||
* **deps:** update dependency ldapts to v8.1.6 ([#5018](https://github.com/homarr-labs/homarr/issues/5018)) ([c92d487](https://github.com/homarr-labs/homarr/commit/c92d487ceaa9159579ef4352d763429887953bb5))
|
||||
* **deps:** update dependency mysql2 to v3.16.3 ([#5014](https://github.com/homarr-labs/homarr/issues/5014)) ([04b3598](https://github.com/homarr-labs/homarr/commit/04b35980f5997e744a455a847f28b1a676f9861e))
|
||||
* **deps:** update dependency next-intl to v4.8.2 ([#5005](https://github.com/homarr-labs/homarr/issues/5005)) ([b67fecd](https://github.com/homarr-labs/homarr/commit/b67fecd3963bc3112ab8ecd91385bbe13c982290))
|
||||
* **deps:** update dependency tsdav to ^2.1.7 ([#5033](https://github.com/homarr-labs/homarr/issues/5033)) ([d694b1a](https://github.com/homarr-labs/homarr/commit/d694b1a5410833a791d57fb3dd4650003510140e))
|
||||
* **deps:** update dependency video.js to ^8.23.7 ([#5034](https://github.com/homarr-labs/homarr/issues/5034)) ([6efabd4](https://github.com/homarr-labs/homarr/commit/6efabd436849b9fdea0d060fe7beaa9c67424c29))
|
||||
* **deps:** update tiptap monorepo to v3.19.0 ([#5012](https://github.com/homarr-labs/homarr/issues/5012)) ([ad41a91](https://github.com/homarr-labs/homarr/commit/ad41a918ef72f322015a02e9601c43728e680b34))
|
||||
* **deps:** update turbo monorepo to ^2.8.2 ([#5010](https://github.com/homarr-labs/homarr/issues/5010)) ([b479fea](https://github.com/homarr-labs/homarr/commit/b479fea6eb0b025b55668201160a5e57267de2e7))
|
||||
* **deps:** update turbo monorepo to ^2.8.3 ([#5016](https://github.com/homarr-labs/homarr/issues/5016)) ([f1d2f07](https://github.com/homarr-labs/homarr/commit/f1d2f07f3888e7567b56ae61a9d137255a2b8529))
|
||||
* **sections:** dynamic sections vanish when adding category ([#5046](https://github.com/homarr-labs/homarr/issues/5046)) ([60fcb71](https://github.com/homarr-labs/homarr/commit/60fcb71bd2616af3e99b20cbbcd1b0417a0fff76))
|
||||
* **seerr:** use reasonable limits for amount of returned entries ([#5001](https://github.com/homarr-labs/homarr/issues/5001)) ([be1a26a](https://github.com/homarr-labs/homarr/commit/be1a26a98d2ed9d25969ded5ae64705c001f71dd))
|
||||
|
||||
## [1.53.0](https://github.com/homarr-labs/homarr/compare/v1.52.0...v1.53.0) (2026-02-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** consolidate server analytics into single comprehensive Umami event ([#4938](https://github.com/homarr-labs/homarr/issues/4938)) ([10848db](https://github.com/homarr-labs/homarr/commit/10848db595b2254b1485ea168d1c327b907d9ff8))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @next/eslint-plugin-next to v16.1.5 ([#4959](https://github.com/homarr-labs/homarr/issues/4959)) ([ec40309](https://github.com/homarr-labs/homarr/commit/ec40309cde2280cf689856d4f78fb1500b583130))
|
||||
* **deps:** update dependency @paralleldrive/cuid2 to ^3.3.0 ([#4952](https://github.com/homarr-labs/homarr/issues/4952)) ([a9db68b](https://github.com/homarr-labs/homarr/commit/a9db68bd3eaea6a4564cd6842054ea60cc45e3a3))
|
||||
* **deps:** update dependency @tanstack/react-query-devtools to ^5.91.3 ([#4985](https://github.com/homarr-labs/homarr/issues/4985)) ([1115ad1](https://github.com/homarr-labs/homarr/commit/1115ad1364bca84dee33aa42499c986bdad2c528))
|
||||
* **deps:** update dependency fast-xml-parser to ^5.3.4 ([#4982](https://github.com/homarr-labs/homarr/issues/4982)) ([460d53c](https://github.com/homarr-labs/homarr/commit/460d53cafa1abeebf4dea7b1bb1e920267c2f9fe))
|
||||
* **deps:** update dependency fastify to ^5.7.2 ([#4955](https://github.com/homarr-labs/homarr/issues/4955)) ([0eab26c](https://github.com/homarr-labs/homarr/commit/0eab26cdace47c0b19519372a3daa6998e0c8207))
|
||||
* **deps:** update dependency jotai to ^2.17.0 ([#4972](https://github.com/homarr-labs/homarr/issues/4972)) ([e67483f](https://github.com/homarr-labs/homarr/commit/e67483fea56f0b06fa54e68b8dd62fd0b5dedadd))
|
||||
* **deps:** update dependency ldapts to v8.1.4 ([#4993](https://github.com/homarr-labs/homarr/issues/4993)) ([c2d9f18](https://github.com/homarr-labs/homarr/commit/c2d9f18e4ef28f063e4abf1175cd90ba746410b7))
|
||||
* **deps:** update dependency mysql2 to v3.16.2 ([#4961](https://github.com/homarr-labs/homarr/issues/4961)) ([5c6eafb](https://github.com/homarr-labs/homarr/commit/5c6eafb272436b78d4668d562d50001bca48e6d3))
|
||||
* **deps:** update dependency next-intl to v4.8.0 ([#4977](https://github.com/homarr-labs/homarr/issues/4977)) ([c3a7ab5](https://github.com/homarr-labs/homarr/commit/c3a7ab52ee3acbcf47255d5813317c3cf67ea49f))
|
||||
* **deps:** update dependency next-intl to v4.8.1 ([#4986](https://github.com/homarr-labs/homarr/issues/4986)) ([62b6939](https://github.com/homarr-labs/homarr/commit/62b6939b92f29618cc6d13a189b65df1922779ba))
|
||||
* **deps:** update dependency pg to ^8.18.0 ([#4989](https://github.com/homarr-labs/homarr/issues/4989)) ([c3071a3](https://github.com/homarr-labs/homarr/commit/c3071a3c95f3a30438050aa37c65950cccabf681))
|
||||
* **deps:** update dependency typescript-eslint to ^8.54.0 ([#4958](https://github.com/homarr-labs/homarr/issues/4958)) ([e88d126](https://github.com/homarr-labs/homarr/commit/e88d126355b96ac5060b552047c191f8b284461c))
|
||||
* **deps:** update dependency video.js to ^8.23.6 ([#4992](https://github.com/homarr-labs/homarr/issues/4992)) ([4209f7c](https://github.com/homarr-labs/homarr/commit/4209f7c70e75870f56b93a97f3b6053962ee3ff7))
|
||||
* **deps:** update mantine monorepo to ^8.3.14 ([#4998](https://github.com/homarr-labs/homarr/issues/4998)) ([0fafa50](https://github.com/homarr-labs/homarr/commit/0fafa505ac1c7f4ddb33ee5e8dedca9a883164ba))
|
||||
* **deps:** update nextjs monorepo to v16.1.6 ([#4969](https://github.com/homarr-labs/homarr/issues/4969)) ([7c3fe3d](https://github.com/homarr-labs/homarr/commit/7c3fe3d6d47879c67950b3dc4d7e39adbc06c146))
|
||||
* **deps:** update react monorepo to v19.2.4 ([#4960](https://github.com/homarr-labs/homarr/issues/4960)) ([d3e0b47](https://github.com/homarr-labs/homarr/commit/d3e0b47875998fc766e1c5f0481244fc659b0c49))
|
||||
* **deps:** update tiptap monorepo to v3.18.0 ([#4975](https://github.com/homarr-labs/homarr/issues/4975)) ([24e24dc](https://github.com/homarr-labs/homarr/commit/24e24dc634a9e5a25ab934541225996e62ac31be))
|
||||
* **deps:** update trpc monorepo to ^11.9.0 ([#4970](https://github.com/homarr-labs/homarr/issues/4970)) ([aeb6e0f](https://github.com/homarr-labs/homarr/commit/aeb6e0f40659bf1c514380a6c96858afdaf7e6db))
|
||||
* **deps:** update turbo monorepo to ^2.8.0 ([#4978](https://github.com/homarr-labs/homarr/issues/4978)) ([054ca57](https://github.com/homarr-labs/homarr/commit/054ca57188656ddac7aaa16f792269eaa2ef319b))
|
||||
* **deps:** update turbo monorepo to ^2.8.1 ([#4991](https://github.com/homarr-labs/homarr/issues/4991)) ([43b6246](https://github.com/homarr-labs/homarr/commit/43b6246876debccbe7239c8ee67753e17527e469))
|
||||
|
||||
## [1.52.0](https://github.com/homarr-labs/homarr/compare/v1.51.0...v1.52.0) (2026-01-30)
|
||||
|
||||
### Features
|
||||
|
||||
* **installations:** support docker secrets file for encryption key ([#4946](https://github.com/homarr-labs/homarr/issues/4946)) ([9ba8213](https://github.com/homarr-labs/homarr/commit/9ba82133da2ae472952358f84d8ee1758c0f108a))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @tanstack/react-query to ^5.90.20 ([#4939](https://github.com/homarr-labs/homarr/issues/4939)) ([840940b](https://github.com/homarr-labs/homarr/commit/840940bab663e728b8516ac48f913fef2b5c61b7))
|
||||
* **deps:** update dependency next to v16.1.5 [security] ([#4937](https://github.com/homarr-labs/homarr/issues/4937)) ([aeb1df1](https://github.com/homarr-labs/homarr/commit/aeb1df122926c7bed71bea9b21f1c42c45cc9b3e))
|
||||
* **deps:** update dependency pg to ^8.17.2 ([#4916](https://github.com/homarr-labs/homarr/issues/4916)) ([a50a29a](https://github.com/homarr-labs/homarr/commit/a50a29a778689efcca0afc2b640fdb497e6e7d98))
|
||||
* **deps:** update dependency prettier to ^3.8.1 ([#4925](https://github.com/homarr-labs/homarr/issues/4925)) ([a727b8e](https://github.com/homarr-labs/homarr/commit/a727b8e323b9fb7cba536909a51fabde0a992a69))
|
||||
* **deps:** update dependency sass to ^1.97.3 ([#4927](https://github.com/homarr-labs/homarr/issues/4927)) ([b1658a5](https://github.com/homarr-labs/homarr/commit/b1658a5d01b5c12f391af9d326ec98b3da000f9e))
|
||||
* **deps:** update dependency typescript-eslint to ^8.53.1 ([#4904](https://github.com/homarr-labs/homarr/issues/4904)) ([c58da00](https://github.com/homarr-labs/homarr/commit/c58da0095738352515d6d8c5e81b3c34c8726ffe))
|
||||
* **deps:** update mantine monorepo to ^8.3.13 ([#4911](https://github.com/homarr-labs/homarr/issues/4911)) ([f2eec74](https://github.com/homarr-labs/homarr/commit/f2eec748494480e773b45a309aa89ae3862e2b05))
|
||||
* **deps:** update nextjs monorepo to v16.1.4 ([#4909](https://github.com/homarr-labs/homarr/issues/4909)) ([99a2574](https://github.com/homarr-labs/homarr/commit/99a2574d80ad5a7dfe3b9c92d43bd4b9423bf22d))
|
||||
* **deps:** update tiptap monorepo to v3.16.0 ([#4912](https://github.com/homarr-labs/homarr/issues/4912)) ([2354510](https://github.com/homarr-labs/homarr/commit/23545101b61e456fd886eedb4b3d86b8cc606639))
|
||||
* **deps:** update tiptap monorepo to v3.17.0 ([#4940](https://github.com/homarr-labs/homarr/issues/4940)) ([d030261](https://github.com/homarr-labs/homarr/commit/d03026187cb6451d727167b02ac138f9c7ccc754))
|
||||
* **deps:** update tiptap monorepo to v3.17.1 ([#4945](https://github.com/homarr-labs/homarr/issues/4945)) ([b354085](https://github.com/homarr-labs/homarr/commit/b354085c7adfb6e5f3cad51b5b825426e072e874))
|
||||
* **deps:** update turbo monorepo to ^2.7.6 ([#4947](https://github.com/homarr-labs/homarr/issues/4947)) ([e078237](https://github.com/homarr-labs/homarr/commit/e078237e31d11029987c6c4370fd8f65023b87bc))
|
||||
|
||||
## [1.51.0](https://github.com/homarr-labs/homarr/compare/v1.50.1...v1.51.0) (2026-01-23)
|
||||
|
||||
### Features
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-alpine AS base
|
||||
FROM node:24.13.1-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
+28
-28
@@ -48,21 +48,21 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.3.12",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@mantine/dropzone": "^8.3.12",
|
||||
"@mantine/hooks": "^8.3.12",
|
||||
"@mantine/modals": "^8.3.12",
|
||||
"@mantine/tiptap": "^8.3.12",
|
||||
"@mantine/colors-generator": "^8.3.15",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/dropzone": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"@mantine/modals": "^8.3.15",
|
||||
"@mantine/tiptap": "^8.3.15",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.36.1",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"@tabler/icons-react": "^3.37.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/react-query-devtools": "^5.91.3",
|
||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/next": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@trpc/client": "^11.10.0",
|
||||
"@trpc/next": "^11.10.0",
|
||||
"@trpc/react-query": "^11.10.0",
|
||||
"@trpc/server": "^11.10.0",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
@@ -70,22 +70,22 @@
|
||||
"chroma-js": "^3.2.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.2.3",
|
||||
"dotenv": "^17.3.1",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^13.0.0",
|
||||
"isomorphic-dompurify": "^2.35.0",
|
||||
"jotai": "^2.16.2",
|
||||
"glob": "^13.0.6",
|
||||
"isomorphic-dompurify": "^3.0.0",
|
||||
"jotai": "^2.18.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.1.3",
|
||||
"next": "16.1.6",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-error-boundary": "^6.1.0",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-error-boundary": "^6.1.1",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.97.2",
|
||||
"sass": "^1.97.3",
|
||||
"superjson": "2.2.6",
|
||||
"swagger-ui-react": "^5.31.0",
|
||||
"swagger-ui-react": "^5.31.2",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
@@ -94,15 +94,15 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.2",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.2.8",
|
||||
"@types/node": "^24.10.13",
|
||||
"@types/prismjs": "^1.26.6",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.8.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { startTransition } from "react";
|
||||
import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
import type { z } from "zod/v4";
|
||||
@@ -24,16 +23,6 @@ export const InitSettings = () => {
|
||||
});
|
||||
const form = useZodForm(settingsInitSchema, { initialValues: defaultServerSettings });
|
||||
|
||||
form.watch("analytics.enableGeneral", ({ value }) => {
|
||||
if (!value) {
|
||||
startTransition(() => {
|
||||
form.setFieldValue("analytics.enableWidgetData", false);
|
||||
form.setFieldValue("analytics.enableIntegrationData", false);
|
||||
form.setFieldValue("analytics.enableUserData", false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleSubmitAsync = async (values: z.infer<typeof settingsInitSchema>) => {
|
||||
await mutateAsync(values);
|
||||
};
|
||||
@@ -47,24 +36,6 @@ export const InitSettings = () => {
|
||||
|
||||
<Stack gap="xs">
|
||||
<AnalyticsRow kind="general" {...form.getInputProps("analytics.enableGeneral", { type: "checkbox" })} />
|
||||
|
||||
<Stack gap="xs" ps="md" w="100%">
|
||||
<AnalyticsRow
|
||||
kind="integrationData"
|
||||
disabled={!form.values.analytics.enableGeneral}
|
||||
{...form.getInputProps("analytics.enableWidgetData", { type: "checkbox" })}
|
||||
/>
|
||||
<AnalyticsRow
|
||||
kind="widgetData"
|
||||
disabled={!form.values.analytics.enableGeneral}
|
||||
{...form.getInputProps("analytics.enableIntegrationData", { type: "checkbox" })}
|
||||
/>
|
||||
<AnalyticsRow
|
||||
kind="usersData"
|
||||
disabled={!form.values.analytics.enableGeneral}
|
||||
{...form.getInputProps("analytics.enableUserData", { type: "checkbox" })}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
@@ -24,15 +24,6 @@ export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!updatedValues.enableGeneral &&
|
||||
(updatedValues.enableWidgetData || updatedValues.enableIntegrationData || updatedValues.enableUserData)
|
||||
) {
|
||||
updatedValues.enableIntegrationData = false;
|
||||
updatedValues.enableUserData = false;
|
||||
updatedValues.enableWidgetData = false;
|
||||
}
|
||||
|
||||
void mutateAsync({
|
||||
settingsKey: "analytics",
|
||||
value: updatedValues,
|
||||
@@ -54,30 +45,6 @@ export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
|
||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
||||
<Stack>
|
||||
<SwitchSetting form={form} formKey="enableGeneral" title={t("general.title")} text={t("general.text")} />
|
||||
<SwitchSetting
|
||||
form={form}
|
||||
formKey="enableIntegrationData"
|
||||
ms="xl"
|
||||
title={t("integrationData.title")}
|
||||
text={t("integrationData.text")}
|
||||
disabled={!form.values.enableGeneral}
|
||||
/>
|
||||
<SwitchSetting
|
||||
form={form}
|
||||
formKey="enableWidgetData"
|
||||
ms="xl"
|
||||
title={t("widgetData.title")}
|
||||
text={t("widgetData.text")}
|
||||
disabled={!form.values.enableGeneral}
|
||||
/>
|
||||
<SwitchSetting
|
||||
form={form}
|
||||
formKey="enableUserData"
|
||||
ms="xl"
|
||||
title={t("usersData.title")}
|
||||
text={t("usersData.text")}
|
||||
disabled={!form.values.enableGeneral}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -117,6 +117,8 @@ const createColumns = (
|
||||
header: tTasks("field.interval.label"),
|
||||
size: 200,
|
||||
Cell({ row }) {
|
||||
if (row.original.preventCustomInterval) return null;
|
||||
|
||||
const handleIntervalChange = (newCron: string | null) => {
|
||||
if (!newCron || newCron === row.original.cron) return;
|
||||
|
||||
|
||||
@@ -14,16 +14,22 @@ import type { SectionItem } from "~/app/[locale]/boards/_types";
|
||||
import { useSectionContext } from "../sections/section-context";
|
||||
import { useItemActions } from "./item-actions";
|
||||
import { ItemMoveModal } from "./item-move-modal";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
|
||||
export const BoardItemMenu = ({
|
||||
offset,
|
||||
item,
|
||||
resetErrorBoundary,
|
||||
}: {
|
||||
offset: number;
|
||||
interface BoardItemMenuProps {
|
||||
offset: number;
|
||||
item: SectionItem;
|
||||
resetErrorBoundary?: () => void;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export const BoardItemMenu = (props: BoardItemMenuProps) => {
|
||||
const {data: session} = useSession();
|
||||
if (!session) return null;
|
||||
|
||||
return <BoardItemMenuInner {...props} />;
|
||||
};
|
||||
|
||||
const BoardItemMenuInner = ({ offset, item, resetErrorBoundary }: BoardItemMenuProps) => {
|
||||
const refResetErrorBoundaryOnNextRender = useRef(false);
|
||||
const tItem = useScopedI18n("item");
|
||||
const t = useI18n();
|
||||
@@ -130,4 +136,4 @@ export const BoardItemMenu = ({
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export const useCategoryActions = () => {
|
||||
...section,
|
||||
yOffset: section.yOffset + 2,
|
||||
})),
|
||||
...previous.sections.filter((section) => section.kind !== "category" && section.kind !== "empty"),
|
||||
] satisfies Section[],
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.2.3",
|
||||
"fastify": "^5.7.1",
|
||||
"dotenv": "^17.3.1",
|
||||
"fastify": "^5.7.4",
|
||||
"superjson": "2.2.6",
|
||||
"undici": "7.18.2"
|
||||
},
|
||||
@@ -47,11 +47,11 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/node": "^24.10.13",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.8.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"eslint": "^9.39.3",
|
||||
"prettier": "^3.8.1",
|
||||
"tsx": "4.20.4",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ export class JobManager implements IJobManager {
|
||||
if (!validateCron(cron)) {
|
||||
throw new Error(`Invalid cron expression: ${cron}`);
|
||||
}
|
||||
if (job.preventCustomInterval && cron !== job.cronExpression) {
|
||||
throw new Error(`Custom cron expressions are not allowed for job ${name}`);
|
||||
}
|
||||
|
||||
await this.updateConfigurationAsync(name, { cronExpression: cron });
|
||||
await this.jobGroup.getTask(name)?.destroy();
|
||||
|
||||
@@ -100,7 +104,13 @@ export class JobManager implements IJobManager {
|
||||
}
|
||||
|
||||
public async getAllAsync(): Promise<
|
||||
{ name: JobGroupKeys; cron: string; preventManualExecution: boolean; isEnabled: boolean }[]
|
||||
{
|
||||
name: JobGroupKeys;
|
||||
cron: string;
|
||||
preventManualExecution: boolean;
|
||||
preventCustomInterval: boolean;
|
||||
isEnabled: boolean;
|
||||
}[]
|
||||
> {
|
||||
const configurations = await this.db.query.cronJobConfigurations.findMany();
|
||||
|
||||
@@ -110,6 +120,7 @@ export class JobManager implements IJobManager {
|
||||
name,
|
||||
cron: config?.cronExpression ?? job.cronExpression,
|
||||
preventManualExecution: job.preventManualExecution,
|
||||
preventCustomInterval: job.preventCustomInterval,
|
||||
isEnabled: config?.isEnabled ?? true,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"dotenv": "^17.3.1",
|
||||
"tsx": "4.20.4",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
@@ -34,9 +34,9 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.8.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"eslint": "^9.39.3",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-trixie AS base
|
||||
FROM node:24.13.1-trixie AS base
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
COPY pnpm-lock.yaml .
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.6.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.0",
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"engines": {
|
||||
"node": ">=24.13.0",
|
||||
"pnpm": ">=10.28.0"
|
||||
"node": ">=24.13.1",
|
||||
"pnpm": ">=10.30.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
|
||||
+28
-14
@@ -134,6 +134,13 @@
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="null" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/glances.svg" alt="Glances" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Glances</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/home-assistant" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg" alt="Home Assistant" width="90" height="90" />
|
||||
<br/>
|
||||
@@ -146,15 +153,15 @@
|
||||
<br/>
|
||||
<p align="center">iCal</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Jellyfin</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/jellyseerr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyseerr.svg" alt="Jellyseerr" width="90" height="90" />
|
||||
<br/>
|
||||
@@ -195,15 +202,15 @@
|
||||
<br/>
|
||||
<p align="center">ntfy</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/nzbget" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg" alt="NZBGet" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">NZBGet</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/open-media-vault" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/openmediavault.svg" alt="OpenMediaVault" width="90" height="90" />
|
||||
<br/>
|
||||
@@ -244,15 +251,15 @@
|
||||
<br/>
|
||||
<p align="center">Prowlarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/proxmox" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/proxmox.svg" alt="Proxmox" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Proxmox</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/q-bittorent" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg" alt="qBittorrent" width="90" height="90" />
|
||||
<br/>
|
||||
@@ -288,6 +295,13 @@
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/slskd" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/slskd.svg" alt="Slskd" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Slskd</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/sonarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg" alt="Sonarr" width="90" height="90" />
|
||||
<br/>
|
||||
@@ -300,8 +314,8 @@
|
||||
<br/>
|
||||
<p align="center">Tdarr</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/transmission" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/transmission.svg" alt="Transmission" width="90" height="90" />
|
||||
<br/>
|
||||
|
||||
+25
-25
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.51.0",
|
||||
"version": "1.54.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -39,30 +39,30 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^12.0.2",
|
||||
"@semantic-release/npm": "^13.1.3",
|
||||
"@semantic-release/github": "^12.0.6",
|
||||
"@semantic-release/npm": "^13.1.4",
|
||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||
"@testcontainers/redis": "^11.11.0",
|
||||
"@turbo/gen": "^2.7.5",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"@vitest/coverage-v8": "^4.0.17",
|
||||
"@vitest/ui": "^4.0.17",
|
||||
"@testcontainers/redis": "^11.12.0",
|
||||
"@turbo/gen": "^2.8.9",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"jsdom": "^28.1.0",
|
||||
"json5": "^2.2.3",
|
||||
"prettier": "^3.8.0",
|
||||
"semantic-release": "^25.0.2",
|
||||
"testcontainers": "^11.11.0",
|
||||
"turbo": "^2.7.5",
|
||||
"prettier": "^3.8.1",
|
||||
"semantic-release": "^25.0.3",
|
||||
"testcontainers": "^11.12.0",
|
||||
"turbo": "^2.8.9",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^6.0.4",
|
||||
"vitest": "^4.0.17"
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.0",
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"engines": {
|
||||
"node": ">=24.13.0",
|
||||
"pnpm": ">=10.28.0"
|
||||
"node": ">=24.13.1",
|
||||
"pnpm": ">=10.30.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -79,18 +79,18 @@
|
||||
"overrides": {
|
||||
"@babel/helpers@<7.26.10": ">=7.28.6",
|
||||
"@babel/runtime@<7.26.10": ">=7.28.6",
|
||||
"axios@>=1.0.0 <1.8.2": ">=1.13.2",
|
||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
|
||||
"axios@>=1.0.0 <1.8.2": ">=1.13.5",
|
||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.3",
|
||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||
"esbuild@<=0.24.2": ">=0.27.2",
|
||||
"esbuild@<=0.24.2": ">=0.27.3",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||
"hono@<4.6.5": ">=4.11.4",
|
||||
"hono@<4.6.5": ">=4.11.9",
|
||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
||||
"prismjs@<1.30.0": ">=1.30.0",
|
||||
"proxmox-api>undici": "7.18.2",
|
||||
"react-is": "^19.2.3",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.55.1",
|
||||
"proxmox-api>undici": "7.19.2",
|
||||
"react-is": "^19.2.4",
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.59.0",
|
||||
"sha.js@<=2.4.11": ">=2.4.12",
|
||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
|
||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@umami/node": "^0.4.0",
|
||||
"superjson": "2.2.6"
|
||||
@@ -32,7 +35,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,52 @@
|
||||
import type { UmamiEventData } from "@umami/node";
|
||||
import { Umami } from "@umami/node";
|
||||
|
||||
import { isProviderEnabled } from "@homarr/auth/server";
|
||||
import { Stopwatch } from "@homarr/common";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { count, db } from "@homarr/db";
|
||||
import { isMysql, isPostgresql } from "@homarr/db/collection";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { integrations, items, users } from "@homarr/db/schema";
|
||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||
import {
|
||||
apiKeys,
|
||||
apps,
|
||||
boards,
|
||||
cronJobConfigurations,
|
||||
groups,
|
||||
iconRepositories,
|
||||
icons,
|
||||
integrations,
|
||||
invites,
|
||||
itemLayouts,
|
||||
items,
|
||||
layouts,
|
||||
medias,
|
||||
searchEngines,
|
||||
sectionLayouts,
|
||||
sections,
|
||||
users,
|
||||
} from "@homarr/db/schema";
|
||||
import { env as dockerEnv } from "@homarr/docker/env";
|
||||
|
||||
import { Stopwatch } from "../../common/src";
|
||||
import packageJson from "../../../package.json";
|
||||
import { UMAMI_HOST_URL, UMAMI_WEBSITE_ID } from "./constants";
|
||||
|
||||
const logger = createLogger({ module: "analytics" });
|
||||
|
||||
const getDatabaseType = (): "mysql" | "postgresql" | "sqlite" => {
|
||||
if (isMysql()) return "mysql";
|
||||
if (isPostgresql()) return "postgresql";
|
||||
return "sqlite";
|
||||
};
|
||||
|
||||
const getEnabledAuthProviders = (): string[] => {
|
||||
const providers: string[] = [];
|
||||
if (isProviderEnabled("credentials")) providers.push("credentials");
|
||||
if (isProviderEnabled("oidc")) providers.push("oidc");
|
||||
if (isProviderEnabled("ldap")) providers.push("ldap");
|
||||
return providers;
|
||||
};
|
||||
|
||||
export const sendServerAnalyticsAsync = async () => {
|
||||
const stopWatch = new Stopwatch();
|
||||
const analyticsSettings = await getServerSettingByKeyAsync(db, "analytics");
|
||||
@@ -27,67 +62,48 @@ export const sendServerAnalyticsAsync = async () => {
|
||||
websiteId: UMAMI_WEBSITE_ID,
|
||||
});
|
||||
|
||||
await sendIntegrationDataAsync(umamiInstance, analyticsSettings);
|
||||
await sendWidgetDataAsync(umamiInstance, analyticsSettings);
|
||||
await sendUserDataAsync(umamiInstance, analyticsSettings);
|
||||
const enabledAuthProviders = getEnabledAuthProviders();
|
||||
|
||||
logger.info(`Sent all analytics in ${stopWatch.getElapsedInHumanWords()}`);
|
||||
};
|
||||
const analyticsData: UmamiEventData = {
|
||||
homarrVersion: packageJson.version,
|
||||
databaseType: getDatabaseType(),
|
||||
dockerEnabled: dockerEnv.ENABLE_DOCKER ? 1 : 0,
|
||||
kubernetesEnabled: dockerEnv.ENABLE_KUBERNETES ? 1 : 0,
|
||||
authCredentials: enabledAuthProviders.includes("credentials") ? 1 : 0,
|
||||
authOidc: enabledAuthProviders.includes("oidc") ? 1 : 0,
|
||||
authLdap: enabledAuthProviders.includes("ldap") ? 1 : 0,
|
||||
countUsers: await db.$count(users),
|
||||
countBoards: await db.$count(boards),
|
||||
countGroups: await db.$count(groups),
|
||||
countApps: await db.$count(apps),
|
||||
countWidgets: await db.$count(items),
|
||||
countSections: await db.$count(sections),
|
||||
countIntegrations: await db.$count(integrations),
|
||||
countSearchEngines: await db.$count(searchEngines),
|
||||
countIconRepositories: await db.$count(iconRepositories),
|
||||
countIcons: await db.$count(icons),
|
||||
countApiKeys: await db.$count(apiKeys),
|
||||
countInvites: await db.$count(invites),
|
||||
countMedias: await db.$count(medias),
|
||||
countLayouts: await db.$count(layouts),
|
||||
countItemLayouts: await db.$count(itemLayouts),
|
||||
countSectionLayouts: await db.$count(sectionLayouts),
|
||||
countCronJobConfigs: await db.$count(cronJobConfigurations),
|
||||
};
|
||||
|
||||
const sendWidgetDataAsync = async (umamiInstance: Umami, analyticsSettings: typeof defaultServerSettings.analytics) => {
|
||||
if (!analyticsSettings.enableWidgetData) {
|
||||
return;
|
||||
}
|
||||
const widgetCount = await db.$count(items);
|
||||
|
||||
const response = await umamiInstance.track("server-widget-data", {
|
||||
countWidgets: widgetCount,
|
||||
});
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("Unable to send track event data to Umami instance");
|
||||
};
|
||||
|
||||
const sendUserDataAsync = async (umamiInstance: Umami, analyticsSettings: typeof defaultServerSettings.analytics) => {
|
||||
if (!analyticsSettings.enableUserData) {
|
||||
return;
|
||||
}
|
||||
const userCount = await db.$count(users);
|
||||
|
||||
const response = await umamiInstance.track("server-user-data", {
|
||||
countUsers: userCount,
|
||||
});
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("Unable to send track event data to Umami instance");
|
||||
};
|
||||
|
||||
const sendIntegrationDataAsync = async (
|
||||
umamiInstance: Umami,
|
||||
analyticsSettings: typeof defaultServerSettings.analytics,
|
||||
) => {
|
||||
if (!analyticsSettings.enableIntegrationData) {
|
||||
return;
|
||||
}
|
||||
const integrationKinds = await db
|
||||
.select({ kind: integrations.kind, count: count(integrations.id) })
|
||||
.from(integrations)
|
||||
.groupBy(integrations.kind);
|
||||
|
||||
const map: UmamiEventData = {};
|
||||
|
||||
integrationKinds.forEach((integrationKind) => {
|
||||
map[integrationKind.kind] = integrationKind.count;
|
||||
analyticsData[`integration_${integrationKind.kind}`] = integrationKind.count;
|
||||
});
|
||||
|
||||
const response = await umamiInstance.track("server-integration-data-kind", map);
|
||||
if (response.ok) {
|
||||
return;
|
||||
const response = await umamiInstance.track("server-analytics-v2", analyticsData);
|
||||
if (!response.ok) {
|
||||
logger.warn("Unable to send analytics to Umami instance");
|
||||
}
|
||||
|
||||
logger.warn("Unable to send track event data to Umami instance");
|
||||
logger.info(`Sent analytics in ${stopWatch.getElapsedInHumanWords()}`);
|
||||
};
|
||||
+10
-10
@@ -41,15 +41,15 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/react-query": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@trpc/tanstack-react-query": "^11.8.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@trpc/client": "^11.10.0",
|
||||
"@trpc/react-query": "^11.10.0",
|
||||
"@trpc/server": "^11.10.0",
|
||||
"@trpc/tanstack-react-query": "^11.10.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "16.1.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"superjson": "2.2.6",
|
||||
"trpc-to-openapi": "^3.1.0",
|
||||
"zod": "^4.2.1"
|
||||
@@ -58,8 +58,8 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.8.0",
|
||||
"eslint": "^9.39.3",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database, InferSelectModel } from "@homarr/db";
|
||||
import { asc, eq, inArray, like } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { selectAppSchema } from "@homarr/db/validationSchemas";
|
||||
@@ -11,7 +13,7 @@ import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
|
||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||
import { canUserSeeAppAsync } from "./app/app-access-control";
|
||||
import { AppAccessControl } from "./app/app-access-control";
|
||||
|
||||
const defaultIcon = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/homarr.svg";
|
||||
|
||||
@@ -89,9 +91,8 @@ export const appRouter = createTRPCRouter({
|
||||
.output(selectAppSchema)
|
||||
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||
.query(async ({ ctx, input }) => {
|
||||
const app = await ctx.db.query.apps.findFirst({
|
||||
where: eq(apps.id, input.id),
|
||||
});
|
||||
const repository = new AppRepository(ctx.db, ctx.session?.user ?? null);
|
||||
const app = await repository.getByIdAsync(input.id);
|
||||
|
||||
if (!app) {
|
||||
throw new TRPCError({
|
||||
@@ -100,20 +101,11 @@ export const appRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const canUserSeeApp = await canUserSeeAppAsync(ctx.session?.user ?? null, app.id);
|
||||
if (!canUserSeeApp) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "App not found",
|
||||
});
|
||||
}
|
||||
|
||||
return app;
|
||||
}),
|
||||
byIds: publicProcedure.input(z.array(z.string())).query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.apps.findMany({
|
||||
where: inArray(apps.id, input),
|
||||
});
|
||||
const repository = new AppRepository(ctx.db, ctx.session?.user ?? null);
|
||||
return await repository.getByIdsAsync(input);
|
||||
}),
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("app-create")
|
||||
@@ -187,3 +179,30 @@ export const appRouter = createTRPCRouter({
|
||||
await ctx.db.delete(apps).where(eq(apps.id, input.id));
|
||||
}),
|
||||
});
|
||||
|
||||
type App = InferSelectModel<typeof apps>;
|
||||
|
||||
export class AppRepository {
|
||||
private readonly accessControl: AppAccessControl;
|
||||
|
||||
constructor(
|
||||
private db: Database,
|
||||
user: Session["user"] | null,
|
||||
) {
|
||||
this.accessControl = new AppAccessControl(db, user);
|
||||
}
|
||||
|
||||
public async getByIdAsync(id: string): Promise<App | null> {
|
||||
const apps = await this.getByIdsAsync([id]);
|
||||
return apps[0] ?? null;
|
||||
}
|
||||
|
||||
public async getByIdsAsync(ids: string[]): Promise<App[]> {
|
||||
const canUserSeeApps = await this.accessControl.canUserSeeAppsAsync(ids);
|
||||
const dbApps = await this.db.query.apps.findMany({
|
||||
where: inArray(apps.id, ids),
|
||||
});
|
||||
|
||||
return canUserSeeApps ? dbApps : [];
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,52 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { db, eq, or } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { eq, or } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../../widgets/src";
|
||||
|
||||
export const canUserSeeAppAsync = async (user: Session["user"] | null, appId: string) => {
|
||||
return await canUserSeeAppsAsync(user, [appId]);
|
||||
};
|
||||
export class AppAccessControl {
|
||||
constructor(
|
||||
private db: Database,
|
||||
private user: Session["user"] | null,
|
||||
) {}
|
||||
|
||||
export const canUserSeeAppsAsync = async (user: Session["user"] | null, appIds: string[]) => {
|
||||
if (user) return true;
|
||||
async canUserSeeAppAsync(appId: string) {
|
||||
return await this.canUserSeeAppsAsync([appId]);
|
||||
}
|
||||
|
||||
const appIdsOnPublicBoards = await getAllAppIdsOnPublicBoardsAsync();
|
||||
return appIds.every((appId) => appIdsOnPublicBoards.includes(appId));
|
||||
};
|
||||
async canUserSeeAppsAsync(appIds: string[]) {
|
||||
// Currently any user can see all apps if they are logged in
|
||||
if (this.user) return true;
|
||||
|
||||
const getAllAppIdsOnPublicBoardsAsync = async () => {
|
||||
const itemsWithApps = await db.query.items.findMany({
|
||||
where: or(eq(items.kind, "app"), eq(items.kind, "bookmarks")),
|
||||
with: {
|
||||
board: {
|
||||
columns: {
|
||||
isPublic: true,
|
||||
const appIdsOnPublicBoards = await this.getAllAppIdsOnPublicBoardsAsync();
|
||||
return appIds.every((appId) => appIdsOnPublicBoards.includes(appId));
|
||||
}
|
||||
|
||||
private async getAllAppIdsOnPublicBoardsAsync() {
|
||||
const itemsWithApps = await this.db.query.items.findMany({
|
||||
where: or(eq(items.kind, "app"), eq(items.kind, "bookmarks")),
|
||||
with: {
|
||||
board: {
|
||||
columns: {
|
||||
isPublic: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return itemsWithApps
|
||||
.filter((item) => item.board.isPublic)
|
||||
.flatMap((item) => {
|
||||
if (item.kind === "app") {
|
||||
const parsedOptions = SuperJSON.parse<WidgetComponentProps<"app">["options"]>(item.options);
|
||||
return [parsedOptions.appId];
|
||||
}
|
||||
|
||||
return itemsWithApps
|
||||
.filter((item) => item.board.isPublic)
|
||||
.flatMap((item) => {
|
||||
if (item.kind === "app") {
|
||||
const parsedOptions = SuperJSON.parse<WidgetComponentProps<"app">["options"]>(item.options);
|
||||
return [parsedOptions.appId];
|
||||
} else if (item.kind === "bookmarks") {
|
||||
const parsedOptions = SuperJSON.parse<WidgetComponentProps<"bookmarks">["options"]>(item.options);
|
||||
return parsedOptions.items;
|
||||
}
|
||||
|
||||
throw new Error("Failed to get app ids from board. Invalid item kind: 'test'");
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import { mapTestConnectionError } from "./map-test-connection-error";
|
||||
const logger = createLogger({ module: "integrationRouter" });
|
||||
|
||||
export const integrationRouter = createTRPCRouter({
|
||||
all: publicProcedure.query(async ({ ctx }) => {
|
||||
all: protectedProcedure.query(async ({ ctx }) => {
|
||||
const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({
|
||||
where: eq(groupMembers.userId, ctx.session?.user.id ?? ""),
|
||||
});
|
||||
@@ -85,7 +85,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
|
||||
);
|
||||
}),
|
||||
allThatSupportSearch: publicProcedure.query(async ({ ctx }) => {
|
||||
allThatSupportSearch: protectedProcedure.query(async ({ ctx }) => {
|
||||
const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({
|
||||
where: eq(groupMembers.userId, ctx.session?.user.id ?? ""),
|
||||
});
|
||||
@@ -133,7 +133,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind),
|
||||
);
|
||||
}),
|
||||
allOfGivenCategory: publicProcedure
|
||||
allOfGivenCategory: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
category: z.enum(integrationCategories),
|
||||
|
||||
@@ -13,6 +13,14 @@ import * as appAccessControl from "../app/app-access-control";
|
||||
// Mock the auth module to return an empty session
|
||||
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
||||
|
||||
const createFakeAccessControl = (canAccess: boolean) =>
|
||||
vi.fn(
|
||||
class {
|
||||
canUserSeeAppAsync = async (_: string) => await Promise.resolve(canAccess);
|
||||
canUserSeeAppsAsync = async (_: string[]) => await Promise.resolve(canAccess);
|
||||
} as unknown as typeof appAccessControl.AppAccessControl,
|
||||
);
|
||||
|
||||
const createDefaultSession = (permissions: GroupPermissionKey[] = []): Session => ({
|
||||
user: { id: createId(), permissions, colorScheme: "light" },
|
||||
expires: new Date().toISOString(),
|
||||
@@ -77,7 +85,7 @@ describe("byId should return an app by id", () => {
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
vi.spyOn(appAccessControl, "canUserSeeAppAsync").mockReturnValue(Promise.resolve(true));
|
||||
vi.spyOn(appAccessControl, "AppAccessControl").mockImplementation(createFakeAccessControl(true));
|
||||
|
||||
await db.insert(apps).values([
|
||||
{
|
||||
@@ -118,7 +126,7 @@ describe("byId should return an app by id", () => {
|
||||
href: "https://mantine.dev",
|
||||
},
|
||||
]);
|
||||
vi.spyOn(appAccessControl, "canUserSeeAppAsync").mockReturnValue(Promise.resolve(false));
|
||||
vi.spyOn(appAccessControl, "AppAccessControl").mockImplementation(createFakeAccessControl(false));
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await caller.byId({ id: "2" });
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import * as ping from "@homarr/ping";
|
||||
|
||||
@@ -9,22 +12,31 @@ import { appRouter } from "../../widgets/app";
|
||||
// Mock the auth module to return an empty session
|
||||
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
||||
vi.mock("@homarr/ping", () => ({ sendPingRequestAsync: async () => await Promise.resolve(null) }));
|
||||
vi.mock("../../app/app-access-control", () => ({
|
||||
AppAccessControl: vi.fn(
|
||||
class {
|
||||
canUserSeeAppAsync = async (_: string) => await Promise.resolve(true);
|
||||
canUserSeeAppsAsync = async (_: string[]) => await Promise.resolve(true);
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ping should call sendPingRequestAsync with url and return result", () => {
|
||||
test("ping with error response should return error and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
vi.spyOn(ping, "sendPingRequestAsync").mockImplementation(() => Promise.resolve({ error: "error" }));
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const app = createApp({ url });
|
||||
await db.insert(apps).values(app);
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ error: "error" }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
const result = await caller.ping({ id: app.id });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
@@ -33,21 +45,32 @@ describe("ping should call sendPingRequestAsync with url and return result", ()
|
||||
|
||||
test("ping with success response should return statusCode and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
vi.spyOn(ping, "sendPingRequestAsync").mockImplementation(() =>
|
||||
Promise.resolve({ statusCode: 200, durationMs: 123 }),
|
||||
);
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const app = createApp({ url });
|
||||
await db.insert(apps).values(app);
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ statusCode: 200, durationMs: 123 }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
const result = await caller.ping({ id: app.id });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("statusCode" in result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
const createApp = ({ url }: { url: string }) =>
|
||||
({
|
||||
id: createId(),
|
||||
iconUrl: "",
|
||||
name: "Test App",
|
||||
href: url,
|
||||
}) satisfies InferInsertModel<typeof apps>;
|
||||
@@ -0,0 +1,221 @@
|
||||
import SuperJSON from "superjson";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import {
|
||||
boardGroupPermissions,
|
||||
boards,
|
||||
boardUserPermissions,
|
||||
groupMembers,
|
||||
groups,
|
||||
items,
|
||||
users,
|
||||
} from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { GroupPermissionKey } from "@homarr/definitions";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../../../widgets/src/definition";
|
||||
import { canAccessAllFeedsAsync, restrictUrlsAsync } from "../../widgets/rssFeed";
|
||||
|
||||
const createSession = (permissions: GroupPermissionKey[] = []): Session => ({
|
||||
user: {
|
||||
id: "user-id",
|
||||
colorScheme: "light",
|
||||
permissions,
|
||||
},
|
||||
expires: new Date().toISOString(),
|
||||
});
|
||||
|
||||
describe("rssFeedRouter", () => {
|
||||
describe("canAccessAllFeedsAsync", () => {
|
||||
test("should return false for unauthenticated users", async () => {
|
||||
// Arrange
|
||||
const { callback } = setupAccessCheck();
|
||||
|
||||
// Act
|
||||
const result = await callback(/* session: */ null);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
test.each([["board-create" as const], ["board-modify-all" as const]])(
|
||||
"should return true for users with %s permission",
|
||||
async (permission) => {
|
||||
// Arrange
|
||||
const { callback } = setupAccessCheck();
|
||||
const session = createSession([permission]);
|
||||
|
||||
// Act
|
||||
const result = await callback(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
test("should return false for authenticated users without any boards or permissions", async () => {
|
||||
// Arrange
|
||||
const { callback, db } = setupAccessCheck();
|
||||
const session = createSession();
|
||||
await db.insert(users).values({
|
||||
id: session.user.id,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await callback(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
test("should return true for authenticated users that own at least one board", async () => {
|
||||
// Arrange
|
||||
const { callback, db } = setupAccessCheck();
|
||||
const session = createSession();
|
||||
await db.insert(users).values({
|
||||
id: session.user.id,
|
||||
});
|
||||
await db.insert(boards).values({
|
||||
id: createId(),
|
||||
name: "test",
|
||||
creatorId: session.user.id,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await callback(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
test.each([["modify" as const], ["full" as const]])(
|
||||
"should return true for authenticated users that have %s permissions on at least one board",
|
||||
async (permission) => {
|
||||
// Arrange
|
||||
const { callback, db } = setupAccessCheck();
|
||||
const session = createSession();
|
||||
await db.insert(users).values({
|
||||
id: session.user.id,
|
||||
});
|
||||
const boardId = createId();
|
||||
await db.insert(boards).values({
|
||||
id: boardId,
|
||||
name: "test",
|
||||
});
|
||||
await db.insert(boardUserPermissions).values({
|
||||
boardId,
|
||||
permission,
|
||||
userId: session.user.id,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await callback(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
test.each([["modify" as const], ["full" as const]])(
|
||||
"should return true for authenticated users that have %s permissions through group on at least one board",
|
||||
async (permission) => {
|
||||
// Arrange
|
||||
const { callback, db } = setupAccessCheck();
|
||||
const session = createSession();
|
||||
await db.insert(users).values({
|
||||
id: session.user.id,
|
||||
});
|
||||
const boardId = createId();
|
||||
await db.insert(boards).values({
|
||||
id: boardId,
|
||||
name: "test",
|
||||
});
|
||||
const groupId = createId();
|
||||
await db.insert(groups).values({
|
||||
id: groupId,
|
||||
name: "test-group",
|
||||
position: 0,
|
||||
});
|
||||
await db.insert(groupMembers).values({
|
||||
groupId,
|
||||
userId: session.user.id,
|
||||
});
|
||||
await db.insert(boardGroupPermissions).values({
|
||||
boardId,
|
||||
groupId,
|
||||
permission,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await callback(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("restrictUrlsAsync", () => {
|
||||
test("should return empty array if there are no rss feed items in the database", async () => {
|
||||
// Arrange
|
||||
const { callback } = await setupUrlRestrictionAsync([]);
|
||||
|
||||
// Act
|
||||
const result = await callback(["http://example.com/feed"]);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test("should return only the URLs that are present in the database", async () => {
|
||||
// Arrange
|
||||
const { callback } = await setupUrlRestrictionAsync(["http://example.com/feed"]);
|
||||
|
||||
// Act
|
||||
const result = await callback(["http://example.com/feed", "http://not-in-db.com/feed"]);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(["http://example.com/feed"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupAccessCheck() {
|
||||
const db = createDb();
|
||||
|
||||
return {
|
||||
callback: (session: Session | null) => canAccessAllFeedsAsync(db, session),
|
||||
db,
|
||||
};
|
||||
}
|
||||
|
||||
async function setupUrlRestrictionAsync(dbUrls: string[]) {
|
||||
const db = createDb();
|
||||
const boardId = createId();
|
||||
await db.insert(boards).values({
|
||||
id: boardId,
|
||||
name: "test",
|
||||
});
|
||||
if (dbUrls.length >= 1) {
|
||||
await db.insert(items).values(
|
||||
dbUrls.map(
|
||||
(url) =>
|
||||
({
|
||||
id: createId(),
|
||||
kind: "rssFeed",
|
||||
boardId,
|
||||
options: SuperJSON.stringify({
|
||||
feedUrls: [url],
|
||||
enableRtl: false,
|
||||
hideDescription: false,
|
||||
textLinesClamp: 3,
|
||||
maximumAmountPosts: 5,
|
||||
} satisfies WidgetComponentProps<"rssFeed">["options"]),
|
||||
}) satisfies InferInsertModel<typeof items>,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
callback: (urls: string[]) => restrictUrlsAsync(db, urls),
|
||||
db,
|
||||
};
|
||||
}
|
||||
@@ -1,43 +1,97 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
import { AppRepository } from "../app";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
ping: publicProcedure.input(z.object({ url: z.string() })).query(async ({ input }) => {
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
ping: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
if (boardSettings.forceDisableStatus) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Ping functionality is disabled by server settings",
|
||||
});
|
||||
}
|
||||
|
||||
const repository = new AppRepository(ctx.db, ctx.session?.user ?? null);
|
||||
const app = await repository.getByIdAsync(input.id);
|
||||
|
||||
if (!app) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "App not found",
|
||||
});
|
||||
}
|
||||
|
||||
const pingUrl = app.pingUrl ?? app.href;
|
||||
|
||||
if (!pingUrl) {
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message: "No URL to ping configured for this app",
|
||||
});
|
||||
}
|
||||
|
||||
const pingResult = await sendPingRequestAsync(pingUrl);
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
url: pingUrl,
|
||||
...pingResult,
|
||||
};
|
||||
}),
|
||||
updatedPing: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
.subscription(async ({ input }) => {
|
||||
await pingUrlChannel.addAsync(input.url);
|
||||
.subscription(async ({ ctx, input }) => {
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
if (boardSettings.forceDisableStatus) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Ping functionality is disabled by server settings",
|
||||
});
|
||||
}
|
||||
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
const repository = new AppRepository(ctx.db, ctx.session?.user ?? null);
|
||||
const app = await repository.getByIdAsync(input.id);
|
||||
|
||||
if (!app) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "App not found",
|
||||
});
|
||||
}
|
||||
|
||||
const pingUrl = app.pingUrl ?? app.href;
|
||||
|
||||
if (!pingUrl) {
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message: "No URL to ping configured for this app",
|
||||
});
|
||||
}
|
||||
|
||||
await pingUrlChannel.addAsync(pingUrl);
|
||||
|
||||
return observable<{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }>(
|
||||
(emit) => {
|
||||
emit.next({ url: input.url, ...pingResult });
|
||||
const unsubscribe = pingChannel.subscribe((message) => {
|
||||
// Only emit if same url
|
||||
if (message.url !== input.url) return;
|
||||
if (message.url !== pingUrl) return;
|
||||
emit.next(message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
void pingUrlChannel.removeAsync(input.url);
|
||||
void pingUrlChannel.removeAsync(pingUrl);
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -9,7 +9,9 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const healthMonitoringRouter = createTRPCRouter({
|
||||
getSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
|
||||
.concat(
|
||||
createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "glances", "mock"),
|
||||
)
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -26,7 +28,9 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeSystemHealthStatus: publicProcedure
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
|
||||
.concat(
|
||||
createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "glances", "mock"),
|
||||
)
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import SuperJSON from "superjson";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { eq } from "@homarr/db";
|
||||
import { items, users } from "@homarr/db/schema";
|
||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../../widgets/src";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const rssFeedRouter = createTRPCRouter({
|
||||
getFeeds: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
urls: z.array(z.string()),
|
||||
urls: z.array(z.string()).max(100),
|
||||
maximumAmountPosts: z.number(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
.query(async ({ ctx, input }) => {
|
||||
const urls = (await canAccessAllFeedsAsync(ctx.db, ctx.session))
|
||||
? input.urls
|
||||
: await restrictUrlsAsync(ctx.db, input.urls);
|
||||
|
||||
const rssFeeds = await Promise.all(
|
||||
input.urls.map(async (url) => {
|
||||
urls.map(async (url) => {
|
||||
const innerHandler = rssFeedsRequestHandler.handler({
|
||||
url,
|
||||
count: input.maximumAmountPosts,
|
||||
@@ -35,3 +45,63 @@ export const rssFeedRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
export async function canAccessAllFeedsAsync(db: Database, session: Session | null) {
|
||||
// Unauthenticated users can never access all feeds
|
||||
if (!session) return false;
|
||||
// Users with those permissions can modify a board and therefore add a rss feed widget with any feed url
|
||||
if (session.user.permissions.includes("board-create") || session.user.permissions.includes("board-modify-all")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, session.user.id),
|
||||
with: {
|
||||
boards: {
|
||||
columns: { id: true },
|
||||
},
|
||||
boardPermissions: true,
|
||||
groups: {
|
||||
with: {
|
||||
group: {
|
||||
with: { boardPermissions: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Should never happen as the user is authenticated, but just in case
|
||||
if (!user) return false;
|
||||
|
||||
// If user is owner of any board he has full access and can therefore create widgets with any feed url
|
||||
if (user.boards.length >= 1) return true;
|
||||
|
||||
// If user has direct permissions on any board that allow modifying it, he can add a rss feed widget with any feed url
|
||||
if (user.boardPermissions.some(({ permission }) => permission === "modify" || permission === "full")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If user is in a group that has permissions on any board that allow modifying it, he can add a rss feed widget with any feed url
|
||||
if (
|
||||
user.groups.some(({ group }) =>
|
||||
group.boardPermissions.some(({ permission }) => permission === "modify" || permission === "full"),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to not allowing access to all feeds
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function restrictUrlsAsync(db: Database, urls: string[]) {
|
||||
const rssFeedItems = await db.query.items.findMany({
|
||||
where: eq(items.kind, "rssFeed"),
|
||||
});
|
||||
|
||||
const configuredUrls = rssFeedItems
|
||||
.map((item) => SuperJSON.parse<WidgetComponentProps<"rssFeed">["options"]>(item.options))
|
||||
.flatMap((options) => options.feedUrls);
|
||||
return urls.filter((url) => configuredUrls.includes(url));
|
||||
}
|
||||
@@ -33,11 +33,11 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.1.3",
|
||||
"next": "16.1.3",
|
||||
"ldapts": "8.1.6",
|
||||
"next": "16.1.6",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -46,8 +46,8 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "6.0.0",
|
||||
"@types/cookies": "0.9.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.8.0",
|
||||
"eslint": "^9.39.3",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,14 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^17.2.3"
|
||||
"dotenv": "^17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"esbuild": "^0.27.3",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,13 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"@paralleldrive/cuid2": "^3.3.0",
|
||||
"cookie": "1.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"next": "16.1.3",
|
||||
"next": "16.1.6",
|
||||
"octokit": "^5.0.5",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"undici": "7.18.2",
|
||||
"zod": "^4.2.1",
|
||||
"zod-validation-error": "^5.0.0"
|
||||
@@ -41,7 +42,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { SerializeOptions } from "cookie";
|
||||
import { parse, serialize } from "cookie";
|
||||
import { parseCookie, stringifySetCookie } from "cookie";
|
||||
|
||||
export function parseCookies(cookieString: string) {
|
||||
return parse(cookieString);
|
||||
return parseCookie(cookieString);
|
||||
}
|
||||
|
||||
export function setClientCookie(name: string, value: string, options: SerializeOptions = {}) {
|
||||
document.cookie = serialize(name, value, options);
|
||||
document.cookie = stringifySetCookie(name, value, options);
|
||||
}
|
||||
@@ -41,9 +41,9 @@
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"dns-caching": "^0.2.9",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"ioredis": "5.9.2",
|
||||
"mysql2": "3.16.1",
|
||||
"pg": "^8.17.1",
|
||||
"ioredis": "5.9.3",
|
||||
"mysql2": "3.17.4",
|
||||
"pg": "^8.18.0",
|
||||
"superjson": "2.2.6",
|
||||
"winston": "3.19.0",
|
||||
"zod": "^4.2.1"
|
||||
@@ -54,7 +54,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.16.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,12 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"@trpc/tanstack-react-query": "^11.8.1",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@trpc/client": "^11.10.0",
|
||||
"@trpc/server": "^11.10.0",
|
||||
"@trpc/tanstack-react-query": "^11.10.0",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.2.3",
|
||||
"react": "19.2.4",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -41,8 +41,8 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "19.2.8",
|
||||
"eslint": "^9.39.2",
|
||||
"@types/react": "19.2.14",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,15 @@ export interface IJobManager {
|
||||
updateIntervalAsync(name: JobGroupKeys, cron: string): Promise<void>;
|
||||
disableAsync(name: JobGroupKeys): Promise<void>;
|
||||
enableAsync(name: JobGroupKeys): Promise<void>;
|
||||
getAllAsync(): Promise<{ name: JobGroupKeys; cron: string; preventManualExecution: boolean; isEnabled: boolean }[]>;
|
||||
getAllAsync(): Promise<
|
||||
{
|
||||
name: JobGroupKeys;
|
||||
cron: string;
|
||||
preventManualExecution: boolean;
|
||||
preventCustomInterval: boolean;
|
||||
isEnabled: boolean;
|
||||
}[]
|
||||
>;
|
||||
}
|
||||
|
||||
const t = initTRPC
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export interface CreateCronJobCreatorOptions<TAllowedNames extends string> {
|
||||
interface CreateCronJobOptions {
|
||||
runOnStart?: boolean;
|
||||
preventManualExecution?: boolean;
|
||||
preventCustomInterval?: boolean;
|
||||
expectedMaximumDurationInMillis?: number;
|
||||
beforeStart?: () => MaybePromise<void>;
|
||||
}
|
||||
@@ -78,14 +79,14 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
|
||||
where: (cronJobConfigurations, { eq }) => eq(cronJobConfigurations.name, name),
|
||||
});
|
||||
|
||||
const scheduledTask = createTask(
|
||||
configuration?.cronExpression ?? defaultCronExpression,
|
||||
() => void catchingCallbackAsync(),
|
||||
{
|
||||
name,
|
||||
timezone: creatorOptions.timezone,
|
||||
},
|
||||
);
|
||||
const cronExpression = options.preventCustomInterval
|
||||
? defaultCronExpression
|
||||
: (configuration?.cronExpression ?? defaultCronExpression);
|
||||
|
||||
const scheduledTask = createTask(cronExpression, () => void catchingCallbackAsync(), {
|
||||
name,
|
||||
timezone: creatorOptions.timezone,
|
||||
});
|
||||
creatorOptions.logger.logDebug("The scheduled task for cron job was created", {
|
||||
name,
|
||||
cronExpression: defaultCronExpression,
|
||||
@@ -114,6 +115,7 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
|
||||
await catchingCallbackAsync();
|
||||
},
|
||||
preventManualExecution: options.preventManualExecution ?? false,
|
||||
preventCustomInterval: options.preventCustomInterval ?? false,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { createCronJob } from "../lib";
|
||||
export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
preventManualExecution: true,
|
||||
preventCustomInterval: true,
|
||||
}).withCallback(async () => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) return;
|
||||
const analyticSetting = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
+11
-11
@@ -47,17 +47,17 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"@testcontainers/mysql": "^11.11.0",
|
||||
"@testcontainers/postgresql": "^11.11.0",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@paralleldrive/cuid2": "^3.3.0",
|
||||
"@testcontainers/mysql": "^11.12.0",
|
||||
"@testcontainers/postgresql": "^11.12.0",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"dotenv": "^17.3.1",
|
||||
"drizzle-kit": "^0.31.9",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.16.1",
|
||||
"pg": "^8.17.1",
|
||||
"mysql2": "3.17.4",
|
||||
"pg": "^8.18.0",
|
||||
"superjson": "2.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -67,9 +67,9 @@
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/pg": "^8.16.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^9.39.2",
|
||||
"prettier": "^3.8.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"eslint": "^9.39.3",
|
||||
"prettier": "^3.8.1",
|
||||
"tsx": "4.20.4",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.3.3",
|
||||
"fast-xml-parser": "^5.3.7",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"tsx": "4.20.4",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export type HomarrDocumentationPath =
|
||||
| "/blog/tags/update"
|
||||
| "/blog/tags/version"
|
||||
| "/blog/translations"
|
||||
| "/carbon-test"
|
||||
| "/search"
|
||||
| "/docs/tags"
|
||||
| "/docs/tags/active-directory"
|
||||
|
||||
@@ -73,6 +73,14 @@ export const integrationDefs = {
|
||||
category: ["downloadClient", "torrent", "miscellaneous"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/aria2"),
|
||||
},
|
||||
slskd: {
|
||||
name: "Slskd",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/slskd.svg",
|
||||
category: ["downloadClient", "miscellaneous"],
|
||||
// @ts-expect-error - docs page will be created when integration is merged
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/slskd"),
|
||||
},
|
||||
sonarr: {
|
||||
name: "Sonarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
@@ -178,6 +186,13 @@ export const integrationDefs = {
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/dash-dot"),
|
||||
},
|
||||
glances: {
|
||||
name: "Glances",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/glances.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: null,
|
||||
},
|
||||
tdarr: {
|
||||
name: "Tdarr",
|
||||
secretKinds: [[], ["apiKey"]],
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^4.0.0",
|
||||
"eslint": "^9.39.2",
|
||||
"@types/dockerode": "^4.0.1",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^8.3.12",
|
||||
"@mantine/form": "^8.3.15",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -30,15 +30,15 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"react": "19.2.3",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"react": "19.2.4",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "6.0.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@ctrl/deluge": "^7.5.0",
|
||||
"@ctrl/qbittorrent": "^9.11.0",
|
||||
"@ctrl/deluge": "^7.5.2",
|
||||
"@ctrl/qbittorrent": "^9.11.2",
|
||||
"@ctrl/transmission": "^7.4.0",
|
||||
"@gitbeaker/rest": "^43.8.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
@@ -39,13 +39,13 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@jellyfin/sdk": "^0.13.0",
|
||||
"@octokit/auth-app": "^8.1.2",
|
||||
"@octokit/auth-app": "^8.2.0",
|
||||
"ical.js": "^2.2.1",
|
||||
"maria2": "^0.4.1",
|
||||
"node-ical": "^0.22.1",
|
||||
"octokit": "^5.0.5",
|
||||
"proxmox-api": "1.1.1",
|
||||
"tsdav": "^2.1.6",
|
||||
"tsdav": "^2.1.8",
|
||||
"undici": "7.18.2",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^4.2.1"
|
||||
@@ -56,7 +56,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-unifi": "^2.5.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,13 @@ import { DelugeIntegration } from "../download-client/deluge/deluge-integration"
|
||||
import { NzbGetIntegration } from "../download-client/nzbget/nzbget-integration";
|
||||
import { QBitTorrentIntegration } from "../download-client/qbittorrent/qbittorrent-integration";
|
||||
import { SabnzbdIntegration } from "../download-client/sabnzbd/sabnzbd-integration";
|
||||
import { SlskdIntegration } from "../download-client/slskd/slskd-integration";
|
||||
import { TransmissionIntegration } from "../download-client/transmission/transmission-integration";
|
||||
import { EmbyIntegration } from "../emby/emby-integration";
|
||||
import { GitHubContainerRegistryIntegration } from "../github-container-registry/github-container-registry-integration";
|
||||
import { GithubIntegration } from "../github/github-integration";
|
||||
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
||||
import { GlancesIntegration } from "../glances/glances-integration";
|
||||
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
||||
import { ICalIntegration } from "../ical/ical-integration";
|
||||
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
||||
@@ -77,6 +79,7 @@ export const integrationCreators = {
|
||||
qBittorrent: QBitTorrentIntegration,
|
||||
deluge: DelugeIntegration,
|
||||
transmission: TransmissionIntegration,
|
||||
slskd: SlskdIntegration,
|
||||
aria2: Aria2Integration,
|
||||
jellyseerr: JellyseerrIntegration,
|
||||
overseerr: OverseerrIntegration,
|
||||
@@ -105,6 +108,7 @@ export const integrationCreators = {
|
||||
truenas: TrueNasIntegration,
|
||||
unraid: UnraidIntegration,
|
||||
coolify: CoolifyIntegration,
|
||||
glances: GlancesIntegration,
|
||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||
|
||||
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { RequestInit } from "undici";
|
||||
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
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 { DownloadClientJobsAndStatus } from "../../interfaces/downloads/download-client-data";
|
||||
import type { IDownloadClientIntegration } from "../../interfaces/downloads/download-client-integration";
|
||||
import type { DownloadClientItem } from "../../interfaces/downloads/download-client-items";
|
||||
|
||||
interface SlskdUserAndDownloads {
|
||||
directories: {
|
||||
files: {
|
||||
id: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
percentComplete: number;
|
||||
averageSpeed: number;
|
||||
/**
|
||||
* Time in hh:mm:ss format.
|
||||
* Optional, undefined when aborted or errored.
|
||||
*/
|
||||
remainingTime?: string;
|
||||
|
||||
/**
|
||||
* A bit-wise combination of the state enums.
|
||||
*/
|
||||
state: string;
|
||||
|
||||
/**
|
||||
* ISO date when the file was requested at.
|
||||
* "enqueued" and "started" are different things.
|
||||
*/
|
||||
requestedAt: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
const logger = createLogger({ module: "slskd-integration" });
|
||||
|
||||
/**
|
||||
* Implementation of the Slskd application for Soulseek https://github.com/slskd/slskd
|
||||
* Slskd integration cannot pause or stop downloads, it can only list downloads.
|
||||
*/
|
||||
export class SlskdIntegration extends Integration implements IDownloadClientIntegration {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
public pauseQueueAsync(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
public pauseItemAsync(_: DownloadClientItem): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
public resumeQueueAsync(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
public resumeItemAsync(_: DownloadClientItem): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax,id-length
|
||||
public deleteItemAsync(_: DownloadClientItem, _2: boolean): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v0/server"), {
|
||||
headers: {
|
||||
"X-API-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult({
|
||||
status: response.status,
|
||||
url: response.url,
|
||||
});
|
||||
}
|
||||
|
||||
await response.json();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getClientJobsAndStatusAsync(input: { limit: number }): Promise<DownloadClientJobsAndStatus> {
|
||||
const downloads = await this.getDownloadsAsync();
|
||||
const flatFiles = downloads.flatMap((user) => user.directories).flatMap((directory) => directory.files);
|
||||
|
||||
return {
|
||||
status: {
|
||||
paused: false, // slskd cannot be paused
|
||||
rates: {
|
||||
down: flatFiles.reduce((acc, file) => acc + file.averageSpeed, 0),
|
||||
up: 0, // slskd does not upload except sharing which isn't what we want to show in downloads
|
||||
},
|
||||
types: ["miscellaneous"],
|
||||
},
|
||||
items: flatFiles.slice(0, input.limit).map((file, index) => {
|
||||
const remainingTimeParts = file.remainingTime ? file.remainingTime.split(":") : null;
|
||||
// The file name returned from API includes the full remote path, which is not relevant for Homarr.
|
||||
// We remove everything before the last slash to beautify the file name.
|
||||
const actualFileName = file.filename.split("\\").at(-1);
|
||||
|
||||
return {
|
||||
id: file.id,
|
||||
name: actualFileName ?? file.filename,
|
||||
size: file.size,
|
||||
time: remainingTimeParts
|
||||
? dayjs
|
||||
.duration({
|
||||
hours: Number(remainingTimeParts[0]),
|
||||
minutes: Number(remainingTimeParts[1]),
|
||||
seconds: Number(remainingTimeParts[2]),
|
||||
})
|
||||
.as("milliseconds")
|
||||
: 0,
|
||||
progress: file.percentComplete / 100,
|
||||
type: "miscellaneous",
|
||||
state: this.getDownloadState(file),
|
||||
added: dayjs(file.requestedAt).unix() * 1000,
|
||||
index,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private async getDownloadsAsync(): Promise<SlskdUserAndDownloads[]> {
|
||||
const response = await this.makeRequestAsync("/api/v0/transfers/downloads", { method: "GET" });
|
||||
return response as SlskdUserAndDownloads[];
|
||||
}
|
||||
|
||||
private async makeRequestAsync(
|
||||
path: `/${string}`,
|
||||
options: RequestInit & { timeout?: number } = {},
|
||||
): Promise<unknown> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||
...options,
|
||||
headers: {
|
||||
"X-API-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ResponseError(response);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType?.includes("application/json")) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
private getDownloadState(
|
||||
item: SlskdUserAndDownloads["directories"][number]["files"][number],
|
||||
): DownloadClientItem["state"] {
|
||||
try {
|
||||
const mappedStates = parseTransferStateArray(item.state);
|
||||
|
||||
if (mappedStates.includes(TransferState.InProgress)) {
|
||||
return "downloading";
|
||||
}
|
||||
|
||||
if (
|
||||
mappedStates.includes(TransferState.Errored) ||
|
||||
mappedStates.includes(TransferState.Aborted) ||
|
||||
mappedStates.includes(TransferState.TimedOut) ||
|
||||
mappedStates.includes(TransferState.Rejected) ||
|
||||
mappedStates.includes(TransferState.Cancelled)
|
||||
) {
|
||||
return "failed";
|
||||
}
|
||||
|
||||
if (mappedStates.includes(TransferState.Queued) || mappedStates.includes(TransferState.Initializing)) {
|
||||
return "processing";
|
||||
}
|
||||
|
||||
// "completed" must come last, since above enums are bit-wise combined even if they failed
|
||||
if (mappedStates.includes(TransferState.Completed) || mappedStates.includes(TransferState.Succeeded)) {
|
||||
return "completed";
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Unable to map the SLSKD transfer state enum '${mappedStates.join(",")}'. Will fall back to 'unknown'`,
|
||||
);
|
||||
|
||||
return "unknown";
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
new ErrorWithMetadata(
|
||||
`Unable to map the SLSKD transfer states '${item.state}' due to an exception in the translation. Will fall back to 'unknown'. This is likely a bug.`,
|
||||
undefined,
|
||||
{
|
||||
cause: error,
|
||||
},
|
||||
),
|
||||
);
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of the transfer states of SLSKD, copied from the following file:
|
||||
* https://github.com/slskd/slskd/blob/86dc6072a28876abfdeb1f5dbb7dfc99ea8e1805/src/slskd/Core/Data/Migrations/Z04012025_TransferStateMigration.cs#L217-L234
|
||||
*/
|
||||
export enum TransferState {
|
||||
None,
|
||||
Requested,
|
||||
Queued,
|
||||
Initializing,
|
||||
InProgress,
|
||||
Completed,
|
||||
Succeeded,
|
||||
Cancelled,
|
||||
TimedOut,
|
||||
Errored,
|
||||
Rejected,
|
||||
Aborted,
|
||||
}
|
||||
|
||||
export function parseTransferStateArray(csv: string, separator = ","): TransferState[] {
|
||||
const parts = csv
|
||||
.split(separator)
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// @ts-expect-error use string to index
|
||||
return parts.map((part) => TransferState[part] as TransferState);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { SessionStore } from "../base/session-store";
|
||||
import { createSessionStore } from "../base/session-store";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration";
|
||||
import type { SystemHealthMonitoring } from "../types";
|
||||
|
||||
export class GlancesIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
private readonly sessionStore: SessionStore<{ version: string }>;
|
||||
|
||||
constructor(integration: IntegrationInput) {
|
||||
super(integration);
|
||||
this.sessionStore = createSessionStore(integration);
|
||||
}
|
||||
|
||||
public async getSystemInfoAsync(): Promise<SystemHealthMonitoring> {
|
||||
let session = await this.sessionStore.getAsync();
|
||||
|
||||
if (session == null) {
|
||||
await this.sessionStore.setAsync({
|
||||
version: await this.getGlancesVersionAsync(),
|
||||
});
|
||||
|
||||
// update the store to re-use the values below.
|
||||
session = await this.sessionStore.getAsync();
|
||||
}
|
||||
|
||||
if (session == null) throw new Error("Session was unexpectitly null");
|
||||
|
||||
const stats = await this.getAllStatsAsync();
|
||||
|
||||
return {
|
||||
cpuUtilization: stats.cpu.total,
|
||||
memUsedInBytes: stats.mem.used,
|
||||
memAvailableInBytes: stats.mem.total - stats.mem.used,
|
||||
network: {
|
||||
down: stats.network.reduce((acc, net) => net.bytes_recv_rate_per_sec + acc, 0),
|
||||
up: stats.network.reduce((acc, net) => net.bytes_sent_rate_per_sec + acc, 0),
|
||||
},
|
||||
availablePkgUpdates: 0,
|
||||
version: session.version,
|
||||
fileSystem: stats.fs.map((fileSystem) => ({
|
||||
deviceName: fileSystem.device_name,
|
||||
used: `${fileSystem.used}`,
|
||||
available: `${fileSystem.free}`,
|
||||
percentage: fileSystem.percent,
|
||||
})),
|
||||
uptime: stats.uptime.as("seconds"),
|
||||
rebootRequired: false,
|
||||
cpuModelName: stats.quicklook.cpu_name,
|
||||
loadAverage: null,
|
||||
smart: [],
|
||||
cpuTemp: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async getGlancesVersionAsync() {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/4/version"));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ResponseError(response);
|
||||
}
|
||||
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
private async getAllStatsAsync() {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/4/all"));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ResponseError(response);
|
||||
}
|
||||
|
||||
return allSchema.parseAsync(await response.json());
|
||||
}
|
||||
|
||||
protected async testingAsync(_: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/4/status"), {
|
||||
headers: { "User-Agent": "Homarr" },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
const regex = /^(?:(?<days>\d+) days, )?(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)$/m;
|
||||
|
||||
const allSchema = z.object({
|
||||
cpu: z.object({
|
||||
total: z.number().min(0).max(100),
|
||||
}),
|
||||
mem: z.object({
|
||||
total: z.number().min(0),
|
||||
used: z.number().min(0),
|
||||
free: z.number().min(0),
|
||||
}),
|
||||
network: z.array(
|
||||
z.object({
|
||||
bytes_sent_rate_per_sec: z.number().min(0),
|
||||
bytes_recv_rate_per_sec: z.number().min(0),
|
||||
}),
|
||||
),
|
||||
fs: z.array(
|
||||
z.object({
|
||||
device_name: z.string(),
|
||||
used: z.number().min(0),
|
||||
free: z.number().min(0),
|
||||
percent: z.number().min(0).max(100),
|
||||
}),
|
||||
),
|
||||
uptime: z
|
||||
.string()
|
||||
.regex(new RegExp(regex))
|
||||
.transform((uptime) => {
|
||||
const match = regex.exec(uptime);
|
||||
if (!match?.groups) {
|
||||
throw new Error(`Unable to parse uptime value '${uptime}' with regex.`);
|
||||
}
|
||||
|
||||
const days = "days" in match.groups ? Number(match.groups.days) : undefined;
|
||||
const hours = "hours" in match.groups ? Number(match.groups.hours) : undefined;
|
||||
const minutes = "minutes" in match.groups ? Number(match.groups.minutes) : undefined;
|
||||
const seconds = "seconds" in match.groups ? Number(match.groups.seconds) : undefined;
|
||||
|
||||
return dayjs.duration({ days, hours, minutes, seconds });
|
||||
}),
|
||||
quicklook: z.object({
|
||||
cpu_name: z.string(),
|
||||
}),
|
||||
});
|
||||
@@ -5,6 +5,7 @@ export { DelugeIntegration } from "./download-client/deluge/deluge-integration";
|
||||
export { NzbGetIntegration } from "./download-client/nzbget/nzbget-integration";
|
||||
export { QBitTorrentIntegration } from "./download-client/qbittorrent/qbittorrent-integration";
|
||||
export { SabnzbdIntegration } from "./download-client/sabnzbd/sabnzbd-integration";
|
||||
export { SlskdIntegration } from "./download-client/slskd/slskd-integration";
|
||||
export { TransmissionIntegration } from "./download-client/transmission/transmission-integration";
|
||||
export { HomeAssistantIntegration } from "./homeassistant/homeassistant-integration";
|
||||
export { JellyfinIntegration } from "./jellyfin/jellyfin-integration";
|
||||
@@ -16,6 +17,7 @@ export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration";
|
||||
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
|
||||
export { NTFYIntegration } from "./ntfy/ntfy-integration";
|
||||
export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration";
|
||||
export { GlancesIntegration } from "./glances/glances-integration";
|
||||
export { OverseerrIntegration } from "./overseerr/overseerr-integration";
|
||||
export { PiHoleIntegrationV5 } from "./pi-hole/v5/pi-hole-integration-v5";
|
||||
export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
|
||||
|
||||
@@ -115,9 +115,8 @@ export class OverseerrIntegration
|
||||
}
|
||||
|
||||
public async getRequestsAsync(): Promise<MediaRequest[]> {
|
||||
//Ensure to get all pending request first
|
||||
const pendingRequests = await fetchWithTrustedCertificatesAsync(
|
||||
this.url("/api/v1/request", { take: -1, filter: "pending" }),
|
||||
this.url("/api/v1/request", { take: 20, filter: "pending" }),
|
||||
{
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
@@ -125,7 +124,6 @@ export class OverseerrIntegration
|
||||
},
|
||||
);
|
||||
|
||||
//Change 20 to integration setting (set to -1 for all)
|
||||
const allRequests = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/request", { take: 20 }), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
@@ -223,7 +221,7 @@ export class OverseerrIntegration
|
||||
}
|
||||
|
||||
public async getUsersAsync(): Promise<RequestUser[]> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/user", { take: -1 }), {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/user", { take: 10, sort: "requests" }), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
|
||||
@@ -33,19 +33,19 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@tabler/icons-react": "^3.36.1",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@tabler/icons-react": "^3.37.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"next": "16.1.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,15 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@mantine/hooks": "^8.3.12",
|
||||
"react": "19.2.3"
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"react": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^8.3.12",
|
||||
"@tabler/icons-react": "^3.36.1"
|
||||
"@mantine/notifications": "^8.3.15",
|
||||
"@tabler/icons-react": "^3.37.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@mantine/hooks": "^8.3.12",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "16.1.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"superjson": "2.2.6",
|
||||
"zod": "^4.2.1",
|
||||
"zod-form-data": "^3.0.1"
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/adm-zip": "0.5.7",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,14 @@
|
||||
"@homarr/core": "workspace:^",
|
||||
"@homarr/db": "workspace:^",
|
||||
"@homarr/definitions": "workspace:^",
|
||||
"ioredis": "5.9.2",
|
||||
"ioredis": "5.9.3",
|
||||
"superjson": "2.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ const getFirstMediaProperty = (feedObject: object) => {
|
||||
* We extend the feed with custom properties.
|
||||
* This interface adds properties on top of the default ones.
|
||||
*/
|
||||
interface ExtendedFeedEntry extends FeedEntry {
|
||||
export interface ExtendedFeedEntry extends FeedEntry {
|
||||
enclosure?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,16 @@
|
||||
"dependencies": {
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^8.3.12",
|
||||
"next": "16.1.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"@mantine/dates": "^8.3.15",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -33,21 +33,21 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.12",
|
||||
"@mantine/hooks": "^8.3.12",
|
||||
"@mantine/spotlight": "^8.3.12",
|
||||
"@tabler/icons-react": "^3.36.1",
|
||||
"jotai": "^2.16.2",
|
||||
"next": "16.1.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"@mantine/core": "^8.3.15",
|
||||
"@mantine/hooks": "^8.3.15",
|
||||
"@mantine/spotlight": "^8.3.15",
|
||||
"@tabler/icons-react": "^3.37.1",
|
||||
"jotai": "^2.18.0",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -32,16 +32,16 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.1.3",
|
||||
"next-intl": "4.7.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"next": "16.1.6",
|
||||
"next-intl": "4.8.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint": "^9.39.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "",
|
||||
"noResults": "",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "危险",
|
||||
"noResults": "未找到结果",
|
||||
"unsavedChanges": "你有未保存的更改 !",
|
||||
"servers": "服务",
|
||||
"applications": "应用",
|
||||
"services": "服务",
|
||||
"preview": {
|
||||
"show": "显示预览",
|
||||
"hide": "隐藏预览"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "获取容器统计信息失败"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "您的 Coolify 实例及其服务器和应用一览",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "显示服务"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "显示应用程序"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "显示服务"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "未找到服务",
|
||||
"applications": "未找到应用程序",
|
||||
"services": "未找到服务"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "已更新 {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "未配置 Coolify 集成",
|
||||
"internalServerError": "获取 Coolify 数据失败"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "编译"
|
||||
},
|
||||
"instances": {
|
||||
"title": "实例"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "城市/邮编",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "上传",
|
||||
"down": "下载"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "系统磁盘",
|
||||
"description": "您系统的磁盘使用",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "如果可用则显示温度"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "显示模式",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "百分比 (例如76%)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "绝对值 (例如,800GB / 1TB)"
|
||||
},
|
||||
"free": {
|
||||
"label": "空闲空间百分比(例如24%空闲)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "显示背景使用栏"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "异常"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -3240,7 +3305,7 @@
|
||||
"title": "分析",
|
||||
"general": {
|
||||
"title": "发送匿名分析",
|
||||
"text": "Homarr 将使用开源软件 Umami 发送匿名分析。它从不收集任何个人信息,因此完全符合 GDPR 和 CCPA 要求。我们鼓励您启用分析,因为它可以帮助我们的开源团队识别问题并确定待办事项的优先级。"
|
||||
"text": "Homarr 将使用开源软件 Umami 发送匿名分析数据。启用后,我们会收集:Homarr 版本、数据库类型(MySQL/PostgreSQL/SQLite)、Docker/Kubernetes 状态、已启用的身份验证方法以及各项功能的数量。所有数据均为匿名且汇总收集——绝不收集任何个人信息、URL、名称或可识别数据。这有助于我们的开源团队了解使用模式并确定开发优先级。"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "组件数据",
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "crwdns610:0crwdne610:0",
|
||||
"noResults": "crwdns612:0crwdne612:0",
|
||||
"unsavedChanges": "crwdns2784:0crwdne2784:0",
|
||||
"servers": "crwdns3846:0crwdne3846:0",
|
||||
"applications": "crwdns3848:0crwdne3848:0",
|
||||
"services": "crwdns3850:0crwdne3850:0",
|
||||
"preview": {
|
||||
"show": "crwdns614:0crwdne614:0",
|
||||
"hide": "crwdns616:0crwdne616:0"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "crwdns3428:0crwdne3428:0"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "crwdns3852:0crwdne3852:0",
|
||||
"description": "crwdns3854:0crwdne3854:0",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "crwdns3856:0crwdne3856:0"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "crwdns3858:0crwdne3858:0"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "crwdns3860:0crwdne3860:0"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "crwdns3862:0crwdne3862:0",
|
||||
"applications": "crwdns3864:0crwdne3864:0",
|
||||
"services": "crwdns3866:0crwdne3866:0"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "crwdns3868:0{when}crwdne3868:0"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "crwdns3870:0crwdne3870:0",
|
||||
"internalServerError": "crwdns3872:0crwdne3872:0"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "crwdns3874:0crwdne3874:0"
|
||||
},
|
||||
"instances": {
|
||||
"title": "crwdns3876:0crwdne3876:0"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "crwdns1110:0crwdne1110:0",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "crwdns3610:0crwdne3610:0",
|
||||
"down": "crwdns3612:0crwdne3612:0"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "crwdns3828:0crwdne3828:0",
|
||||
"description": "crwdns3830:0crwdne3830:0",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "crwdns3832:0crwdne3832:0"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "crwdns3834:0crwdne3834:0",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "crwdns3836:0crwdne3836:0"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "crwdns3838:0crwdne3838:0"
|
||||
},
|
||||
"free": {
|
||||
"label": "crwdns3840:0crwdne3840:0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "crwdns3842:0crwdne3842:0"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "crwdns3844:0crwdne3844:0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -3240,7 +3305,7 @@
|
||||
"title": "crwdns1764:0crwdne1764:0",
|
||||
"general": {
|
||||
"title": "crwdns1766:0crwdne1766:0",
|
||||
"text": "crwdns1768:0crwdne1768:0"
|
||||
"text": "crwdns3878:0crwdne3878:0"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "crwdns1770:0crwdne1770:0",
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Nebezpečná zóna",
|
||||
"noResults": "Nebyly nalezeny žádné výsledky",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "Zobrazit náhled",
|
||||
"hide": "Skrýt náhled"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Farezone",
|
||||
"noResults": "Ingen resultater fundet",
|
||||
"unsavedChanges": "Du har ikke gemte ændringer!",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "Vis forhåndsvisning",
|
||||
"hide": "Skjul forhåndsvisning"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Kunne ikke hente containerstatistik"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "By / Postnummer",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "OP",
|
||||
"down": "NED"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Gefahrenbereich",
|
||||
"noResults": "Die Suche ergab keine Treffer",
|
||||
"unsavedChanges": "Sie haben ungespeicherte Änderungen!",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "Vorschau ansehen",
|
||||
"hide": "Vorschau ausblenden"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Stadt / Postleitzahl",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Gefahrenbereich",
|
||||
"noResults": "Die Suche ergab keine Treffer",
|
||||
"unsavedChanges": "Sie haben ungespeicherte Änderungen!",
|
||||
"servers": "Server",
|
||||
"applications": "Apps",
|
||||
"services": "Dienste",
|
||||
"preview": {
|
||||
"show": "Vorschau ansehen",
|
||||
"hide": "Vorschau ausblenden"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Fehler beim Abrufen der Container Statistiken"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "Übersicht Ihrer Coolify-Instanz mit Servern und Anwendungen",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "Server anzeigen"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "Anwendungen anzeigen"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "Dienste anzeigen"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "Keine Server gefunden",
|
||||
"applications": "Keine Anwendungen gefunden",
|
||||
"services": "Keine Dienste gefunden"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "Aktualisiert {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "Keine Coolify-Integration konfiguriert",
|
||||
"internalServerError": "Fehler beim Abrufen der Coolify-Daten"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "Version"
|
||||
},
|
||||
"instances": {
|
||||
"title": "Instanzen"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Stadt / Postleitzahl",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "OBEN",
|
||||
"down": "UNTEN"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "Systemdatenträger",
|
||||
"description": "Festplattennutzung Ihres Systems",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "Zeige Temperatur, falls verfügbar"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "Anzeigemodus",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "Prozentsatz (z.B. 76%)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "Absolute Werte (z.B. 800GB / 1TB)"
|
||||
},
|
||||
"free": {
|
||||
"label": "Prozentsatz freier Speicherplatz (z. B. 24% frei)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "Zeige Hintergrundnutzungsleiste"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "Fehlerhaft"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -3240,7 +3305,7 @@
|
||||
"title": "Nutzungsdaten",
|
||||
"general": {
|
||||
"title": "Anonymisierte Nutzungsdaten senden",
|
||||
"text": "Homarr schickt anonymisierte Nutzungsdaten mit der Open-Source-Software Umami. Es sammelt zu keinem Zeitpunkt personenbezogene Daten und ist daher vollständig DPR & CCPA konform. Wir empfehlen diese Nutzungsdaten zu aktivieren, da es unserem Open-Source-Team hilft Probleme zu identifizieren und unserem Backlog ermöglicht Priorität einzuräumen."
|
||||
"text": "Homarr sendet anonymisierte Analysen mit der Open-Source-Software Umami. Wenn aktiviert, sammeln wir die Homarr-Version, den Datenbanktyp (MySQL/PostgreSQL/SQLite), den Docker/Kubernetes Status, die Authentifizierungsmethoden und die Anzahl der einzelnen Features. Alle Daten sind anonym und aggregiert - es werden keine persönlichen Informationen, URLs, Namen oder identifizierbare Daten gesammelt. Dies hilft unserem Open-Source-Team, Nutzungsmuster zu verstehen und der Entwicklung Priorität einzuräumen."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget Daten",
|
||||
@@ -3295,10 +3360,10 @@
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"title": "Benutzer",
|
||||
"enableGravatar": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Gravatar aktivieren",
|
||||
"description": "Falls kein benutzerdefinierter Avatar gesetzt ist und eine E-Mail konfiguriert wurde, ist ein nutzen der Avatare von Libravatar/Gravatar möglich"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Επικίνδυνη Περιοχή",
|
||||
"noResults": "Δεν βρέθηκαν αποτελέσματα",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -836,19 +836,19 @@
|
||||
"title": "Request error",
|
||||
"description": {
|
||||
"connection": {
|
||||
"hostUnreachable": "",
|
||||
"networkUnreachable": "",
|
||||
"refused": "",
|
||||
"reset": ""
|
||||
"hostUnreachable": "The server could not be reached. This usually means the host is offline or unreachable from your network.",
|
||||
"networkUnreachable": "The network is unreachable. Please check your Internet connection or network configuration.",
|
||||
"refused": "The server refused the connection. It may not be running or is rejecting requests on the specified port.",
|
||||
"reset": "The connection was unexpectedly closed by the server. This can happen if the server is unstable or restarted."
|
||||
},
|
||||
"dns": {
|
||||
"notFound": "",
|
||||
"timeout": "",
|
||||
"noAnswer": ""
|
||||
"notFound": "The server address could not be found. Please check the URL for typos or invalid domain names.",
|
||||
"timeout": "DNS lookup timed out. This may be a temporary issue—please try again in a few moments.",
|
||||
"noAnswer": "The DNS server didn't return a valid response. The domain may exist but has no valid records."
|
||||
},
|
||||
"timeout": {
|
||||
"aborted": "",
|
||||
"timeout": ""
|
||||
"aborted": "The request was aborted before it could complete. This might be due to a user action or system timeout.",
|
||||
"timeout": "The request took too long to complete and was timed out. Check your network or try again later."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -920,8 +920,8 @@
|
||||
"message": "The path is incorrect. Please double-check the URL"
|
||||
},
|
||||
"tooManyRequests": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Too many requests in a given time",
|
||||
"message": "There were too many requests. You are likely being rate-limited or rejected by the target system"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -963,36 +963,36 @@
|
||||
"newLabel": "New realm"
|
||||
},
|
||||
"personalAccessToken": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Personal Access Token",
|
||||
"newLabel": "New Personal Access Token"
|
||||
},
|
||||
"topic": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Topic",
|
||||
"newLabel": "New topic"
|
||||
},
|
||||
"url": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "URL",
|
||||
"newLabel": "New URL"
|
||||
},
|
||||
"opnsenseApiKey": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "API Key (Key)",
|
||||
"newLabel": "New API Key (Key)"
|
||||
},
|
||||
"opnsenseApiSecret": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "API Key (Secret)",
|
||||
"newLabel": "New API Key (Secret)"
|
||||
},
|
||||
"githubAppId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "App Id",
|
||||
"newLabel": "New App Id"
|
||||
},
|
||||
"githubInstallationId": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Installation Id",
|
||||
"newLabel": "New Installation Id"
|
||||
},
|
||||
"privateKey": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "Private key",
|
||||
"newLabel": "New private key"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1044,14 +1044,14 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"success": "",
|
||||
"success": "Success",
|
||||
"beta": "Beta",
|
||||
"error": "Alpha",
|
||||
"action": {
|
||||
"add": "Add",
|
||||
"apply": "Apply",
|
||||
"backToOverview": "Back to overview",
|
||||
"change": "",
|
||||
"change": "Change",
|
||||
"create": "Create",
|
||||
"createAnother": "Create and start over",
|
||||
"edit": "Edit",
|
||||
@@ -1063,7 +1063,7 @@
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"discard": "Discard",
|
||||
"close": "",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"continue": "Continue",
|
||||
"previous": "Previous",
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Danger zone",
|
||||
"noResults": "No results have been found",
|
||||
"unsavedChanges": "You have unsaved changes!",
|
||||
"servers": "Servers",
|
||||
"applications": "Apps",
|
||||
"services": "Services",
|
||||
"preview": {
|
||||
"show": "Show a preview",
|
||||
"hide": "Hide the preview"
|
||||
@@ -1174,8 +1177,8 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kilometersPerHour": "",
|
||||
"milesPerHour": ""
|
||||
"kilometersPerHour": "km/h",
|
||||
"milesPerHour": "mph"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1190,7 +1193,7 @@
|
||||
"label": "Title"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": ""
|
||||
"label": "Custom CSS classes"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "Border colour"
|
||||
@@ -1223,75 +1226,75 @@
|
||||
},
|
||||
"remove": {
|
||||
"title": "Remove category",
|
||||
"message": ""
|
||||
"message": "Are you sure you want to remove the category {name}?"
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"submit": ""
|
||||
"title": "Rename category",
|
||||
"submit": "Rename category"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"create": "",
|
||||
"changePosition": ""
|
||||
"create": "New category",
|
||||
"changePosition": "Change position"
|
||||
}
|
||||
},
|
||||
"openAllInNewTabs": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
"title": "Open all in tabs",
|
||||
"text": "Some browsers may block the bulk-opening of tabs for security reasons. Homarr was unable to open all windows, because your browser blocked this action. Please allow \"Open pop-up windows\" and re-try."
|
||||
}
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"action": {
|
||||
"create": "",
|
||||
"import": "",
|
||||
"edit": "",
|
||||
"moveResize": "",
|
||||
"duplicate": "",
|
||||
"remove": ""
|
||||
"create": "New item",
|
||||
"import": "Import item",
|
||||
"edit": "Edit item",
|
||||
"moveResize": "Move / resize item",
|
||||
"duplicate": "Duplicate item",
|
||||
"remove": "Remove item"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"settings": ""
|
||||
"settings": "Settings"
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"search": "",
|
||||
"addToBoard": ""
|
||||
"title": "Choose item to add",
|
||||
"search": "Filter items",
|
||||
"addToBoard": "Add to board"
|
||||
},
|
||||
"moveResize": {
|
||||
"title": "",
|
||||
"title": "Move / resize item",
|
||||
"field": {
|
||||
"width": {
|
||||
"label": ""
|
||||
"label": "Width"
|
||||
},
|
||||
"height": {
|
||||
"label": ""
|
||||
"label": "Height"
|
||||
},
|
||||
"xOffset": {
|
||||
"label": ""
|
||||
"label": "X offset"
|
||||
},
|
||||
"yOffset": {
|
||||
"label": ""
|
||||
"label": "Y offset"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"title": "Edit item",
|
||||
"advancedOptions": {
|
||||
"label": "",
|
||||
"title": ""
|
||||
"label": "Advanced options",
|
||||
"title": "Advanced item options"
|
||||
},
|
||||
"field": {
|
||||
"integrations": {
|
||||
"label": ""
|
||||
"label": "Integrations"
|
||||
},
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "Title"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": ""
|
||||
"label": "Custom CSS classes"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "Border colour"
|
||||
@@ -1299,68 +1302,68 @@
|
||||
}
|
||||
},
|
||||
"remove": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Remove item",
|
||||
"message": "Are you sure you want to remove this item?"
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "App",
|
||||
"description": "Embeds an app into the board.",
|
||||
"option": {
|
||||
"appId": {
|
||||
"label": ""
|
||||
"label": "Choose app"
|
||||
},
|
||||
"openInNewTab": {
|
||||
"label": ""
|
||||
"label": "Open in new tab"
|
||||
},
|
||||
"showTitle": {
|
||||
"label": ""
|
||||
"label": "Show app name"
|
||||
},
|
||||
"pingEnabled": {
|
||||
"label": ""
|
||||
"label": "Enable status check"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": "",
|
||||
"row-reverse": "",
|
||||
"column": "",
|
||||
"column-reverse": ""
|
||||
"row": "Horizontal",
|
||||
"row-reverse": "Horizontal (reversed)",
|
||||
"column": "Vertical",
|
||||
"column-reverse": "Vertical (reversed)"
|
||||
}
|
||||
},
|
||||
"descriptionDisplayMode": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"label": "Description display mode",
|
||||
"description": "Choose how to display the app description",
|
||||
"option": {
|
||||
"normal": "",
|
||||
"tooltip": "",
|
||||
"hidden": ""
|
||||
"normal": "Within widget",
|
||||
"tooltip": "As tooltip",
|
||||
"hidden": "Hidden"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"notFound": {
|
||||
"label": "",
|
||||
"tooltip": ""
|
||||
"label": "No app",
|
||||
"tooltip": "You have no valid app selected"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bookmarks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Bookmarks",
|
||||
"description": "Displays multiple app links",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "Title"
|
||||
},
|
||||
"layout": {
|
||||
"label": "",
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": ""
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": ""
|
||||
"label": "Vertical"
|
||||
},
|
||||
"grid": {
|
||||
"label": ""
|
||||
@@ -1424,46 +1427,46 @@
|
||||
},
|
||||
"dnsHoleControls": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"description": "Control PiHole or AdGuard from your dashboard",
|
||||
"option": {
|
||||
"layout": {
|
||||
"label": "",
|
||||
"label": "Layout",
|
||||
"option": {
|
||||
"row": {
|
||||
"label": ""
|
||||
"label": "Horizontal"
|
||||
},
|
||||
"column": {
|
||||
"label": ""
|
||||
"label": "Vertical"
|
||||
},
|
||||
"grid": {
|
||||
"label": ""
|
||||
"label": "Grid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showToggleAllButtons": {
|
||||
"label": ""
|
||||
"label": "Show Toggle All Buttons"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
"internalServerError": "Failed to control DNS Hole"
|
||||
},
|
||||
"controls": {
|
||||
"enableAll": "",
|
||||
"disableAll": "",
|
||||
"setTimer": "",
|
||||
"set": "",
|
||||
"enabled": "",
|
||||
"disabled": "",
|
||||
"processing": "",
|
||||
"disconnected": "",
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"unlimited": ""
|
||||
"enableAll": "Enable All",
|
||||
"disableAll": "Disable All",
|
||||
"setTimer": "Set Timer",
|
||||
"set": "Set",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"processing": "Processing",
|
||||
"disconnected": "Disconnected",
|
||||
"hours": "Hours",
|
||||
"minutes": "Minutes",
|
||||
"unlimited": "Leave blank to unlimited"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Date and time",
|
||||
"description": "Displays the current date and time.",
|
||||
"option": {
|
||||
"customTitleToggle": {
|
||||
"label": "",
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -4251,52 +4316,52 @@
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"help": "",
|
||||
"help": "Search for pages",
|
||||
"group": {
|
||||
"page": {
|
||||
"title": "",
|
||||
"title": "Pages",
|
||||
"option": {
|
||||
"manageHome": {
|
||||
"label": ""
|
||||
"label": "Manage home page"
|
||||
},
|
||||
"manageBoard": {
|
||||
"label": ""
|
||||
"label": "Manage boards"
|
||||
},
|
||||
"manageApp": {
|
||||
"label": ""
|
||||
"label": "Manage apps"
|
||||
},
|
||||
"manageIntegration": {
|
||||
"label": ""
|
||||
"label": "Manage integrations"
|
||||
},
|
||||
"manageSearchEngine": {
|
||||
"label": ""
|
||||
"label": "Manage search engines"
|
||||
},
|
||||
"manageMedia": {
|
||||
"label": ""
|
||||
"label": "Manage medias"
|
||||
},
|
||||
"manageUser": {
|
||||
"label": ""
|
||||
"label": "Manage users"
|
||||
},
|
||||
"manageInvite": {
|
||||
"label": ""
|
||||
"label": "Manage invites"
|
||||
},
|
||||
"manageGroup": {
|
||||
"label": ""
|
||||
"label": "Manage groups"
|
||||
},
|
||||
"manageDocker": {
|
||||
"label": ""
|
||||
"label": "Manage docker"
|
||||
},
|
||||
"manageApi": {
|
||||
"label": ""
|
||||
"label": "Swagger API"
|
||||
},
|
||||
"manageLog": {
|
||||
"label": ""
|
||||
"label": "View logs"
|
||||
},
|
||||
"manageTask": {
|
||||
"label": ""
|
||||
"label": "Manage tasks"
|
||||
},
|
||||
"manageSettings": {
|
||||
"label": ""
|
||||
"label": "Global settings"
|
||||
},
|
||||
"about": {
|
||||
"label": ""
|
||||
@@ -4378,44 +4443,44 @@
|
||||
"title": "",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Search engine created",
|
||||
"message": "The search engine was created successfully"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Search engine not created",
|
||||
"message": "The search engine could not be created"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"title": "",
|
||||
"title": "Edit search engine",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Changes applied successfully",
|
||||
"message": "The search engine was saved successfully"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Unable to apply changes",
|
||||
"message": "The search engine could not be saved"
|
||||
}
|
||||
},
|
||||
"configControl": "",
|
||||
"configControl": "Configuration",
|
||||
"searchEngineType": {
|
||||
"generic": "",
|
||||
"fromIntegration": ""
|
||||
"generic": "Generic",
|
||||
"fromIntegration": "From integration"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"title": "",
|
||||
"message": "",
|
||||
"title": "Delete search engine",
|
||||
"message": "Are you sure you want to delete the search engine {name}?",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Search engine deleted",
|
||||
"message": "The search engine was deleted successfully"
|
||||
},
|
||||
"error": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Search engine not deleted",
|
||||
"message": "The search engine could not be deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4423,15 +4488,15 @@
|
||||
"media": {
|
||||
"request": {
|
||||
"modal": {
|
||||
"title": "",
|
||||
"title": "Request \"{name}\"",
|
||||
"table": {
|
||||
"header": {
|
||||
"season": "",
|
||||
"episodes": ""
|
||||
"season": "Season",
|
||||
"episodes": "Episodes"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"send": ""
|
||||
"send": "Send request"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4441,44 +4506,44 @@
|
||||
"certificate": {
|
||||
"field": {
|
||||
"hostname": {
|
||||
"label": ""
|
||||
"label": "Hostname"
|
||||
},
|
||||
"subject": {
|
||||
"label": ""
|
||||
"label": "Subject"
|
||||
},
|
||||
"issuer": {
|
||||
"label": ""
|
||||
"label": "Issuer"
|
||||
},
|
||||
"validFrom": {
|
||||
"label": ""
|
||||
"label": "Valid from"
|
||||
},
|
||||
"validTo": {
|
||||
"label": ""
|
||||
"label": "Valid to"
|
||||
},
|
||||
"serialNumber": {
|
||||
"label": ""
|
||||
"label": "Serial number"
|
||||
},
|
||||
"fingerprint": {
|
||||
"label": ""
|
||||
"label": "Fingerprint"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Trusted certificates",
|
||||
"description": "Used by Homarr to request data from integrations.",
|
||||
"noResults": {
|
||||
"title": ""
|
||||
"title": "There are no certificates yet"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Invalid certificate",
|
||||
"description": "Failed to parse certificate"
|
||||
},
|
||||
"expires": "",
|
||||
"toHostnames": ""
|
||||
"expires": "Expires {when}",
|
||||
"toHostnames": "Trusted hostnames"
|
||||
},
|
||||
"hostnames": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"title": "Trusted certificate hostnames",
|
||||
"description": "Some certificates do not allow the specific domain Homarr uses to request them, because of this all trusted hostnames with their certificate thumbprints are used to bypass these restrictions.",
|
||||
"noResults": {
|
||||
"title": "There are no hostnames yet"
|
||||
},
|
||||
|
||||
@@ -3305,7 +3305,7 @@
|
||||
"title": "Analytics",
|
||||
"general": {
|
||||
"title": "Send anonymous analytics",
|
||||
"text": "Homarr will send anonymized analytics using the open source software Umami. It never collects any personal information and is therefore fully GDPR & CCPA compliant. We encourage you to enable analytics because it helps our open source team to identify issues and prioritize our backlog."
|
||||
"text": "Homarr will send anonymized analytics using the open source software Umami. When enabled, we collect: Homarr version, database type (MySQL/PostgreSQL/SQLite), Docker/Kubernetes status, authentication methods enabled, and the number of each feature. All data is anonymous and aggregated - no personal information, URLs, names, or identifiable data is ever collected. This helps our open source team understand usage patterns and prioritize development."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget data",
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Zona de riesgo",
|
||||
"noResults": "No se han encontrado resultados",
|
||||
"unsavedChanges": "¡Tienes cambios sin guardar!",
|
||||
"servers": "Servidores",
|
||||
"applications": "Aplicaciones",
|
||||
"services": "Servicios",
|
||||
"preview": {
|
||||
"show": "Mostrar vista previa",
|
||||
"hide": "Ocultar vista previa"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "No se pudo obtener las estadísticas de los contenedores"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "Resumen de su instancia de Coolify con servidores y aplicaciones",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "Mostrar servidores"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "Mostrar aplicaciones"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "Mostrar servicios"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "No se encontraron servidores",
|
||||
"applications": "No se encontraron aplicaciones",
|
||||
"services": "No se encontraron servicios"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "Actualizado {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "No hay integración de Coolify configurada",
|
||||
"internalServerError": "No se pudieron obtener los datos de Coolify"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "Construir"
|
||||
},
|
||||
"instances": {
|
||||
"title": "Instancias"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Ciudad / Código postal",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "ENCENDIDO",
|
||||
"down": "APAGADO"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "Discos del sistema",
|
||||
"description": "Uso del disco de su sistema",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "Mostrar la temperatura si está disponible"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "Modo de visualización",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "Porcentaje (e.g., 76 %)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "Valores absolutos (e.g., 800 GB / 1 TB)"
|
||||
},
|
||||
"free": {
|
||||
"label": "Porcentaje de espacio libre (e.g., 24 % libre)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "Mostrar la barra de uso en segundo plano"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "Poco sano"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "",
|
||||
"noResults": "",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Zone de danger",
|
||||
"noResults": "Aucun résultat trouvé",
|
||||
"unsavedChanges": "Vous avez des modifications non enregistrées !",
|
||||
"servers": "Serveurs",
|
||||
"applications": "Applications",
|
||||
"services": "Services",
|
||||
"preview": {
|
||||
"show": "Afficher l’aperçu",
|
||||
"hide": "Masquer l'aperçu"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Impossible de récupérer les statistiques des conteneurs"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "Aperçu de votre instance Coolify avec les serveurs et les applications",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "Afficher les serveurs"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "Afficher les applications"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "Afficher les services"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "Aucun serveur trouvé",
|
||||
"applications": "Aucune application trouvée",
|
||||
"services": "Aucun service trouvé"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "Mis à jour {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "Aucune intégration de Coolify configurée",
|
||||
"internalServerError": "Impossible de récupérer les données de Coolify"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "Version"
|
||||
},
|
||||
"instances": {
|
||||
"title": "Instances"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Ville / Code Postal",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "UP",
|
||||
"down": "HORS SERVICE"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "Disques système",
|
||||
"description": "Utilisation du disque de votre système",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "Afficher la température si disponible"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "Mode d'affichage",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "Pourcentage (ex : 70%)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "Valeurs absolues (ex. : 800 Go / 1 To)"
|
||||
},
|
||||
"free": {
|
||||
"label": "Pourcentage d'espace libre (ex. : 24 % libre)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "Afficher la barre d'utilisation en arrière-plan"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "Dysfonctionnement"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "אזור מסוכן",
|
||||
"noResults": "לא נמצאו תוצאות",
|
||||
"unsavedChanges": "ישנם שינויים שלא נשמרו!",
|
||||
"servers": "שרתים",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "הצג תצוגה מקדימה",
|
||||
"hide": "הסתר תצוגה המקדימה"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "נכשל באחזור סטטיסטיקות המכולות"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "עיר / מיקוד",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "העלאה",
|
||||
"down": "הורדה"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -3295,7 +3360,7 @@
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "",
|
||||
"title": "משתמשים",
|
||||
"enableGravatar": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Opasna zona",
|
||||
"noResults": "Nije pronađen nijedan rezultat",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Veszélyzóna",
|
||||
"noResults": "Nincs eredmény",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Zona pericolosa",
|
||||
"noResults": "Nessun risultato trovato",
|
||||
"unsavedChanges": "Ci sono modifiche non salvate!",
|
||||
"servers": "Server",
|
||||
"applications": "App",
|
||||
"services": "Servizi",
|
||||
"preview": {
|
||||
"show": "Mostra anteprima",
|
||||
"hide": "Nascondi anteprima"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Recupero statistiche container non riuscito"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "Panoramica della tua istanza Coolify con server e applicazioni",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "Mostra server"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "Mostra applicazioni"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "Mostra servizi"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "Nessun server trovato",
|
||||
"applications": "Nessuna applicazione trovata",
|
||||
"services": "Nessun servizio trovato"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "Aggiornato {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "Nessuna integrazione di Coolify configurata",
|
||||
"internalServerError": "Impossibile recuperare i dati Coolify"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "Build"
|
||||
},
|
||||
"instances": {
|
||||
"title": "Istanze"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Città / Codice postale",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "SU",
|
||||
"down": "DOWN"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "Dischi di sistema",
|
||||
"description": "Uso del disco del sistema",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "Mostra temperatura se disponibile"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "Modalità di visualizzazione",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "Percentuale (es. 76%)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "Valori assoluti (ad es. 800GB / 1TB)"
|
||||
},
|
||||
"free": {
|
||||
"label": "Percentuale di spazio libero (ad es. 24% libero)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "Mostra barra di utilizzo in background"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "Compromesso"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "危険な操作",
|
||||
"noResults": "結果が見つかりません",
|
||||
"unsavedChanges": "保存していない変更があります!",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "プレビューを表示",
|
||||
"hide": "プレビューを閉じる"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "コンテナステータスの取得に失敗しました"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "都市/郵便番号",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "UP",
|
||||
"down": "DOWN"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "위험한 설정",
|
||||
"noResults": "결과를 찾을 수 없습니다.",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "",
|
||||
"noResults": "Rezultatų nerasta",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Bīstamā zona",
|
||||
"noResults": "Nav atrasts neviens rezultāts",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Gevarenzone",
|
||||
"noResults": "Geen resultaten gevonden",
|
||||
"unsavedChanges": "Er zijn niet-opgeslagen wijzigingen!",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "Voorbeeld weergeven",
|
||||
"hide": "Voorbeeld verbergen"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Stad / postcode",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "OMHOOG",
|
||||
"down": "OMLAAG"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Faresonen",
|
||||
"noResults": "Ingen resultater funnet",
|
||||
"unsavedChanges": "Du har ulagrede endringer!",
|
||||
"servers": "Servere",
|
||||
"applications": "Apper",
|
||||
"services": "Tjenester",
|
||||
"preview": {
|
||||
"show": "Vis forhåndsvisning",
|
||||
"hide": "Skjul forhåndsvisning"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Kunne ikke hente containerstatistikk"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "Coolify",
|
||||
"description": "Oversikt over din Coolify-instans med servere og apper",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": "Vis servere"
|
||||
},
|
||||
"showApplications": {
|
||||
"label": "Vis applikasjoner"
|
||||
},
|
||||
"showServices": {
|
||||
"label": "Vis tjenester"
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "Ingen server funnet",
|
||||
"applications": "Ingen applikasjoner funnet",
|
||||
"services": "Ingen tjenester funnet"
|
||||
},
|
||||
"footer": {
|
||||
"updated": "Oppdatert {when}"
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "Ingen Coolify integrasjon konfigurert",
|
||||
"internalServerError": "Kunne ikke hente Coolify data"
|
||||
},
|
||||
"server": {
|
||||
"buildServer": "Bygg"
|
||||
},
|
||||
"instances": {
|
||||
"title": "Instanser"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "By / Postnummer",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "OPP",
|
||||
"down": "NED"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "Systemdisker",
|
||||
"description": "Disk bruk av systemet",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": "Vis temperatur hvis tilgjengelig"
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "Visningsmodus",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": "Prosentandel (f.eks. 76%)"
|
||||
},
|
||||
"absolute": {
|
||||
"label": "Absolutte verdier (f.eks. 800GB / 1TB)"
|
||||
},
|
||||
"free": {
|
||||
"label": "Prosentandel ledig plass (f.eks. 24% ledig)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": "Vis bakgrunnsbrukslinje"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": "Usunn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
@@ -3240,7 +3305,7 @@
|
||||
"title": "Statistikk",
|
||||
"general": {
|
||||
"title": "Send anonym brukerdata",
|
||||
"text": "Homarr vil sende anonymt brukerdata med åpen kildekode programvaren Umami. Det vil aldri samle inn personlig informasjon og følger derfor GDPR & CCPA fullstendig. Vi oppfordrer deg til å aktivere sending av brukerdata da det hjelper teamet vårt å identifisere problemer og sette prioritering."
|
||||
"text": "Homarr vil sende anonymiserte analyser ved bruk av den åpne programvaren Umami. Når dette er aktivert, samler vi inn: Homarr-versjon, databastype (MySQL/PostgreSQL/SQLite), Docker/Kubernetes-status, aktiverte autentiseringsmetoder, og antallet av hver funksjon. Alle data er anonyme og aggregerte – ingen personlig informasjon, URL-er, navn eller identifiserbare data samles inn. Dette hjelper vårt open source-team med å forstå bruks‑mønstre og prioritere utvikling."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget data",
|
||||
|
||||
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Strefa zagrożenia",
|
||||
"noResults": "Nie znaleziono żadnych wyników",
|
||||
"unsavedChanges": "Masz niezapisane zmiany!",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "Pokaż podgląd",
|
||||
"hide": "Ukryj podgląd"
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": "Nie udało się pobrać statystyk kontenerów"
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "Miasto / kod pocztowy",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "Aktywny",
|
||||
"down": "Nieaktywny"
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
+1462
-1397
File diff suppressed because it is too large.
Load diff
@@ -1138,6 +1138,9 @@
|
||||
"dangerZone": "Zonă periculoasă",
|
||||
"noResults": "Nici un rezultat găsit",
|
||||
"unsavedChanges": "",
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": "",
|
||||
"preview": {
|
||||
"show": "",
|
||||
"hide": ""
|
||||
@@ -1960,6 +1963,39 @@
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"coolify": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showServers": {
|
||||
"label": ""
|
||||
},
|
||||
"showApplications": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"empty": {
|
||||
"servers": "",
|
||||
"applications": "",
|
||||
"services": ""
|
||||
},
|
||||
"footer": {
|
||||
"updated": ""
|
||||
},
|
||||
"error": {
|
||||
"noIntegration": "",
|
||||
"internalServerError": ""
|
||||
},
|
||||
"server": {
|
||||
"buildServer": ""
|
||||
},
|
||||
"instances": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"location": {
|
||||
"query": "",
|
||||
@@ -2608,6 +2644,35 @@
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
},
|
||||
"systemDisks": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showTemperatureIfAvailable": {
|
||||
"label": ""
|
||||
},
|
||||
"displayMode": {
|
||||
"label": "",
|
||||
"option": {
|
||||
"percentage": {
|
||||
"label": ""
|
||||
},
|
||||
"absolute": {
|
||||
"label": ""
|
||||
},
|
||||
"free": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"showBackgroundBar": {
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"unhealthy": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgetPreview": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user