Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51bbc89f52 | ||
|
|
cbd0cf3dee | ||
|
|
c3461677e8 | ||
|
|
5168cba8e4 | ||
|
|
a4aa2aea90 | ||
|
|
10feaa37ce | ||
|
|
84f1e78701 | ||
|
|
97bc092aea | ||
|
|
d9f0983f0d | ||
|
|
6847b31060 | ||
|
|
52d18dc132 | ||
|
|
5489590ebd | ||
|
|
cd8c726c52 | ||
|
|
e179b28bc0 | ||
|
|
3218cd2333 | ||
|
|
8d5d258905 | ||
|
|
d2ac99765b | ||
|
|
00d979931b | ||
|
|
6fb32a6061 | ||
|
|
1451c3c41e | ||
|
|
498acdcdc1 | ||
|
|
afdf9d9c0f | ||
|
|
f2ff90b6e2 | ||
|
|
fad7207efe | ||
|
|
683e817493 | ||
|
|
de98156d84 | ||
|
|
7685438790 | ||
|
|
1a8e40e1f9 | ||
|
|
1c32bb73bb | ||
|
|
acd09c8c02 | ||
|
|
d7b2298d14 | ||
|
|
20a2ea8968 | ||
|
|
7e700b2732 | ||
|
|
adad43f9fe | ||
|
|
950636e81d | ||
|
|
a88f1c3cba | ||
|
|
09fdb167a7 | ||
|
|
72cdba79cb | ||
|
|
82fcdae7e5 | ||
|
|
7c4ad40198 | ||
|
|
2457f9e4a7 | ||
|
|
dc43a015c4 | ||
|
|
f281b45cf8 | ||
|
|
8417ddebd7 | ||
|
|
9d14fcba36 | ||
|
|
063b9a87c7 | ||
|
|
5c99622fa8 | ||
|
|
b7455d18ed | ||
|
|
12ced491b3 | ||
|
|
98d344c4b7 | ||
|
|
f36ae8b52b | ||
|
|
eea387b092 | ||
|
|
7ffcd1a08e | ||
|
|
c72c154305 | ||
|
|
e9cb878169 | ||
|
|
70ce3e168b | ||
|
|
8a4198db7e | ||
|
|
ab5c03512e | ||
|
|
134ce6f82d | ||
|
|
99c42722d9 | ||
|
|
7d123b83e0 | ||
|
|
7a4961e9ad | ||
|
|
433be5e82c | ||
|
|
8044abd093 | ||
|
|
16efaae67e | ||
|
|
53860dda9b | ||
|
|
ff1e02ec78 | ||
|
|
5fe5d6f64d | ||
|
|
166de16bef | ||
|
|
f9506b0ce5 | ||
|
|
47226f7032 | ||
|
|
aeb338f6b1 | ||
|
|
f6b7ca1059 |
No files matched your search
@@ -34,6 +34,10 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
|||||||
# DB_PASSWORD='password'
|
# DB_PASSWORD='password'
|
||||||
# DB_NAME='name-of-database'
|
# DB_NAME='name-of-database'
|
||||||
|
|
||||||
|
# The following is an example on how to use the node-postgres driver:
|
||||||
|
# DB_DRIVER='node-postgres'
|
||||||
|
# DB_URL='postgres://user:password@host:port/database'
|
||||||
|
|
||||||
# The below path can be used to store trusted certificates, it is not required and can be left empty.
|
# The below path can be used to store trusted certificates, it is not required and can be left empty.
|
||||||
# If it is empty, it will default to `/appdata/trusted-certificates` in production.
|
# If it is empty, it will default to `/appdata/trusted-certificates` in production.
|
||||||
# If it is used, please use the full path to the directory where the certificates are stored.
|
# If it is used, please use the full path to the directory where the certificates are stored.
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ body:
|
|||||||
label: Version
|
label: Version
|
||||||
description: What version of Homarr are you running?
|
description: What version of Homarr are you running?
|
||||||
options:
|
options:
|
||||||
|
# The below comment is used to insert a new version with on-release.yml
|
||||||
|
#NEXT_VERSION#
|
||||||
|
- 1.34.0
|
||||||
|
- 1.33.0
|
||||||
|
- 1.32.0
|
||||||
- 1.31.0
|
- 1.31.0
|
||||||
- 1.30.1
|
- 1.30.1
|
||||||
- 1.30.0
|
- 1.30.0
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
matchPackagePatterns: ["^@homarr/"],
|
matchPackagePatterns: ["^@homarr/"],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
matchPackagePatterns: ["^zod$", "^drizzle-zod$", "^zod-form-data$"],
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||||
automerge: true,
|
automerge: true,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
|
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
uses: tibdex/github-app-token@v2
|
uses: tibdex/github-app-token@v2
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
format:
|
format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
typecheck:
|
typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Setup
|
- name: Setup
|
||||||
uses: ./tooling/github/setup
|
uses: ./tooling/github/setup
|
||||||
- name: Copy env
|
- name: Copy env
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ jobs:
|
|||||||
validate-pull-request-title:
|
validate-pull-request-title:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v5
|
- uses: amannn/action-semantic-pull-request@v6
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Obtain token
|
- name: Obtain token
|
||||||
id: obtainToken
|
id: obtainToken
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Upload Crowdin translations
|
- name: Upload Crowdin translations
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
digest: ${{ steps.build.outputs.digest }}
|
digest: ${{ steps.build.outputs.digest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.git_ref }}
|
ref: ${{ needs.release.outputs.git_ref }}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
digest: ${{ steps.build.outputs.digest }}
|
digest: ${{ steps.build.outputs.digest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.git_ref }}
|
ref: ${{ needs.release.outputs.git_ref }}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get Next Version
|
- name: Get Next Version
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
permissions: {}
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger-docs-release:
|
||||||
|
name: Trigger Documentation Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Obtain token
|
||||||
|
id: obtainToken
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
|
with:
|
||||||
|
private_key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||||
|
app_id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||||
|
installation_retrieval_mode: repository
|
||||||
|
installation_retrieval_payload: homarr-labs/documentation
|
||||||
|
- name: Trigger documentation release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
SOURCE_TAG: ${{ github.event.release.tag_name }}
|
||||||
|
run: |
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
https://api.github.com/repos/homarr-labs/documentation/dispatches \
|
||||||
|
-d @- <<EOF
|
||||||
|
{
|
||||||
|
"event_type": "trigger-release",
|
||||||
|
"client_payload": {
|
||||||
|
"tag": "${SOURCE_TAG}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
update-bug-report-template:
|
||||||
|
name: Update Bug Report Template
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Obtain token
|
||||||
|
id: obtainToken
|
||||||
|
uses: tibdex/github-app-token@v2
|
||||||
|
with:
|
||||||
|
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||||
|
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
- 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 add .
|
||||||
|
git commit -m "chore: update bug report template"
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
id: create-pull-request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
token: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
branch: update-bug-report-template
|
||||||
|
base: dev
|
||||||
|
title: "chore: update bug report template"
|
||||||
|
delete-branch: true
|
||||||
|
body: |
|
||||||
|
This PR automatically updates the bug report template of Homarr in the .github/ISSUE_TEMPLATE/bug_report.yml
|
||||||
|
|
||||||
|
- name: Install GitHub CLI
|
||||||
|
run: sudo apt-get install -y gh
|
||||||
|
|
||||||
|
- name: Enable auto-merge
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
run: |
|
||||||
|
gh pr merge ${{steps.create-pull-request.outputs.pull-request-number}} --auto --squash
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||||
- name: Setup
|
- name: Setup
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="db:migration:postgresql:generate" type="js.build_tools.npm">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="db:migration:postgresql:generate" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<package-manager value="pnpm" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -1,3 +1,62 @@
|
|||||||
|
## [1.35.0](https://github.com/homarr-labs/homarr/compare/v1.34.0...v1.35.0) (2025-08-29)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **app-widget:** show description in widget ([#3876](https://github.com/homarr-labs/homarr/issues/3876)) ([c346167](https://github.com/homarr-labs/homarr/commit/c3461677e82f52bd43bd3c4ecc867b4a6ae2bee8))
|
||||||
|
* **db:** support postgresql database ([#3643](https://github.com/homarr-labs/homarr/issues/3643)) ([5168cba](https://github.com/homarr-labs/homarr/commit/5168cba8e4ccfb8a1c348c80706b2e7a987bb152))
|
||||||
|
* **env:** improve support for instances without internet connection ([#3915](https://github.com/homarr-labs/homarr/issues/3915)) ([10feaa3](https://github.com/homarr-labs/homarr/commit/10feaa37ce1a3ecd28246db9b2f1b0414d0a75e6))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api:** document generation not working with nextjs ([#3920](https://github.com/homarr-labs/homarr/issues/3920)) ([97bc092](https://github.com/homarr-labs/homarr/commit/97bc092aea72dce19f5d1626054ecae69cf01aac))
|
||||||
|
* **security:** deepsource issues ([#3919](https://github.com/homarr-labs/homarr/issues/3919)) ([84f1e78](https://github.com/homarr-labs/homarr/commit/84f1e787013468cab7f2f0e2bcdd1f012d4e0747))
|
||||||
|
* system resources widget opacity support for transparent backgrounds ([#3835](https://github.com/homarr-labs/homarr/issues/3835)) ([a4aa2ae](https://github.com/homarr-labs/homarr/commit/a4aa2aea904081ff49e9cfc173c35770a019c587))
|
||||||
|
|
||||||
|
## [1.34.0](https://github.com/homarr-labs/homarr/compare/v1.33.0...v1.34.0) (2025-08-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **app-widget:** add layout option ([#3875](https://github.com/homarr-labs/homarr/issues/3875)) ([acd09c8](https://github.com/homarr-labs/homarr/commit/acd09c8c02c13daac6e5b2432bb72640f075cec6))
|
||||||
|
* **app-widget:** multiline description ([#3873](https://github.com/homarr-labs/homarr/issues/3873)) ([950636e](https://github.com/homarr-labs/homarr/commit/950636e81d4a934183f27694829dd3af05e5373f))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** update dependency drizzle-zod to ^0.8.3 ([#3866](https://github.com/homarr-labs/homarr/issues/3866)) ([dc43a01](https://github.com/homarr-labs/homarr/commit/dc43a015c43d13ae01a0f32be654c756f41eb352))
|
||||||
|
* **deps:** update dependency mantine-form-zod-resolver to ^1.3.0 ([#3868](https://github.com/homarr-labs/homarr/issues/3868)) ([72cdba7](https://github.com/homarr-labs/homarr/commit/72cdba79cbbaa03f35c90dd71a3703bacbe57558))
|
||||||
|
* **deps:** update dependency trpc-to-openapi to ^3.0.1 ([#3761](https://github.com/homarr-labs/homarr/issues/3761)) ([2457f9e](https://github.com/homarr-labs/homarr/commit/2457f9e4a754221e8674a95d721ffc77573a4633))
|
||||||
|
* **deps:** update dependency typescript-eslint to ^8.40.0 ([#3884](https://github.com/homarr-labs/homarr/issues/3884)) ([1a8e40e](https://github.com/homarr-labs/homarr/commit/1a8e40e1f9dbd4dbcf4ec0c54aef3297bc4cea7f))
|
||||||
|
* **deps:** update dependency undici to v7.14.0 ([#3879](https://github.com/homarr-labs/homarr/issues/3879)) ([d7b2298](https://github.com/homarr-labs/homarr/commit/d7b2298d1488ef8d38558864cf359fc23bc5bb26))
|
||||||
|
* **deps:** update dependency zod to ^4.0.17 ([#3867](https://github.com/homarr-labs/homarr/issues/3867)) ([82fcdae](https://github.com/homarr-labs/homarr/commit/82fcdae7e5cbc326f0051beb56c3a3805623e2a0))
|
||||||
|
* **deps:** update dependency zod-validation-error to v4 ([#3670](https://github.com/homarr-labs/homarr/issues/3670)) ([09fdb16](https://github.com/homarr-labs/homarr/commit/09fdb167a76db2caf3f9aa253b516ee28baba3dd))
|
||||||
|
* **deps:** update mantine monorepo to ^8.2.5 ([#3874](https://github.com/homarr-labs/homarr/issues/3874)) ([7e700b2](https://github.com/homarr-labs/homarr/commit/7e700b2732e2f0abff13d79191addb04097ca9bc))
|
||||||
|
* **deps:** update nextjs monorepo to v15.4.7 ([#3886](https://github.com/homarr-labs/homarr/issues/3886)) ([683e817](https://github.com/homarr-labs/homarr/commit/683e817493eb1b391751fe36b59d9b95a760324c))
|
||||||
|
* **deps:** update nextjs monorepo to v15.5.0 ([#3896](https://github.com/homarr-labs/homarr/issues/3896)) ([1451c3c](https://github.com/homarr-labs/homarr/commit/1451c3c41e08945eb31d025d187ff466c5104a05))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.4 ([#3889](https://github.com/homarr-labs/homarr/issues/3889)) ([7685438](https://github.com/homarr-labs/homarr/commit/76854387903b48b3af7be466972cf7b9c165acc5))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.5 ([#3890](https://github.com/homarr-labs/homarr/issues/3890)) ([fad7207](https://github.com/homarr-labs/homarr/commit/fad7207efe553111334b21739717b9813dea0b56))
|
||||||
|
* **nextcloud:** integration ignores trusted certificates ([#3893](https://github.com/homarr-labs/homarr/issues/3893)) ([498acdc](https://github.com/homarr-labs/homarr/commit/498acdcdc190de457dc95e5b13f43e1b2c31f624))
|
||||||
|
|
||||||
|
## [1.33.0](https://github.com/homarr-labs/homarr/compare/v1.32.0...v1.33.0) (2025-08-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **tasks:** replace card layout with table interface for better UX ([#3804](https://github.com/homarr-labs/homarr/issues/3804)) ([9d14fcb](https://github.com/homarr-labs/homarr/commit/9d14fcba3646485d73d20455552e56602a346736))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **dashdot:** disabled widgets cause parse errors ([#3861](https://github.com/homarr-labs/homarr/issues/3861)) ([b7455d1](https://github.com/homarr-labs/homarr/commit/b7455d18edb3f4a76e9aec35f715cc722b13f0c3))
|
||||||
|
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.6.2 ([#3854](https://github.com/homarr-labs/homarr/issues/3854)) ([8a4198d](https://github.com/homarr-labs/homarr/commit/8a4198db7e7fcb1e8556cf92c51888779ac6b614))
|
||||||
|
* **deps:** update dependency fastify to ^5.5.0 ([#3836](https://github.com/homarr-labs/homarr/issues/3836)) ([ff1e02e](https://github.com/homarr-labs/homarr/commit/ff1e02ec78b31a9cd155828b2f143fbae8e15b88))
|
||||||
|
* **deps:** update dependency jotai to ^2.13.1 ([#3834](https://github.com/homarr-labs/homarr/issues/3834)) ([5fe5d6f](https://github.com/homarr-labs/homarr/commit/5fe5d6f64d5c5b4096db8d4b2c7aefeab1fcef31))
|
||||||
|
* **deps:** update dependency tsx to v4.20.4 ([#3845](https://github.com/homarr-labs/homarr/issues/3845)) ([7a4961e](https://github.com/homarr-labs/homarr/commit/7a4961e9ad3e58ab09160f5cb7f0d8968f16d73a))
|
||||||
|
* **deps:** update dependency typescript-eslint to ^8.39.1 ([#3840](https://github.com/homarr-labs/homarr/issues/3840)) ([433be5e](https://github.com/homarr-labs/homarr/commit/433be5e82cbfb557bc1f69f119505c58f2612d25))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.0 ([#3846](https://github.com/homarr-labs/homarr/issues/3846)) ([7d123b8](https://github.com/homarr-labs/homarr/commit/7d123b83e05a43d86d3822155cd9ed9d5cd4b55d))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.1 ([#3858](https://github.com/homarr-labs/homarr/issues/3858)) ([70ce3e1](https://github.com/homarr-labs/homarr/commit/70ce3e168b0220486f90ef434ade61047de2ccb6))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.2 ([#3859](https://github.com/homarr-labs/homarr/issues/3859)) ([e9cb878](https://github.com/homarr-labs/homarr/commit/e9cb878169b151c6090e2b736f6597be934bf4b5))
|
||||||
|
* **deps:** update tanstack-query monorepo to ^5.85.3 ([#3860](https://github.com/homarr-labs/homarr/issues/3860)) ([c72c154](https://github.com/homarr-labs/homarr/commit/c72c154305bc40fbaae728f3bffeeee065ab819d))
|
||||||
|
* **deps:** update turbo monorepo to ^2.5.6 ([#3865](https://github.com/homarr-labs/homarr/issues/3865)) ([98d344c](https://github.com/homarr-labs/homarr/commit/98d344c4b7f88fd0b7c6ce508b30cefd47f98ce5))
|
||||||
|
* **deps:** upgrade zod to v4 and fix breaking changes ([#3461](https://github.com/homarr-labs/homarr/issues/3461)) ([5c99622](https://github.com/homarr-labs/homarr/commit/5c99622fa86b7b0721384f089862a8405a27a61e))
|
||||||
|
* **mysql:** increase max media size restricted from 16kb to 4gb ([#3839](https://github.com/homarr-labs/homarr/issues/3839)) ([7ffcd1a](https://github.com/homarr-labs/homarr/commit/7ffcd1a08ede840cf67da2c74a4ba0965645a673))
|
||||||
|
|
||||||
## [1.32.0](https://github.com/homarr-labs/homarr/compare/v1.31.0...v1.32.0) (2025-08-08)
|
## [1.32.0](https://github.com/homarr-labs/homarr/compare/v1.31.0...v1.32.0) (2025-08-08)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
+4
-4
@@ -1,18 +1,18 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
This policy is relevant if you found potential vulnerabilities in an audit.
|
This policy is relevant if you found potential vulnerabilities in an audit.
|
||||||
We consider something as a vulnerability if it...
|
We consider something as a vulnerability if it...
|
||||||
|
|
||||||
1. puts users or user data at risk
|
1. puts users or user data at risk
|
||||||
2. enables third parties to gain control or access (e.g. [RATs](https://en.wikipedia.org/wiki/Remote_desktop_software#RAT), [privilege escalation](https://en.wikipedia.org/wiki/Privilege_escalation), ...)
|
2. enables third parties to gain control or access (e.g. [RATs](https://en.wikipedia.org/wiki/Remote_desktop_software#RAT), [privilege escalation](https://en.wikipedia.org/wiki/Privilege_escalation), ...)
|
||||||
3. abuses the system in an unintended way (e.g. crypto mining, proxy, ...)
|
3. abuses the system in an unintended way (e.g. crypto mining, proxy, ...)
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
We only fix security issues in the [latest stable version](https://github.com/homarr-labs/homarr/releases/latest). Meaning security issues in prior versions will not be fixed and users have to upgrade to the latest version to receive them.
|
||||||
| ------- | ------------------ |
|
|
||||||
| >1.0.0 | :white_check_mark: |
|
|
||||||
| <1.0.0 | :x: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
We use [GitHub's system for reporting vulnerabilities](https://docs.github.com/en/enterprise-cloud@latest/code-security/security-advisories/working-with-repository-security-advisories/creating-a-repository-security-advisory).
|
We use [GitHub's system for reporting vulnerabilities](https://docs.github.com/en/enterprise-cloud@latest/code-security/security-advisories/working-with-repository-security-advisories/creating-a-repository-security-advisory).
|
||||||
Click [**here to report an advisory**](https://github.com/homarr-labs/homarr/security/advisories/new). Our team will get notified and will get back to you within 1-6 business days.
|
Click [**here to report an advisory**](https://github.com/homarr-labs/homarr/security/advisories/new). Our team will get notified and will get back to you within 1-6 business days.
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -50,17 +50,17 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/colors-generator": "^8.2.4",
|
"@mantine/colors-generator": "^8.2.5",
|
||||||
"@mantine/core": "^8.2.4",
|
"@mantine/core": "^8.2.5",
|
||||||
"@mantine/dropzone": "^8.2.4",
|
"@mantine/dropzone": "^8.2.5",
|
||||||
"@mantine/hooks": "^8.2.4",
|
"@mantine/hooks": "^8.2.5",
|
||||||
"@mantine/modals": "^8.2.4",
|
"@mantine/modals": "^8.2.5",
|
||||||
"@mantine/tiptap": "^8.2.4",
|
"@mantine/tiptap": "^8.2.5",
|
||||||
"@million/lint": "1.0.14",
|
"@million/lint": "1.0.14",
|
||||||
"@tabler/icons-react": "^3.34.1",
|
"@tabler/icons-react": "^3.34.1",
|
||||||
"@tanstack/react-query": "^5.84.2",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"@tanstack/react-query-devtools": "^5.84.2",
|
"@tanstack/react-query-devtools": "^5.85.5",
|
||||||
"@tanstack/react-query-next-experimental": "^5.84.2",
|
"@tanstack/react-query-next-experimental": "^5.85.5",
|
||||||
"@trpc/client": "^11.4.4",
|
"@trpc/client": "^11.4.4",
|
||||||
"@trpc/next": "^11.4.4",
|
"@trpc/next": "^11.4.4",
|
||||||
"@trpc/react-query": "^11.4.4",
|
"@trpc/react-query": "^11.4.4",
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"flag-icons": "^7.5.0",
|
"flag-icons": "^7.5.0",
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
"jotai": "^2.13.0",
|
"jotai": "^2.13.1",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "15.4.6",
|
"next": "15.5.0",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
@@ -87,20 +87,20 @@
|
|||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"swagger-ui-react": "^5.27.1",
|
"swagger-ui-react": "^5.27.1",
|
||||||
"use-deep-compare-effect": "^1.8.1",
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
"zod": "^3.25.76"
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/chroma-js": "3.1.1",
|
"@types/chroma-js": "3.1.1",
|
||||||
"@types/node": "^22.17.1",
|
"@types/node": "^22.17.2",
|
||||||
"@types/prismjs": "^1.26.5",
|
"@types/prismjs": "^1.26.5",
|
||||||
"@types/react": "19.1.9",
|
"@types/react": "19.1.10",
|
||||||
"@types/react-dom": "19.1.7",
|
"@types/react-dom": "19.1.7",
|
||||||
"@types/swagger-ui-react": "^5.18.0",
|
"@types/swagger-ui-react": "^5.18.0",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.2.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"node-loader": "^2.1.0",
|
"node-loader": "^2.1.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core";
|
import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { signIn } from "@homarr/auth/client";
|
import { signIn } from "@homarr/auth/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Button, Card, Stack, TextInput } from "@mantine/core";
|
import { Button, Card, Stack, TextInput } from "@mantine/core";
|
||||||
import { IconArrowRight } from "@tabler/icons-react";
|
import { IconArrowRight } from "@tabler/icons-react";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { startTransition } from "react";
|
import { startTransition } from "react";
|
||||||
import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core";
|
import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core";
|
||||||
import { IconArrowRight } from "@tabler/icons-react";
|
import { IconArrowRight } from "@tabler/icons-react";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { signIn } from "@homarr/auth/client";
|
import { signIn } from "@homarr/auth/client";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { Fragment } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
import { IconBox, IconPencil } from "@tabler/icons-react";
|
import { IconBox, IconPencil } from "@tabler/icons-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { api } from "@homarr/api/server";
|
import { api } from "@homarr/api/server";
|
||||||
@@ -98,7 +99,12 @@ const AppCard = async ({ app }: AppCardProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
{app.description && (
|
{app.description && (
|
||||||
<Text size="sm" c="gray.6" lineClamp={4}>
|
<Text size="sm" c="gray.6" lineClamp={4}>
|
||||||
{app.description}
|
{app.description.split("\n").map((line, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{line}
|
||||||
|
<br />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{app.href && (
|
{app.href && (
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import { useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
|
import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { Container, Group, Stack, Title } from "@mantine/core";
|
import { Container, Group, Stack, Title } from "@mantine/core";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { auth } from "@homarr/auth/next";
|
import { auth } from "@homarr/auth/next";
|
||||||
import type { IntegrationKind } from "@homarr/definitions";
|
import type { IntegrationKind } from "@homarr/definitions";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconExternalLink } from "@tabler/icons-react";
|
import { IconExternalLink } from "@tabler/icons-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { api } from "@homarr/api/server";
|
import { api } from "@homarr/api/server";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
|||||||
import type { SegmentedControlItem } from "@mantine/core";
|
import type { SegmentedControlItem } from "@mantine/core";
|
||||||
import { Button, Fieldset, Grid, Group, SegmentedControl, Stack, Textarea, TextInput } from "@mantine/core";
|
import { Button, Fieldset, Grid, Group, SegmentedControl, Stack, Textarea, TextInput } from "@mantine/core";
|
||||||
import { WidgetIntegrationSelect } from "node_modules/@homarr/widgets/src/widget-integration-select";
|
import { WidgetIntegrationSelect } from "node_modules/@homarr/widgets/src/widget-integration-select";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { searchEngineTypes } from "@homarr/definitions";
|
import { searchEngineTypes } from "@homarr/definitions";
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
import { IconPencil, IconSearch } from "@tabler/icons-react";
|
import { IconPencil, IconSearch } from "@tabler/icons-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { api } from "@homarr/api/server";
|
import { api } from "@homarr/api/server";
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useTransition } from "react";
|
|
||||||
import { ActionIcon, Badge, Button, Card, Group, Select, Stack, Text } from "@mantine/core";
|
|
||||||
import { useMap } from "@mantine/hooks";
|
|
||||||
import { IconPlayerPlay, IconPower, IconSettings } from "@tabler/icons-react";
|
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
|
||||||
import { getMantineColor, useTimeAgo } from "@homarr/common";
|
|
||||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
|
||||||
import { useForm } from "@homarr/form";
|
|
||||||
import { createModal, useModalAction } from "@homarr/modals";
|
|
||||||
import { TranslationFunction } from "@homarr/translation";
|
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
|
||||||
import { IconPowerOff } from "@homarr/ui/icons";
|
|
||||||
|
|
||||||
interface JobsListProps {
|
|
||||||
initialJobs: RouterOutputs["cronJobs"]["getJobs"];
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobName = RouterOutputs["cronJobs"]["getJobs"][number]["name"];
|
|
||||||
|
|
||||||
export const JobsList = ({ initialJobs }: JobsListProps) => {
|
|
||||||
const [jobs] = clientApi.cronJobs.getJobs.useSuspenseQuery(undefined, {
|
|
||||||
initialData: initialJobs,
|
|
||||||
refetchOnMount: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const jobStatusMap = useMap<string, TaskStatus | null>(initialJobs.map(({ name }) => [name, null] as const));
|
|
||||||
|
|
||||||
clientApi.cronJobs.subscribeToStatusUpdates.useSubscription(undefined, {
|
|
||||||
onData: (data) => {
|
|
||||||
jobStatusMap.set(data.name, data);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
{jobs.map((job) => {
|
|
||||||
const status = jobStatusMap.get(job.name);
|
|
||||||
|
|
||||||
return <JobCard key={job.name} job={job} status={status ?? null} />;
|
|
||||||
})}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cronExpressions = [
|
|
||||||
{
|
|
||||||
value: "*/5 * * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "*/10 * * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 10 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "*/20 * * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.seconds", { interval: 20 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "* * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 1 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "*/5 * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 5 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "*/10 * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 10 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "*/15 * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.minutes", { interval: 15 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "0 * * * *",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.hours", { interval: 1 }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "0 0 * * */1",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.midnight"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "0 0 * * 1",
|
|
||||||
label: (t) => t("management.page.tool.tasks.interval.weeklyMonday"),
|
|
||||||
},
|
|
||||||
] satisfies { value: string; label: (t: TranslationFunction) => string }[];
|
|
||||||
|
|
||||||
interface JobCardProps {
|
|
||||||
job: RouterOutputs["cronJobs"]["getJobs"][number];
|
|
||||||
status: TaskStatus | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const JobCard = ({ job, status }: JobCardProps) => {
|
|
||||||
const t = useI18n();
|
|
||||||
const tTasks = useScopedI18n("management.page.tool.tasks");
|
|
||||||
const triggerMutation = clientApi.cronJobs.triggerJob.useMutation();
|
|
||||||
const handleJobTrigger = React.useCallback(
|
|
||||||
async (name: JobName) => {
|
|
||||||
if (status?.status === "running") return;
|
|
||||||
await triggerMutation.mutateAsync(name);
|
|
||||||
},
|
|
||||||
[triggerMutation, status],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { openModal } = useModalAction(TaskConfigurationModal);
|
|
||||||
const [isEnabled, setEnabled] = useState(job.isEnabled);
|
|
||||||
const disableMutation = clientApi.cronJobs.disableJob.useMutation();
|
|
||||||
const enableMutation = clientApi.cronJobs.enableJob.useMutation();
|
|
||||||
|
|
||||||
const [activeStatePending, startActiveTransition] = useTransition();
|
|
||||||
const handleActiveChange = () =>
|
|
||||||
startActiveTransition(async () => {
|
|
||||||
if (isEnabled) {
|
|
||||||
await disableMutation.mutateAsync(job.name, {
|
|
||||||
onSuccess() {
|
|
||||||
setEnabled(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await enableMutation.mutateAsync(job.name, {
|
|
||||||
onSuccess() {
|
|
||||||
setEnabled(true);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card key={job.name} withBorder>
|
|
||||||
<Group justify={"space-between"} gap={"md"}>
|
|
||||||
<Stack gap={0}>
|
|
||||||
<Group>
|
|
||||||
<Text>{tTasks(`job.${job.name}.label`)}</Text>
|
|
||||||
<StatusBadge isEnabled={isEnabled} status={status} />
|
|
||||||
{status?.lastExecutionStatus === "error" && <Badge color="red">{tTasks("status.error")}</Badge>}
|
|
||||||
</Group>
|
|
||||||
<Group gap="xs">
|
|
||||||
{status && (
|
|
||||||
<>
|
|
||||||
<TimeAgo timestamp={status.lastExecutionTimestamp} />
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
•
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{cronExpressions.find((expression) => expression.value === job.cron)?.label(t) ?? job.cron}
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Group>
|
|
||||||
{!job.preventManualExecution && (
|
|
||||||
<ActionIcon
|
|
||||||
onClick={() => handleJobTrigger(job.name)}
|
|
||||||
disabled={status?.status === "running"}
|
|
||||||
loading={triggerMutation.isPending}
|
|
||||||
variant="default"
|
|
||||||
size="xl"
|
|
||||||
radius="xl"
|
|
||||||
>
|
|
||||||
<IconPlayerPlay color={getMantineColor("green", 6)} stroke={1.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
<ActionIcon onClick={handleActiveChange} loading={activeStatePending} variant="default" size="xl" radius="xl">
|
|
||||||
{isEnabled ? (
|
|
||||||
<IconPower color={getMantineColor("green", 6)} stroke={1.5} />
|
|
||||||
) : (
|
|
||||||
<IconPowerOff color={getMantineColor("gray", 6)} stroke={1.5} />
|
|
||||||
)}
|
|
||||||
</ActionIcon>
|
|
||||||
<ActionIcon
|
|
||||||
onClick={() =>
|
|
||||||
openModal(
|
|
||||||
{ job },
|
|
||||||
{
|
|
||||||
title: tTasks("settings.title", {
|
|
||||||
jobName: tTasks(`job.${job.name}.label`),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
variant={"default"}
|
|
||||||
size={"xl"}
|
|
||||||
radius={"xl"}
|
|
||||||
>
|
|
||||||
<IconSettings stroke={1.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface StatusBadgeProps {
|
|
||||||
isEnabled: boolean;
|
|
||||||
status: TaskStatus | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StatusBadge = ({ isEnabled, status }: StatusBadgeProps) => {
|
|
||||||
const t = useScopedI18n("management.page.tool.tasks");
|
|
||||||
if (!isEnabled) return <Badge color="yellow">{t("status.disabled")}</Badge>;
|
|
||||||
|
|
||||||
if (!status) return null;
|
|
||||||
|
|
||||||
if (status.status === "running") return <Badge color="green">{t("status.running")}</Badge>;
|
|
||||||
return <Badge variant="default">{t("status.idle")}</Badge>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TimeAgo = ({ timestamp }: { timestamp: string }) => {
|
|
||||||
const timeAgo = useTimeAgo(new Date(timestamp));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text size={"sm"} c={"dimmed"}>
|
|
||||||
{timeAgo}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TaskConfigurationModal = createModal<{
|
|
||||||
job: RouterOutputs["cronJobs"]["getJobs"][number];
|
|
||||||
}>(({ actions, innerProps }) => {
|
|
||||||
const t = useI18n();
|
|
||||||
const form = useForm({
|
|
||||||
initialValues: {
|
|
||||||
cron: innerProps.job.cron,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { mutateAsync, isPending } = clientApi.cronJobs.updateJobInterval.useMutation();
|
|
||||||
const utils = clientApi.useUtils();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={form.onSubmit(async (values) => {
|
|
||||||
utils.cronJobs.getJobs.setData(undefined, (data) =>
|
|
||||||
data?.map((job) =>
|
|
||||||
job.name === innerProps.job.name
|
|
||||||
? {
|
|
||||||
...job,
|
|
||||||
cron: values.cron,
|
|
||||||
}
|
|
||||||
: job,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await mutateAsync(
|
|
||||||
{
|
|
||||||
name: innerProps.job.name,
|
|
||||||
cron: values.cron,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess() {
|
|
||||||
actions.closeModal();
|
|
||||||
},
|
|
||||||
async onSettled() {
|
|
||||||
await utils.cronJobs.getJobs.invalidate();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
|
||||||
<Select
|
|
||||||
label={t("management.page.tool.tasks.field.interval.label")}
|
|
||||||
{...form.getInputProps("cron")}
|
|
||||||
data={cronExpressions.map(({ value, label }) => ({ value, label: label(t) }))}
|
|
||||||
/>
|
|
||||||
<Group justify="end">
|
|
||||||
<Button variant="subtle" color="gray" disabled={isPending} onClick={actions.closeModal}>
|
|
||||||
{t("common.action.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" loading={isPending}>
|
|
||||||
{t("common.action.save")}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}).withOptions({
|
|
||||||
defaultTitle: "",
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,451 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ActionIcon, Badge, Button, Group, Select, Text } from "@mantine/core";
|
||||||
|
import { useMap } from "@mantine/hooks";
|
||||||
|
import { IconPlayerPlay, IconPower, IconRefresh } from "@tabler/icons-react";
|
||||||
|
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { MantineReactTable } from "mantine-react-table";
|
||||||
|
|
||||||
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { useTimeAgo } from "@homarr/common";
|
||||||
|
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||||
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
|
import type { ScopedTranslationFunction, TranslationFunction } from "@homarr/translation";
|
||||||
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
|
import { useTranslatedMantineReactTable } from "@homarr/ui/hooks";
|
||||||
|
import { IconPowerOff } from "@homarr/ui/icons";
|
||||||
|
|
||||||
|
const cronExpressions = [
|
||||||
|
{
|
||||||
|
value: "*/5 * * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 5 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/10 * * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 10 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/20 * * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 20 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/30 * * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.seconds", { interval: 30 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "* * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.minutes", { interval: 1 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/5 * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.minutes", { interval: 5 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/10 * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.minutes", { interval: 10 }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "*/15 * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.minutes", { interval: 15 }),
|
||||||
|
},
|
||||||
|
// Every hour
|
||||||
|
{
|
||||||
|
value: "0 * * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.hours", { interval: 1 }),
|
||||||
|
},
|
||||||
|
// Every two hours
|
||||||
|
{
|
||||||
|
value: "0 */2 * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.hours", { interval: 2 }),
|
||||||
|
},
|
||||||
|
// Every four hours
|
||||||
|
{
|
||||||
|
value: "0 */4 * * *",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.hours", { interval: 4 }),
|
||||||
|
},
|
||||||
|
// Every midnight
|
||||||
|
{
|
||||||
|
value: "0 0 * * */1",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.midnight"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "0 0 * * 1",
|
||||||
|
label: (t: TranslationFunction) => t("management.page.tool.tasks.interval.weeklyMonday"),
|
||||||
|
},
|
||||||
|
] satisfies { value: string; label: (t: TranslationFunction) => string }[];
|
||||||
|
|
||||||
|
type JobData = RouterOutputs["cronJobs"]["getJobs"][number] & {
|
||||||
|
status?: TaskStatus | null;
|
||||||
|
lastExecutionTime?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createColumns = (
|
||||||
|
t: TranslationFunction,
|
||||||
|
tTasks: ScopedTranslationFunction<"management.page.tool.tasks">,
|
||||||
|
jobStatusMap: Map<string, TaskStatus | null>,
|
||||||
|
triggerMutation: ReturnType<typeof clientApi.cronJobs.triggerJob.useMutation>,
|
||||||
|
updateIntervalMutation: ReturnType<typeof clientApi.cronJobs.updateJobInterval.useMutation>,
|
||||||
|
enableMutation: ReturnType<typeof clientApi.cronJobs.enableJob.useMutation>,
|
||||||
|
disableMutation: ReturnType<typeof clientApi.cronJobs.disableJob.useMutation>,
|
||||||
|
loadingStates: Map<string, { toggle: boolean; trigger: boolean; interval: boolean }>,
|
||||||
|
): MRT_ColumnDef<JobData>[] => [
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
header: tTasks("field.name.label"),
|
||||||
|
Cell({ row }) {
|
||||||
|
const status = jobStatusMap.get(row.original.name);
|
||||||
|
return (
|
||||||
|
<Group gap="xs">
|
||||||
|
<Text fw={500}>{tTasks(`job.${row.original.name}.label`)}</Text>
|
||||||
|
<StatusBadge isEnabled={row.original.isEnabled} status={status ?? null} />
|
||||||
|
{status?.lastExecutionStatus === "error" && (
|
||||||
|
<Badge color="red" size="sm">
|
||||||
|
{tTasks("status.error")}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "cron",
|
||||||
|
header: tTasks("field.interval.label"),
|
||||||
|
size: 200,
|
||||||
|
Cell({ row }) {
|
||||||
|
const handleIntervalChange = (newCron: string | null) => {
|
||||||
|
if (!newCron || newCron === row.original.cron) return;
|
||||||
|
|
||||||
|
const currentStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...currentStates,
|
||||||
|
interval: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateIntervalMutation.mutate({
|
||||||
|
name: row.original.name,
|
||||||
|
cron: newCron,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
const updatedStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...updatedStates,
|
||||||
|
interval: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={row.original.cron}
|
||||||
|
onChange={handleIntervalChange}
|
||||||
|
data={cronExpressions.map(({ value, label }) => ({
|
||||||
|
value,
|
||||||
|
label: label(t),
|
||||||
|
}))}
|
||||||
|
size="sm"
|
||||||
|
disabled={loadingStates.get(row.original.name)?.interval ?? false}
|
||||||
|
style={{ minWidth: 180 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "lastExecutionTime",
|
||||||
|
header: tTasks("field.lastExecution.label"),
|
||||||
|
size: 150,
|
||||||
|
Cell({ row }) {
|
||||||
|
const status = jobStatusMap.get(row.original.name);
|
||||||
|
if (!status?.lastExecutionTimestamp) {
|
||||||
|
return (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
—
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <TimeAgo timestamp={status.lastExecutionTimestamp} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: tTasks("field.actions.label"),
|
||||||
|
size: 120,
|
||||||
|
enableSorting: false,
|
||||||
|
Cell({ row }) {
|
||||||
|
const status = jobStatusMap.get(row.original.name);
|
||||||
|
|
||||||
|
const handleToggleEnabled = () => {
|
||||||
|
const currentStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...currentStates,
|
||||||
|
toggle: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
if (row.original.isEnabled) {
|
||||||
|
disableMutation.mutate(row.original.name);
|
||||||
|
} else {
|
||||||
|
enableMutation.mutate(row.original.name);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
const updatedStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...updatedStates,
|
||||||
|
toggle: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTrigger = () => {
|
||||||
|
if (status?.status === "running") return;
|
||||||
|
|
||||||
|
const currentStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...currentStates,
|
||||||
|
trigger: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
triggerMutation.mutate(row.original.name);
|
||||||
|
} finally {
|
||||||
|
const updatedStates = loadingStates.get(row.original.name) ?? {
|
||||||
|
toggle: false,
|
||||||
|
trigger: false,
|
||||||
|
interval: false,
|
||||||
|
};
|
||||||
|
loadingStates.set(row.original.name, {
|
||||||
|
...updatedStates,
|
||||||
|
trigger: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap="xs">
|
||||||
|
{!row.original.preventManualExecution && (
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleTrigger}
|
||||||
|
disabled={status?.status === "running"}
|
||||||
|
loading={loadingStates.get(row.original.name)?.trigger ?? false}
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconPlayerPlay size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleToggleEnabled}
|
||||||
|
loading={loadingStates.get(row.original.name)?.toggle ?? false}
|
||||||
|
variant="light"
|
||||||
|
color={row.original.isEnabled ? "green" : "gray"}
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
{row.original.isEnabled ? <IconPower size={16} /> : <IconPowerOff size={16} />}
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface TasksTableProps {
|
||||||
|
initialJobs: RouterOutputs["cronJobs"]["getJobs"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TasksTable = ({ initialJobs }: TasksTableProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const tTasks = useScopedI18n("management.page.tool.tasks");
|
||||||
|
|
||||||
|
const { data: jobs } = clientApi.cronJobs.getJobs.useQuery(undefined, {
|
||||||
|
initialData: initialJobs,
|
||||||
|
refetchOnMount: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const jobStatusMap = useMap<string, TaskStatus | null>(initialJobs.map(({ name }) => [name, null] as const));
|
||||||
|
|
||||||
|
const loadingStates = useMap<string, { toggle: boolean; trigger: boolean; interval: boolean }>();
|
||||||
|
|
||||||
|
clientApi.cronJobs.subscribeToStatusUpdates.useSubscription(undefined, {
|
||||||
|
onData: (data) => {
|
||||||
|
jobStatusMap.set(data.name, data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerMutation = clientApi.cronJobs.triggerJob.useMutation({
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: t("common.error"),
|
||||||
|
message: tTasks("trigger.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
showSuccessNotification({
|
||||||
|
title: t("common.success"),
|
||||||
|
message: tTasks("trigger.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const updateIntervalMutation = clientApi.cronJobs.updateJobInterval.useMutation({
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: t("common.error"),
|
||||||
|
message: tTasks("interval.update.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.cronJobs.getJobs.invalidate();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: t("common.success"),
|
||||||
|
message: tTasks("interval.update.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const enableMutation = clientApi.cronJobs.enableJob.useMutation({
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: t("common.error"),
|
||||||
|
message: tTasks("toggle.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.cronJobs.getJobs.invalidate();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: t("common.success"),
|
||||||
|
message: tTasks("enable.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const disableMutation = clientApi.cronJobs.disableJob.useMutation({
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: t("common.error"),
|
||||||
|
message: tTasks("toggle.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.cronJobs.getJobs.invalidate();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: t("common.success"),
|
||||||
|
message: tTasks("disable.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Utils for refresh functionality
|
||||||
|
const utils = clientApi.useUtils();
|
||||||
|
const handleRefreshAsync = async () => {
|
||||||
|
try {
|
||||||
|
await utils.cronJobs.getJobs.invalidate();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: t("common.success"),
|
||||||
|
message: tTasks("refresh.success.message"),
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
showErrorNotification({
|
||||||
|
title: t("common.error"),
|
||||||
|
message: tTasks("refresh.error.message"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const table = useTranslatedMantineReactTable({
|
||||||
|
data: jobs,
|
||||||
|
enableDensityToggle: false,
|
||||||
|
enableColumnActions: false,
|
||||||
|
enableColumnFilters: false,
|
||||||
|
enablePagination: false,
|
||||||
|
enableRowSelection: false,
|
||||||
|
enableTableFooter: false,
|
||||||
|
enableBottomToolbar: false,
|
||||||
|
enableRowActions: false,
|
||||||
|
enableColumnOrdering: false,
|
||||||
|
enableSorting: false,
|
||||||
|
enableSortingRemoval: false,
|
||||||
|
positionGlobalFilter: "right",
|
||||||
|
mantineSearchTextInputProps: {
|
||||||
|
placeholder: tTasks("table.search", { count: String(jobs.length) }),
|
||||||
|
style: { minWidth: 300 },
|
||||||
|
},
|
||||||
|
initialState: { density: "xs", showGlobalFilter: true },
|
||||||
|
renderTopToolbarCustomActions: () => (
|
||||||
|
<Button variant="default" rightSection={<IconRefresh size="1rem" />} onClick={handleRefreshAsync}>
|
||||||
|
{tTasks("action.refresh.label")}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
columns: createColumns(
|
||||||
|
t,
|
||||||
|
tTasks,
|
||||||
|
jobStatusMap,
|
||||||
|
triggerMutation,
|
||||||
|
updateIntervalMutation,
|
||||||
|
enableMutation,
|
||||||
|
disableMutation,
|
||||||
|
loadingStates,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return <MantineReactTable table={table} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusBadgeProps {
|
||||||
|
isEnabled: boolean;
|
||||||
|
status: TaskStatus | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusBadge = ({ isEnabled, status }: StatusBadgeProps) => {
|
||||||
|
const tTasks = useScopedI18n("management.page.tool.tasks");
|
||||||
|
|
||||||
|
if (!isEnabled) {
|
||||||
|
return (
|
||||||
|
<Badge color="yellow" size="sm">
|
||||||
|
{tTasks("status.disabled")}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status) return null;
|
||||||
|
|
||||||
|
if (status.status === "running") {
|
||||||
|
return (
|
||||||
|
<Badge color="green" size="sm">
|
||||||
|
{tTasks("status.running")}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge variant="default" size="sm">
|
||||||
|
{tTasks("status.idle")}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TimeAgo = ({ timestamp }: { timestamp: string }) => {
|
||||||
|
const timeAgo = useTimeAgo(new Date(timestamp));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{timeAgo}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { Box, Title } from "@mantine/core";
|
import { Stack, Title } from "@mantine/core";
|
||||||
|
|
||||||
import { api } from "@homarr/api/server";
|
import { api } from "@homarr/api/server";
|
||||||
import { auth } from "@homarr/auth/next";
|
import { auth } from "@homarr/auth/next";
|
||||||
import { getScopedI18n } from "@homarr/translation/server";
|
import { getScopedI18n } from "@homarr/translation/server";
|
||||||
|
|
||||||
|
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||||
import { createMetaTitle } from "~/metadata";
|
import { createMetaTitle } from "~/metadata";
|
||||||
import { JobsList } from "./_components/jobs-list";
|
import { TasksTable } from "./_components/tasks-table";
|
||||||
|
|
||||||
export async function generateMetadata() {
|
export async function generateMetadata() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
@@ -27,10 +28,15 @@ export default async function TasksPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jobs = await api.cronJobs.getJobs();
|
const jobs = await api.cronJobs.getJobs();
|
||||||
|
const tTasks = await getScopedI18n("management.page.tool.tasks");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<>
|
||||||
<Title mb={"md"}>Tasks</Title>
|
<DynamicBreadcrumb />
|
||||||
<JobsList initialJobs={jobs} />
|
<Stack>
|
||||||
</Box>
|
<Title order={1}>{tTasks("title")}</Title>
|
||||||
|
<TasksTable initialJobs={jobs} />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, Group, Stack } from "@mantine/core";
|
import { Button, Group, Stack } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, Group, Select, Stack, Switch } from "@mantine/core";
|
import { Button, Group, Select, Stack, Switch } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import { Button, Group, Radio, Stack } from "@mantine/core";
|
|||||||
import type { DayOfWeek } from "@mantine/dates";
|
import type { DayOfWeek } from "@mantine/dates";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import localeData from "dayjs/plugin/localeData";
|
import localeData from "dayjs/plugin/localeData";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, Group, Stack, Switch } from "@mantine/core";
|
import { Button, Group, Stack, Switch } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useListState } from "@mantine/hooks";
|
import { useListState } from "@mantine/hooks";
|
||||||
import { IconPlus, IconUserCheck } from "@tabler/icons-react";
|
import { IconPlus, IconUserCheck } from "@tabler/icons-react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { everyoneGroup, groupPermissions } from "@homarr/definitions";
|
import { everyoneGroup, groupPermissions } from "@homarr/definitions";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
import { Button, Grid, Group, NumberInput, Stack } from "@mantine/core";
|
import { Button, Grid, Group, NumberInput, Stack } from "@mantine/core";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import type { GridStack } from "@homarr/gridstack";
|
import type { GridStack } from "@homarr/gridstack";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||||
import type { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
|
import type { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, CloseButton, ColorInput, Group, Stack, TextInput, useMantineTheme } from "@mantine/core";
|
import { Button, CloseButton, ColorInput, Group, Stack, TextInput, useMantineTheme } from "@mantine/core";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
|
|||||||
@@ -37,20 +37,20 @@
|
|||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"fastify": "^5.4.0",
|
"fastify": "^5.5.0",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"undici": "7.13.0"
|
"undici": "7.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "^22.17.1",
|
"@types/node": "^22.17.2",
|
||||||
"dotenv-cli": "^10.0.0",
|
"dotenv-cli": "^10.0.0",
|
||||||
"esbuild": "^0.25.8",
|
"esbuild": "^0.25.9",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"tsx": "4.20.3",
|
"tsx": "4.20.4",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"tsx": "4.20.3",
|
"tsx": "4.20.4",
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"esbuild": "^0.25.8",
|
"esbuild": "^0.25.9",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,29 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
# set shared memory limit when using docker compose
|
||||||
|
shm_size: 128mb
|
||||||
|
# or set shared memory limit when deploy via swarm stack
|
||||||
|
#volumes:
|
||||||
|
# - type: tmpfs
|
||||||
|
# target: /dev/shm
|
||||||
|
# tmpfs:
|
||||||
|
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: homarr
|
||||||
|
POSTGRES_USER: homarr
|
||||||
|
POSTGRES_DB: homarrdb
|
||||||
|
PGDATA: /var/lib/postgresql/data/pgdata
|
||||||
|
volumes:
|
||||||
|
- postgresql_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
# if already run PostgreSQL, change port number to use container's service
|
||||||
|
# - 2345:5432
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql_data:
|
mysql_data:
|
||||||
|
postgresql_data:
|
||||||
+34
-13
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homarr",
|
"name": "homarr",
|
||||||
"version": "1.32.0",
|
"version": "1.35.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env CI=true turbo build",
|
"build": "cross-env CI=true turbo build",
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
"cli": "pnpm with-env tsx packages/cli/index.ts",
|
"cli": "pnpm with-env tsx packages/cli/index.ts",
|
||||||
"db:migration:mysql:generate": "pnpm -F db migration:mysql:generate",
|
"db:migration:mysql:generate": "pnpm -F db migration:mysql:generate",
|
||||||
"db:migration:mysql:run": "pnpm -F db migration:mysql:run",
|
"db:migration:mysql:run": "pnpm -F db migration:mysql:run",
|
||||||
|
"db:migration:postgresql:generate": "pnpm -F db migration:postgresql:generate",
|
||||||
|
"db:migration:postgresql:run": "pnpm -F db migration:postgresql:run",
|
||||||
"db:migration:sqlite:generate": "pnpm -F db migration:sqlite:generate",
|
"db:migration:sqlite:generate": "pnpm -F db migration:sqlite:generate",
|
||||||
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
"db:migration:sqlite:run": "pnpm -F db migration:sqlite:run",
|
||||||
"db:push": "pnpm -F db push:sqlite",
|
"db:push": "pnpm -F db push:sqlite",
|
||||||
@@ -22,13 +24,14 @@
|
|||||||
"lint:ws": "pnpm dlx sherif@latest",
|
"lint:ws": "pnpm dlx sherif@latest",
|
||||||
"package:new": "turbo gen init",
|
"package:new": "turbo gen init",
|
||||||
"release": "semantic-release",
|
"release": "semantic-release",
|
||||||
|
"scripts:update-bug-report-template": "tsx ./scripts/update-bug-report-template.mts",
|
||||||
|
"scripts:update-readme-integrations": "tsx ./scripts/update-integration-list.mts",
|
||||||
"start": "concurrently \"pnpm with-env node apps/tasks/tasks.cjs\" \"pnpm with-env node apps/websocket/wssServer.cjs\" \"pnpm -F nextjs start\"",
|
"start": "concurrently \"pnpm with-env node apps/tasks/tasks.cjs\" \"pnpm with-env node apps/websocket/wssServer.cjs\" \"pnpm -F nextjs start\"",
|
||||||
"test": "cross-env NODE_ENV=development CI=true vitest run --exclude e2e --coverage.enabled ",
|
"test": "cross-env NODE_ENV=development CI=true vitest run --exclude e2e --coverage.enabled ",
|
||||||
"test:e2e": "cross-env NODE_ENV=development CI=true vitest e2e",
|
"test:e2e": "cross-env NODE_ENV=development CI=true vitest e2e",
|
||||||
"test:ui": "cross-env NODE_ENV=development CI=true vitest --exclude e2e --ui --coverage.enabled",
|
"test:ui": "cross-env NODE_ENV=development CI=true vitest --exclude e2e --ui --coverage.enabled",
|
||||||
"typecheck": "turbo typecheck",
|
"typecheck": "turbo typecheck",
|
||||||
"with-env": "dotenv -e .env --",
|
"with-env": "dotenv -e .env --"
|
||||||
"scripts:update-readme-integrations": "tsx ./scripts/update-integration-list.mts"
|
|
||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -36,12 +39,12 @@
|
|||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||||
"@semantic-release/git": "^10.0.1",
|
"@semantic-release/git": "^10.0.1",
|
||||||
"@semantic-release/github": "^11.0.3",
|
"@semantic-release/github": "^11.0.4",
|
||||||
"@semantic-release/npm": "^12.0.2",
|
"@semantic-release/npm": "^12.0.2",
|
||||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||||
"@testcontainers/redis": "^11.5.1",
|
"@testcontainers/redis": "^11.5.1",
|
||||||
"@turbo/gen": "^2.5.5",
|
"@turbo/gen": "^2.5.6",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^5.0.1",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||||
@@ -50,12 +53,12 @@
|
|||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"semantic-release": "^24.2.7",
|
"semantic-release": "^24.2.7",
|
||||||
"testcontainers": "^11.5.1",
|
"testcontainers": "^11.5.1",
|
||||||
"turbo": "^2.5.5",
|
"turbo": "^2.5.6",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.14.0",
|
"packageManager": "pnpm@10.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.18.0"
|
"node": ">=22.18.0"
|
||||||
},
|
},
|
||||||
@@ -72,16 +75,34 @@
|
|||||||
"tree-sitter-json"
|
"tree-sitter-json"
|
||||||
],
|
],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"proxmox-api>undici": "7.13.0"
|
"@babel/helpers@<7.26.10": ">=7.26.10",
|
||||||
|
"@babel/runtime@<7.26.10": ">=7.26.10",
|
||||||
|
"axios@>=1.0.0 <1.8.2": ">=1.8.2",
|
||||||
|
"brace-expansion@>=2.0.0 <=2.0.1": ">=2.0.2",
|
||||||
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=1.1.12",
|
||||||
|
"esbuild@<=0.24.2": ">=0.25.0",
|
||||||
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
||||||
|
"hono@<4.6.5": ">=4.6.5",
|
||||||
|
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||||
|
"nanoid@>=4.0.0 <5.0.9": ">=5.0.9",
|
||||||
|
"prismjs@<1.30.0": ">=1.30.0",
|
||||||
|
"proxmox-api>undici": "7.14.0",
|
||||||
|
"rollup@>=4.0.0 <4.22.4": ">=4.22.4",
|
||||||
|
"sha.js@<=2.4.11": ">=2.4.12",
|
||||||
|
"tar-fs@>=3.0.0 <3.0.9": ">=3.0.9",
|
||||||
|
"tar-fs@>=2.0.0 <2.1.3": ">=2.1.3",
|
||||||
|
"tmp@<=0.2.3": ">=0.2.4",
|
||||||
|
"vite@>=5.0.0 <=5.4.18": ">=5.4.19"
|
||||||
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||||
|
"trpc-to-openapi": "patches/trpc-to-openapi.patch"
|
||||||
},
|
},
|
||||||
"allowUnusedPatches": true,
|
"allowUnusedPatches": true,
|
||||||
"ignoredBuiltDependencies": [
|
"ignoredBuiltDependencies": [
|
||||||
"@scarf/scarf",
|
"@scarf/scarf",
|
||||||
"core-js-pure",
|
"core-js-pure",
|
||||||
"protobufjs"
|
"protobufjs"
|
||||||
],
|
]
|
||||||
"patchedDependencies": {
|
|
||||||
"@types/node-unifi": "patches/@types__node-unifi.patch"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@homarr/auth": "workspace:^0.1.0",
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/certificates": "workspace:^0.1.0",
|
"@homarr/certificates": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
"@homarr/cron-job-api": "workspace:^0.1.0",
|
||||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
@@ -41,24 +42,24 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@kubernetes/client-node": "^1.3.0",
|
"@kubernetes/client-node": "^1.3.0",
|
||||||
"@tanstack/react-query": "^5.84.2",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"@trpc/client": "^11.4.4",
|
"@trpc/client": "^11.4.4",
|
||||||
"@trpc/react-query": "^11.4.4",
|
"@trpc/react-query": "^11.4.4",
|
||||||
"@trpc/server": "^11.4.4",
|
"@trpc/server": "^11.4.4",
|
||||||
"@trpc/tanstack-react-query": "^11.4.4",
|
"@trpc/tanstack-react-query": "^11.4.4",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"next": "15.4.6",
|
"next": "15.5.0",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"superjson": "2.2.2",
|
"superjson": "2.2.2",
|
||||||
"trpc-to-openapi": "^2.4.0",
|
"trpc-to-openapi": "^3.0.1",
|
||||||
"zod": "^3.25.76"
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createEnv } from "@t3-oss/env-nextjs";
|
import { z } from "zod/v4";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
|
import { createEnv } from "@homarr/core/infrastructure/env";
|
||||||
|
|
||||||
export const env = createEnv({
|
export const env = createEnv({
|
||||||
server: {
|
server: {
|
||||||
@@ -10,6 +9,4 @@ export const env = createEnv({
|
|||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME,
|
KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME,
|
||||||
},
|
},
|
||||||
skipValidation: shouldSkipEnvValidation(),
|
|
||||||
emptyStringAsUndefined: true,
|
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Session } from "@homarr/auth";
|
import type { Session } from "@homarr/auth";
|
||||||
import { hasQueryAccessToIntegrationsAsync } from "@homarr/auth/server";
|
import { hasQueryAccessToIntegrationsAsync } from "@homarr/auth/server";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { and, eq } from "@homarr/db";
|
import { and, eq } from "@homarr/db";
|
||||||
import { items } from "@homarr/db/schema";
|
import { items } from "@homarr/db/schema";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { asc, eq, inArray, like } from "@homarr/db";
|
import { asc, eq, inArray, like } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
@@ -1623,7 +1623,7 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, use
|
|||||||
const forKind = <T extends WidgetKind>(kind: T) =>
|
const forKind = <T extends WidgetKind>(kind: T) =>
|
||||||
z.object({
|
z.object({
|
||||||
kind: z.literal(kind),
|
kind: z.literal(kind),
|
||||||
options: z.record(z.unknown()),
|
options: z.record(z.string(), z.unknown()),
|
||||||
});
|
});
|
||||||
|
|
||||||
const outputItemSchema = zodUnionFromArray(widgetKinds.map((kind) => forKind(kind))).and(sharedItemSchema);
|
const outputItemSchema = zodUnionFromArray(widgetKinds.map((kind) => forKind(kind))).and(sharedItemSchema);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { X509Certificate } from "node:crypto";
|
import { X509Certificate } from "node:crypto";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
|
||||||
import { zfd } from "zod-form-data";
|
import { zfd } from "zod-form-data";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
||||||
import { and, eq } from "@homarr/db";
|
import { and, eq } from "@homarr/db";
|
||||||
import { trustedCertificateHostnames } from "@homarr/db/schema";
|
import { trustedCertificateHostnames } from "@homarr/db/schema";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates";
|
import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export const certificateRouter = createTRPCRouter({
|
|||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(
|
.input(
|
||||||
zfd.formData({
|
zfd.formData({
|
||||||
file: zfd.file().superRefine(superRefineCertificateFile),
|
file: zfd.file().check(checkCertificateFile),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Container, ContainerState, Docker, Port } from "@homarr/docker";
|
import type { Container, ContainerState, Docker, Port } from "@homarr/docker";
|
||||||
import { DockerSingleton } from "@homarr/docker";
|
import { DockerSingleton } from "@homarr/docker";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { analyseOldmarrImportForRouterAsync, analyseOldmarrImportInputSchema } from "@homarr/old-import/analyse";
|
import { analyseOldmarrImportForRouterAsync, analyseOldmarrImportInputSchema } from "@homarr/old-import/analyse";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import z from "zod";
|
import z from "zod/v4";
|
||||||
|
|
||||||
import packageJson from "../../../../package.json";
|
import packageJson from "../../../../package.json";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId, objectEntries } from "@homarr/common";
|
import { createId, objectEntries } from "@homarr/common";
|
||||||
import { decryptSecret, encryptSecret } from "@homarr/common/server";
|
import { decryptSecret, encryptSecret } from "@homarr/common/server";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { asc, eq } from "@homarr/db";
|
import { asc, eq } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { fetchWithTimeout } from "@homarr/common";
|
import { fetchWithTimeout } from "@homarr/common";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import z from "zod";
|
import z from "zod/v4";
|
||||||
|
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import { logLevels } from "@homarr/log/constants";
|
import { logLevels } from "@homarr/log/constants";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import type { InferInsertModel } from "@homarr/db";
|
import type { InferInsertModel } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { onboarding } from "@homarr/db/schema";
|
import { onboarding } from "@homarr/db/schema";
|
||||||
import { onboardingSteps } from "@homarr/definitions";
|
import { onboardingSteps } from "@homarr/definitions";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { asc, eq, like } from "@homarr/db";
|
import { asc, eq, like } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { and, eq } from "@homarr/db";
|
import { and, eq } from "@homarr/db";
|
||||||
import { sectionCollapseStates, sections } from "@homarr/db/schema";
|
import { sectionCollapseStates, sections } from "@homarr/db/schema";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||||
import type { ServerSettings } from "@homarr/server-settings";
|
import type { ServerSettings } from "@homarr/server-settings";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Session } from "@homarr/auth";
|
import type { Session } from "@homarr/auth";
|
||||||
import type { Modify } from "@homarr/common/types";
|
import type { Modify } from "@homarr/common/types";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { sendPingRequestAsync } from "@homarr/ping";
|
import { sendPingRequestAsync } from "@homarr/ping";
|
||||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import { radarrReleaseTypes } from "@homarr/integrations/types";
|
import { radarrReleaseTypes } from "@homarr/integrations/types";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Modify } from "@homarr/common/types";
|
import type { Modify } from "@homarr/common/types";
|
||||||
import type { Integration } from "@homarr/db/schema";
|
import type { Integration } from "@homarr/db/schema";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Modify } from "@homarr/common/types";
|
import type { Modify } from "@homarr/common/types";
|
||||||
import type { Integration } from "@homarr/db/schema";
|
import type { Integration } from "@homarr/db/schema";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import { createIntegrationAsync } from "@homarr/integrations";
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import type { StreamSession } from "@homarr/integrations";
|
import type { StreamSession } from "@homarr/integrations";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import type { MinecraftServerStatus } from "@homarr/request-handler/minecraft-server-status";
|
import type { MinecraftServerStatus } from "@homarr/request-handler/minecraft-server-status";
|
||||||
import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
|
import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import SuperJSON from "superjson";
|
import SuperJSON from "superjson";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { eq } from "@homarr/db";
|
import { eq } from "@homarr/db";
|
||||||
import { boards, items } from "@homarr/db/schema";
|
import { boards, items } from "@homarr/db/schema";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { escapeForRegEx } from "@tiptap/react";
|
import { escapeForRegEx } from "@tiptap/react";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import { releasesRequestHandler } from "@homarr/request-handler/releases";
|
import { releasesRequestHandler } from "@homarr/request-handler/releases";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import { createIntegrationAsync } from "@homarr/integrations";
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { fetchStockPriceHandler } from "@homarr/request-handler/stock-price";
|
import { fetchStockPriceHandler } from "@homarr/request-handler/stock-price";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { fetchWithTimeout } from "@homarr/common";
|
import { fetchWithTimeout } from "@homarr/common";
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
import type { AnyZodObject, ZodIntersection, ZodObject } from "zod";
|
import type { ZodIntersection, ZodObject } from "zod/v4";
|
||||||
|
|
||||||
export function convertIntersectionToZodObject<TIntersection extends ZodIntersection<AnyZodObject, AnyZodObject>>(
|
export function convertIntersectionToZodObject<TIntersection extends ZodIntersection<ZodObject, ZodObject>>(
|
||||||
intersection: TIntersection,
|
intersection: TIntersection,
|
||||||
) {
|
) {
|
||||||
const { _def } = intersection;
|
const left = intersection.def.left;
|
||||||
|
const right = intersection.def.right;
|
||||||
|
|
||||||
// Merge the shapes
|
// Merge the shapes
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
const mergedShape = { ...left.def.shape, ...right.def.shape };
|
||||||
const mergedShape = { ..._def.left.shape, ..._def.right.shape };
|
|
||||||
|
|
||||||
// Return a new ZodObject
|
// Return a new ZodObject
|
||||||
return z.object(mergedShape) as unknown as TIntersection extends ZodIntersection<infer TLeft, infer TRight>
|
return z.object(mergedShape) as unknown as TIntersection extends ZodIntersection<infer TLeft, infer TRight>
|
||||||
? TLeft extends AnyZodObject
|
? TLeft extends ZodObject
|
||||||
? TRight extends AnyZodObject
|
? TRight extends ZodObject
|
||||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
? ZodObject<TLeft["shape"] & TRight["shape"]>
|
||||||
ZodObject<TLeft["shape"] & TRight["shape"], any, any, z.infer<TLeft> & z.infer<TRight>>
|
|
||||||
: never
|
: never
|
||||||
: never
|
: never
|
||||||
: never;
|
: never;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
import z from "zod/v4";
|
||||||
|
|
||||||
|
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||||
|
|
||||||
|
describe("convertIntersectionToZodObject should convert zod intersection to zod object", () => {
|
||||||
|
test("should merge two ZodObjects with different properties", () => {
|
||||||
|
const objectA = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
const objectB = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const intersection = objectA.and(objectB);
|
||||||
|
|
||||||
|
const result = convertIntersectionToZodObject(intersection);
|
||||||
|
|
||||||
|
expect(result.def.type).toBe("object");
|
||||||
|
expect(result.shape).toHaveProperty("id");
|
||||||
|
expect(result.shape).toHaveProperty("name");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import type { OpenApiMeta } from "trpc-to-openapi";
|
import type { OpenApiMeta } from "trpc-to-openapi";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod/v4";
|
||||||
|
|
||||||
import type { Session } from "@homarr/auth";
|
import type { Session } from "@homarr/auth";
|
||||||
import { FlattenError } from "@homarr/common";
|
import { FlattenError } from "@homarr/common";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createBooleanSchema, createDurationSchema, createEnv } from "@homarr/core/infrastructure/env";
|
import { createBooleanSchema, createDurationSchema, createEnv } from "@homarr/core/infrastructure/env";
|
||||||
import { supportedAuthProviders } from "@homarr/definitions";
|
import { supportedAuthProviders } from "@homarr/definitions";
|
||||||
@@ -19,7 +19,7 @@ const authProvidersSchema = z
|
|||||||
return false;
|
return false;
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.default("credentials");
|
.default(["credentials"]);
|
||||||
|
|
||||||
const authProviders = authProvidersSchema.safeParse(process.env.AUTH_PROVIDERS).data ?? [];
|
const authProviders = authProvidersSchema.safeParse(process.env.AUTH_PROVIDERS).data ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"ldapts": "8.0.9",
|
"ldapts": "8.0.9",
|
||||||
"next": "15.4.6",
|
"next": "15.5.0",
|
||||||
"next-auth": "5.0.0-beta.29",
|
"next-auth": "5.0.0-beta.29",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"zod": "^3.25.76"
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/bcrypt": "6.0.0",
|
"@types/bcrypt": "6.0.0",
|
||||||
"@types/cookies": "0.9.1",
|
"@types/cookies": "0.9.1",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
import { and, eq } from "@homarr/db";
|
import { and, eq } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CredentialsSignin } from "@auth/core/errors";
|
import { CredentialsSignin } from "@auth/core/errors";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createId, extractErrorMessage } from "@homarr/common";
|
import { createId, extractErrorMessage } from "@homarr/common";
|
||||||
import type { Database, InferInsertModel } from "@homarr/db";
|
import type { Database, InferInsertModel } from "@homarr/db";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { expireDateAfter, generateSessionToken } from "../session";
|
import { expireDateAfter, generateSessionToken } from "../session";
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"undici": "7.13.0"
|
"undici": "7.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"esbuild": "^0.25.8",
|
"esbuild": "^0.25.9",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ export const recreateAdmin = command({
|
|||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.error("Invalid username:");
|
console.error("Invalid username:");
|
||||||
console.error(result.error.errors.map((error) => `- ${error.message}`).join("\n"));
|
console.error(result.error.issues.map((error) => `- ${error.message}`).join("\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { z } from "zod";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { createEnv } from "@homarr/core/infrastructure/env";
|
import { createBooleanSchema, createEnv } from "@homarr/core/infrastructure/env";
|
||||||
|
|
||||||
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
|
const errorSuffix = `, please generate a 64 character secret in hex format or use the following: "${randomBytes(32).toString("hex")}"`;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export const env = createEnv({
|
|||||||
server: {
|
server: {
|
||||||
SECRET_ENCRYPTION_KEY: z
|
SECRET_ENCRYPTION_KEY: z
|
||||||
.string({
|
.string({
|
||||||
required_error: `SECRET_ENCRYPTION_KEY is required${errorSuffix}`,
|
error: `SECRET_ENCRYPTION_KEY is required${errorSuffix}`,
|
||||||
})
|
})
|
||||||
.min(64, {
|
.min(64, {
|
||||||
message: `SECRET_ENCRYPTION_KEY has to be 64 characters${errorSuffix}`,
|
message: `SECRET_ENCRYPTION_KEY has to be 64 characters${errorSuffix}`,
|
||||||
@@ -23,9 +23,11 @@ export const env = createEnv({
|
|||||||
.regex(/^[0-9a-fA-F]{64}$/, {
|
.regex(/^[0-9a-fA-F]{64}$/, {
|
||||||
message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`,
|
message: `SECRET_ENCRYPTION_KEY must only contain hex characters${errorSuffix}`,
|
||||||
}),
|
}),
|
||||||
|
NO_EXTERNAL_CONNECTION: createBooleanSchema(false),
|
||||||
},
|
},
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
|
SECRET_ENCRYPTION_KEY: process.env.SECRET_ENCRYPTION_KEY,
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
NO_EXTERNAL_CONNECTION: process.env.NO_EXTERNAL_CONNECTION,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
"./types": "./src/types.ts",
|
"./types": "./src/types.ts",
|
||||||
"./server": "./src/server.ts",
|
"./server": "./src/server.ts",
|
||||||
"./client": "./src/client.ts",
|
"./client": "./src/client.ts",
|
||||||
"./env": "./env.ts",
|
"./env": "./env.ts"
|
||||||
"./env-validation": "./src/env-validation.ts"
|
|
||||||
},
|
},
|
||||||
"typesVersions": {
|
"typesVersions": {
|
||||||
"*": {
|
"*": {
|
||||||
@@ -31,18 +30,18 @@
|
|||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"next": "15.4.6",
|
"next": "15.5.0",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"undici": "7.13.0",
|
"undici": "7.14.0",
|
||||||
"zod": "^3.25.76",
|
"zod": "^4.0.17",
|
||||||
"zod-validation-error": "^3.5.3"
|
"zod-validation-error": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.33.0",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded 100 of 287 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user