Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
faf2525de5 | ||
|
|
3343468c55 | ||
|
|
27bd93fa8a | ||
|
|
e636e02d6a | ||
|
|
7f474683a5 | ||
|
|
4a07b3f866 | ||
|
|
d23196b430 | ||
|
|
ef40e75818 | ||
|
|
60accd66f6 | ||
|
|
4b6c69d48a | ||
|
|
c73ce1f704 | ||
|
|
ffa629064c | ||
|
|
dde41f6baf | ||
|
|
d32703910e | ||
|
|
a4914d09fd | ||
|
|
ed950e9302 | ||
|
|
bb681f7b5e | ||
|
|
72e976ee5e | ||
|
|
e6be8e1db3 | ||
|
|
5d494111df | ||
|
|
3ee408bf53 | ||
|
|
1b5ccb5293 | ||
|
|
cc6d421e7d | ||
|
|
c8d59d88e5 | ||
|
|
3dbf085e59 | ||
|
|
ff03220960 | ||
|
|
001f7a2a16 | ||
|
|
2780b1e794 | ||
|
|
59c092f800 | ||
|
|
4bc053cc17 | ||
|
|
baa652f314 | ||
|
|
2465676713 | ||
|
|
e45bef317a | ||
|
|
5d0966f466 |
No files matched your search
@@ -31,6 +31,8 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.32.0
|
||||
- 1.31.0
|
||||
- 1.30.1
|
||||
- 1.30.0
|
||||
- 1.29.0
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
matchPackagePatterns: ["^@homarr/"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchPackagePatterns: ["^zod$", "^drizzle-zod$", "^zod-form-data$"],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||
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
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: tibdex/github-app-token@v2
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Build docker image
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Copy env
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Upload Crowdin translations
|
||||
uses: crowdin/github-action@v2
|
||||
|
||||
@@ -53,14 +53,14 @@ jobs:
|
||||
with:
|
||||
private_key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
app_id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 22.18.0
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- name: Install dependencies
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
with:
|
||||
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Next Version
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Update integration list
|
||||
|
||||
on:
|
||||
workflow_dispatch: { }
|
||||
push:
|
||||
paths:
|
||||
- packages/definitions/src/integration.ts
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-readme:
|
||||
concurrency:
|
||||
group: update-integration
|
||||
cancel-in-progress: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
private_key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
app_id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: pnpm run scripts:update-readme-integrations
|
||||
- name: Commit changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
git config --global user.email "210161987+homarr-update-contributors[bot]@users.noreply.github.com"
|
||||
git config --global user.name "Homarr Update Contributors"
|
||||
git add .
|
||||
git commit -m "chore: update integrations list readme"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-integrations-readme
|
||||
base: dev
|
||||
title: "chore: update integrations list readme"
|
||||
delete-branch: true
|
||||
body: |
|
||||
This PR automatically updates the list of integrations of Homarr in the README.md
|
||||
|
||||
- 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
|
||||
@@ -1,3 +1,54 @@
|
||||
## [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)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add info endpoint ([#3737](https://github.com/homarr-labs/homarr/issues/3737)) ([baa652f](https://github.com/homarr-labs/homarr/commit/baa652f31493aad1490cf8e5d50bb04dc0006da1))
|
||||
* system resources widget ([#3538](https://github.com/homarr-labs/homarr/issues/3538)) ([3ee408b](https://github.com/homarr-labs/homarr/commit/3ee408bf53c45f0b2abf04449b3653ed3e9cf225))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @gitbeaker/rest to ^43.4.0 ([#3806](https://github.com/homarr-labs/homarr/issues/3806)) ([c73ce1f](https://github.com/homarr-labs/homarr/commit/c73ce1f704aa1fcbc6beac4cfa80b080b6800373))
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.6.0 ([#3797](https://github.com/homarr-labs/homarr/issues/3797)) ([a4914d0](https://github.com/homarr-labs/homarr/commit/a4914d09fd6c9fa6a602f9f76b71c096369a34cd))
|
||||
* **deps:** update dependency @ianvs/prettier-plugin-sort-imports to ^4.6.1 ([#3809](https://github.com/homarr-labs/homarr/issues/3809)) ([ef40e75](https://github.com/homarr-labs/homarr/commit/ef40e7581896df4371c1f13487f3830bb57f6886))
|
||||
* **deps:** update dependency jotai to ^2.13.0 ([#3763](https://github.com/homarr-labs/homarr/issues/3763)) ([c8d59d8](https://github.com/homarr-labs/homarr/commit/c8d59d88e5872783e767156eb063f329901fff6f))
|
||||
* **deps:** update dependency sass to ^1.90.0 ([#3794](https://github.com/homarr-labs/homarr/issues/3794)) ([bb681f7](https://github.com/homarr-labs/homarr/commit/bb681f7b5ebb4a0858dac40eacfcc809c04b3d5e))
|
||||
* **deps:** update dependency trpc-to-openapi to ^2.4.0 ([#3734](https://github.com/homarr-labs/homarr/issues/3734)) ([e45bef3](https://github.com/homarr-labs/homarr/commit/e45bef317a6ffae05d7a1685b1e6164e2ed09921))
|
||||
* **deps:** update dependency typescript-eslint to ^8.39.0 ([#3781](https://github.com/homarr-labs/homarr/issues/3781)) ([1b5ccb5](https://github.com/homarr-labs/homarr/commit/1b5ccb529358836682a1cccd4a3da14f612884b9))
|
||||
* **deps:** update dependency video.js to ^8.23.4 ([#3808](https://github.com/homarr-labs/homarr/issues/3808)) ([60accd6](https://github.com/homarr-labs/homarr/commit/60accd66f635ddb2f8c7a73faa865ee47aa40721))
|
||||
* **deps:** update mantine monorepo to ^8.2.3 ([#3789](https://github.com/homarr-labs/homarr/issues/3789)) ([e6be8e1](https://github.com/homarr-labs/homarr/commit/e6be8e1db3d843e3d0e5b6d54c64999fd45f103d))
|
||||
* **deps:** update mantine monorepo to ^8.2.4 ([#3803](https://github.com/homarr-labs/homarr/issues/3803)) ([ffa6290](https://github.com/homarr-labs/homarr/commit/ffa629064cdf202849d6f9c0ce645c6077c63e49))
|
||||
* **deps:** update nextjs monorepo to v15.4.6 ([#3799](https://github.com/homarr-labs/homarr/issues/3799)) ([d327039](https://github.com/homarr-labs/homarr/commit/d32703910e0228a70a55a84ab712d6bd58fe7149))
|
||||
* **deps:** update tanstack-query monorepo to ^5.84.2 ([#3810](https://github.com/homarr-labs/homarr/issues/3810)) ([d23196b](https://github.com/homarr-labs/homarr/commit/d23196b43034349d92cb9277633dd06b15ee0b7d))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.5.0 ([#3738](https://github.com/homarr-labs/homarr/issues/3738)) ([4bc053c](https://github.com/homarr-labs/homarr/commit/4bc053cc17d5a38db4255f9913e389fe86831676))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.5.1 ([#3796](https://github.com/homarr-labs/homarr/issues/3796)) ([ed950e9](https://github.com/homarr-labs/homarr/commit/ed950e9302450f33150d9800bc66e712bf8f6724))
|
||||
* **deps:** update trpc monorepo to ^11.4.4 ([#3777](https://github.com/homarr-labs/homarr/issues/3777)) ([cc6d421](https://github.com/homarr-labs/homarr/commit/cc6d421e7db52b994e9e62fa1f828c72b5a20d72))
|
||||
* **groups:** saving permissions with none activated causes error ([#3739](https://github.com/homarr-labs/homarr/issues/3739)) ([59c092f](https://github.com/homarr-labs/homarr/commit/59c092f8000f9a4e9fd70bde4176697dabb2e1c9))
|
||||
* not hardcode hero banner scroll ([#3741](https://github.com/homarr-labs/homarr/issues/3741)) ([001f7a2](https://github.com/homarr-labs/homarr/commit/001f7a2a165fade9deaafb4cb319aa27926685e5))
|
||||
* **opnsense:** switch secrets to api-key key and secret for easier understanding ([#3744](https://github.com/homarr-labs/homarr/issues/3744)) ([7f47468](https://github.com/homarr-labs/homarr/commit/7f474683a5e5b9ada978c055e57cd2b4114e17e0))
|
||||
* some integrations handle parse errors wrong ([#3746](https://github.com/homarr-labs/homarr/issues/3746)) ([72e976e](https://github.com/homarr-labs/homarr/commit/72e976ee5ea28e549b7674c71b9a284e905e455b))
|
||||
|
||||
## [1.31.0](https://github.com/homarr-labs/homarr/compare/v1.30.1...v1.31.0) (2025-08-01)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.1-alpine AS base
|
||||
FROM node:22.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -50,21 +50,21 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^8.2.2",
|
||||
"@mantine/core": "^8.2.2",
|
||||
"@mantine/dropzone": "^8.2.2",
|
||||
"@mantine/hooks": "^8.2.2",
|
||||
"@mantine/modals": "^8.2.2",
|
||||
"@mantine/tiptap": "^8.2.2",
|
||||
"@mantine/colors-generator": "^8.2.4",
|
||||
"@mantine/core": "^8.2.4",
|
||||
"@mantine/dropzone": "^8.2.4",
|
||||
"@mantine/hooks": "^8.2.4",
|
||||
"@mantine/modals": "^8.2.4",
|
||||
"@mantine/tiptap": "^8.2.4",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@tanstack/react-query-devtools": "^5.84.1",
|
||||
"@tanstack/react-query-next-experimental": "^5.84.1",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/next": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@tanstack/react-query-devtools": "^5.85.3",
|
||||
"@tanstack/react-query-next-experimental": "^5.85.3",
|
||||
"@trpc/client": "^11.4.4",
|
||||
"@trpc/next": "^11.4.4",
|
||||
"@trpc/react-query": "^11.4.4",
|
||||
"@trpc/server": "^11.4.4",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -74,33 +74,33 @@
|
||||
"dotenv": "^17.2.1",
|
||||
"flag-icons": "^7.5.0",
|
||||
"glob": "^11.0.3",
|
||||
"jotai": "^2.12.5",
|
||||
"jotai": "^2.13.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.4.5",
|
||||
"next": "15.4.6",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.89.2",
|
||||
"sass": "^1.90.0",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.25.76"
|
||||
"zod": "^4.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.17.0",
|
||||
"@types/node": "^22.17.2",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react": "19.1.10",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint": "^9.33.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
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 { useZodForm } from "@homarr/form";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { signIn } from "@homarr/auth/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Button, Card, Stack, TextInput } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { startTransition } from "react";
|
||||
import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { signIn } from "@homarr/auth/client";
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
import { Box, Grid, GridCol, Group, Image, Stack, Title } from "@mantine/core";
|
||||
|
||||
import { splitToNChunks } from "@homarr/common";
|
||||
import { integrationDefs } from "@homarr/definitions";
|
||||
|
||||
import classes from "./hero-banner.module.css";
|
||||
|
||||
const icons = [
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sabnzbd.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/deluge.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lidarr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/pihole.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/dashdot.png",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyfin.svg",
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/homeassistant.svg",
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/freshrss.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/readarr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/transmission.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qbittorrent.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/nzbget.png",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/openmediavault.svg",
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/docker.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyseerr.svg",
|
||||
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/adguardhome.svg",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/tdarr.png",
|
||||
"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/prowlarr.svg",
|
||||
];
|
||||
const icons = Object.values(integrationDefs)
|
||||
.filter((int) => int.name !== "Mock")
|
||||
.map((int) => int.iconUrl);
|
||||
|
||||
const countIconGroups = 3;
|
||||
const animationDurationInSeconds = 12;
|
||||
const animationDurationInSeconds = icons.length;
|
||||
const arrayInChunks = splitToNChunks(icons, countIconGroups);
|
||||
|
||||
export const HeroBanner = () => {
|
||||
@@ -61,12 +40,26 @@ export const HeroBanner = () => {
|
||||
}}
|
||||
>
|
||||
{arrayInChunks[columnIndex]?.map((icon, index) => (
|
||||
<Image key={`grid-column-${columnIndex}-scroll-1-${index}`} src={icon} radius="md" w={50} h={50} />
|
||||
<Image
|
||||
key={`grid-column-${columnIndex}-scroll-1-${index}`}
|
||||
src={icon}
|
||||
radius="md"
|
||||
fit={"contain"}
|
||||
w={50}
|
||||
h={50}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* This is used for making the animation seem seamless */}
|
||||
{arrayInChunks[columnIndex]?.map((icon, index) => (
|
||||
<Image key={`grid-column-${columnIndex}-scroll-2-${index}`} src={icon} radius="md" w={50} h={50} />
|
||||
<Image
|
||||
key={`grid-column-${columnIndex}-scroll-2-${index}`}
|
||||
src={icon}
|
||||
radius="md"
|
||||
fit={"contain"}
|
||||
w={50}
|
||||
h={50}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</GridCol>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconBox, IconPencil } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
|
||||
@@ -19,4 +19,6 @@ export const integrationSecretIcons = {
|
||||
tokenId: IconGrid3x3,
|
||||
personalAccessToken: IconPasswordUser,
|
||||
topic: IconMessage,
|
||||
opnsenseApiKey: IconKey,
|
||||
opnsenseApiSecret: IconPassword,
|
||||
} satisfies Record<IntegrationSecretKind, TablerIcon>;
|
||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
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 { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { Container, Group, Stack, Title } from "@mantine/core";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { IconExternalLink } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
import type { SegmentedControlItem } 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 type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { searchEngineTypes } from "@homarr/definitions";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||
|
||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconPencil, IconSearch } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
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 { Box, Title } from "@mantine/core";
|
||||
import { Stack, Title } from "@mantine/core";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { JobsList } from "./_components/jobs-list";
|
||||
import { TasksTable } from "./_components/tasks-table";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const session = await auth();
|
||||
@@ -27,10 +28,15 @@ export default async function TasksPage() {
|
||||
}
|
||||
|
||||
const jobs = await api.cronJobs.getJobs();
|
||||
const tTasks = await getScopedI18n("management.page.tool.tasks");
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Title mb={"md"}>Tasks</Title>
|
||||
<JobsList initialJobs={jobs} />
|
||||
</Box>
|
||||
<>
|
||||
<DynamicBreadcrumb />
|
||||
<Stack>
|
||||
<Title order={1}>{tTasks("title")}</Title>
|
||||
<TasksTable initialJobs={jobs} />
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button, Group, Radio, Stack } from "@mantine/core";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import dayjs from "dayjs";
|
||||
import localeData from "dayjs/plugin/localeData";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { clientApi } from "@homarr/api/client";
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { useListState } from "@mantine/hooks";
|
||||
import { IconPlus, IconUserCheck } from "@tabler/icons-react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { everyoneGroup, groupPermissions } from "@homarr/definitions";
|
||||
|
||||
@@ -21,6 +21,12 @@ interface BoardItemContentProps {
|
||||
item: SectionItem;
|
||||
}
|
||||
|
||||
const getOverflowFromKind = (kind: SectionItem["kind"]) => {
|
||||
if (kind === "iframe") return "hidden";
|
||||
if (kind === "systemResources") return "visible";
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
||||
const { ref, width, height } = useElementSize<HTMLDivElement>();
|
||||
const board = useRequiredBoard();
|
||||
@@ -41,7 +47,7 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
||||
root: {
|
||||
"--opacity": board.opacity / 100,
|
||||
containerType: "size",
|
||||
overflow: item.kind === "iframe" ? "hidden" : undefined,
|
||||
overflow: getOverflowFromKind(item.kind),
|
||||
"--border-color": item.advancedOptions.borderColor !== "" ? item.advancedOptions.borderColor : undefined,
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { Button, Grid, Group, NumberInput, Stack } from "@mantine/core";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import type { GridStack } from "@homarr/gridstack";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { useZodForm } from "@homarr/form";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Group, Stack, TextInput } from "@mantine/core";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { createModal } from "@homarr/modals";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import type { z } from "zod";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||
import type { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
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 { createModal } from "@homarr/modals";
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^17.2.1",
|
||||
"fastify": "^5.4.0",
|
||||
"fastify": "^5.5.0",
|
||||
"superjson": "2.2.2",
|
||||
"undici": "7.13.0"
|
||||
},
|
||||
@@ -45,12 +45,12 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.17.0",
|
||||
"@types/node": "^22.17.2",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.32.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "^9.33.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "4.20.3",
|
||||
"tsx": "4.20.4",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"tsx": "4.20.3",
|
||||
"tsx": "4.20.4",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -34,8 +34,8 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"esbuild": "^0.25.8",
|
||||
"eslint": "^9.32.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"eslint": "^9.33.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<!-- Project Title -->
|
||||
[](https://homarr.dev/)
|
||||

|
||||
|
||||
<!-- Badges -->
|
||||
<p align="center">
|
||||
@@ -20,10 +19,6 @@
|
||||
|
||||
<!-- Links -->
|
||||
<p align="center">
|
||||
<a href="https://demo.homarr.dev/">
|
||||
<strong>Demo ✨</strong>
|
||||
</a>
|
||||
•
|
||||
<a href="https://homarr.dev/docs/category/installation-1/">
|
||||
<strong>Install 💻</strong>
|
||||
</a> •
|
||||
@@ -35,8 +30,7 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
[](https://homarr.dev/)
|
||||

|
||||
|
||||
- 🖌️ Highly customizable with an extensive drag and drop grid system
|
||||
- ✨ Integrates seamlessly with your favorite self-hosted applications
|
||||
@@ -53,44 +47,268 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
[](https://homarr.dev/docs/category/widgets)
|
||||

|
||||
|
||||
Homarr has a [built-in collection of widgets and integrations](https://homarr.dev/docs/category/integrations), that connect to your applications and enable you to control them directly from the dashboard.
|
||||
<!-- AUTO_GENERATE_INTEGRATION_LIST_START -->
|
||||
|
||||
- 📥 **Torrent clients**
|
||||
- [Deluge](https://homarr.dev/docs/integrations/torrent#deluge)
|
||||
- [Transmission](https://homarr.dev/docs/integrations/torrent#transmission)
|
||||
- [qBittorent](https://homarr.dev/docs/integrations/torrent#qbittorrent-integration)
|
||||
- 📥 **Usenet clients**
|
||||
- [SABnzbd](https://homarr.dev/docs/integrations/usenet#sabnzbd)
|
||||
- [NZBGet](https://homarr.dev/docs/integrations/usenet#nzbget)
|
||||
- 📺 **Media servers**
|
||||
- [Plex](https://homarr.dev/docs/integrations/media-server/#plex)
|
||||
- [Jellyfin](https://homarr.dev/docs/integrations/media-server#jellyfin-and-emby)
|
||||
- 📚 **Media collection managers**
|
||||
- [Sonarr](https://homarr.dev/docs/integrations/servarr#sonarr)
|
||||
- [Radarr](https://homarr.dev/docs/integrations/servarr#radarr)
|
||||
- [Lidarr](https://homarr.dev/docs/integrations/servarr#lidarr)
|
||||
- [Readarr](https://homarr.dev/docs/integrations/servarr#readarr)
|
||||
- 🎞️ **Media request managers**
|
||||
- [Overseerr](https://homarr.dev/docs/integrations/media-requester)
|
||||
- [Jellyseerr](https://homarr.dev/docs/integrations/media-requester)
|
||||
- 🚫 **DNS ad-blockers**
|
||||
- [Pihole](https://homarr.dev/docs/integrations/dns#pihole)
|
||||
- [AdGuard Home](https://homarr.dev/docs/integrations/dns#adguard-home)
|
||||
- 🖥️ **Monitoring**
|
||||
- [Dash.](https://homarr.dev/docs/integrations/hardware)
|
||||
- [OpenMediaVault.](https://homarr.dev/docs/integrations/hardware/#openmediavault)
|
||||
- [Proxmox.](https://homarr.dev/docs/integrations/hardware/#proxmox)
|
||||
- 🐳 **Container management**:
|
||||
- [Docker](https://homarr.dev/docs/integrations/containers)
|
||||
<div align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/adguard-home" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/adguard-home.svg" alt="AdGuard Home" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">AdGuard<br/>Home</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/aria2" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg" alt="Aria2" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Aria2</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/codeberg" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/codeberg.svg" alt="Codeberg" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Codeberg</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/dash-dot" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/dashdot.png" alt="Dash." width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Dash.</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/deluge" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/deluge.svg" alt="Deluge" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Deluge</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/docker-hub" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg" alt="Docker Hub" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Docker<br/>Hub</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/emby" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg" alt="Emby" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Emby</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/github" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg" alt="Github" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Github</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/github" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg" alt="GitHub Container Registry" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">GitHub<br/>Container<br/>Registry</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/gitlab" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/gitlab.svg" alt="Gitlab" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Gitlab</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/home-assistant" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg" alt="Home Assistant" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Home<br/>Assistant</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/jellyfin" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg" alt="Jellyfin" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Jellyfin</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/jellyseerr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyseerr.svg" alt="Jellyseerr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Jellyseerr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/lidarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg" alt="Lidarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Lidarr</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/linux-server-io" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg" alt="LinuxServer.io" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">LinuxServer.io</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/nextcloud" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nextcloud.svg" alt="Nextcloud" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Nextcloud</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/npm" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg" alt="NPM" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">NPM</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/ntfy" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg" alt="ntfy" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">ntfy</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/nzbget" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg" alt="NZBGet" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">NZBGet</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/open-media-vault" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/openmediavault.svg" alt="OpenMediaVault" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">OpenMediaVault</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/opnsense" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg" alt="OPNsense" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">OPNsense</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/overseerr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg" alt="Overseerr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Overseerr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/pi-hole" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg" alt="Pi-hole" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Pi-hole</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/plex" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg" alt="Plex" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Plex</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/prowlarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg" alt="Prowlarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Prowlarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/proxmox" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/proxmox.svg" alt="Proxmox" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Proxmox</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/q-bittorent" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg" alt="qBittorrent" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">qBittorrent</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/quay" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png" alt="Quay" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Quay</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr><td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/radarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg" alt="Radarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Radarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/readarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/readarr.svg" alt="Readarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Readarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/sabnzbd" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg" alt="SABnzbd" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">SABnzbd</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/sonarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg" alt="Sonarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Sonarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/tdarr" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png" alt="Tdarr" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Tdarr</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/transmission" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/transmission.svg" alt="Transmission" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Transmission</p>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://homarr.dev/docs/integrations/unifi-controller" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png" alt="Unifi Controller" width="90" height="90" />
|
||||
<br/>
|
||||
<p align="center">Unifi<br/>Controller</p>
|
||||
</a>
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- AUTO_GENERATE_INTEGRATION_LIST_END -->
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
[](https://homarr.dev/docs/category/installation-1)
|
||||
|
||||
Since we are updating Homarr very frequently, we recommend reading our official installation guides:
|
||||

|
||||
|
||||
<h2>
|
||||
<a href="https://homarr.dev/docs/category/installation-1/">
|
||||
@@ -101,12 +319,15 @@ Since we are updating Homarr very frequently, we recommend reading our official
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
[](https://opencollective.com/homarr)
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
Homarr is a free to use open source project that is maintained by volunteers and developers from all over the world. We publish under the ``Apache License 2.0`` license which allows commercial usage. We invest multiple hours daily in to providing support, developing Homarr, integrating to third party software and more. We also pay for licensing and server hosting fees.
|
||||
Please consider to help us cover these costs to enable the future development of Homarr. Thank you!
|
||||
Homarr is a free-to-use open source project maintained by volunteers and developers from all over the world.
|
||||
We publish under the `Apache License 2.0` license which allows commercial usage.
|
||||
We invest multiple hours daily in providing support, developing Homarr, integrating to third party software and more.
|
||||
We also pay for licensing and server hosting fees.
|
||||
Please consider helping us cover these costs to enable the future development of Homarr. Thank you!
|
||||
|
||||
<h2>
|
||||
<a href="https://opencollective.com/homarr">
|
||||
@@ -117,9 +338,9 @@ Please consider to help us cover these costs to enable the future development of
|
||||
You can also support us by helping with [translating the entire project](https://homarr.dev/docs/community/translations) to as many languages as possible or contributing directly to the code or documentation. Please read our [Contribution Guidelines](/CONTRIBUTING.md). All contributions, regardless of their size or scope, are welcome and highly appreciated! Thank you ❤️
|
||||
|
||||
## Sponsors
|
||||
Thanks to your generous sponsors we can continue to build Homarr. Check them out for high quality and easy to use development tools.
|
||||
|
||||
Thanks to your generous sponsors, we can continue to build Homarr. Check them out for high-quality and easy-to-use development tools.
|
||||
Feel free to contact us at homarr-labs@proton.me if you wish to become a sponsor.
|
||||
|
||||
[](https://argos-ci.com?utm_source=%5Bhomarr%5D&utm_campaign=oss) \
|
||||
[](https://www.pikapods.com/pods?run=homarr-v1)
|
||||
|
||||
|
Before Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.31.0",
|
||||
"version": "1.33.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -27,7 +27,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"devDependencies": {
|
||||
@@ -35,12 +36,12 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^13.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/release-notes-generator": "^14.0.3",
|
||||
"@testcontainers/redis": "^11.4.0",
|
||||
"@turbo/gen": "^2.5.5",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@testcontainers/redis": "^11.5.1",
|
||||
"@turbo/gen": "^2.5.6",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
@@ -48,8 +49,8 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^24.2.7",
|
||||
"testcontainers": "^11.4.0",
|
||||
"turbo": "^2.5.5",
|
||||
"testcontainers": "^11.5.1",
|
||||
"turbo": "^2.5.6",
|
||||
"typescript": "^5.9.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
@@ -73,14 +74,14 @@
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.13.0"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch"
|
||||
},
|
||||
"allowUnusedPatches": true,
|
||||
"ignoredBuiltDependencies": [
|
||||
"@scarf/scarf",
|
||||
"core-js-pure",
|
||||
"protobufjs"
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint": "^9.33.0",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "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-status": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
@@ -41,24 +42,24 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@trpc/tanstack-react-query": "^11.4.3",
|
||||
"@tanstack/react-query": "^5.85.3",
|
||||
"@trpc/client": "^11.4.4",
|
||||
"@trpc/react-query": "^11.4.4",
|
||||
"@trpc/server": "^11.4.4",
|
||||
"@trpc/tanstack-react-query": "^11.4.4",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.4.5",
|
||||
"next": "15.4.6",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.3.2",
|
||||
"zod": "^3.25.76"
|
||||
"trpc-to-openapi": "^3.0.0",
|
||||
"zod": "^4.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint": "^9.33.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { shouldSkipEnvValidation } from "@homarr/common/env-validation";
|
||||
import { createEnv } from "@homarr/core/infrastructure/env";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
@@ -10,6 +9,4 @@ export const env = createEnv({
|
||||
runtimeEnv: {
|
||||
KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME,
|
||||
},
|
||||
skipValidation: shouldSkipEnvValidation(),
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { hasQueryAccessToIntegrationsAsync } from "@homarr/auth/server";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema";
|
||||
|
||||
@@ -8,6 +8,7 @@ import { groupRouter } from "./router/group";
|
||||
import { homeRouter } from "./router/home";
|
||||
import { iconsRouter } from "./router/icons";
|
||||
import { importRouter } from "./router/import/import-router";
|
||||
import { infoRouter } from "./router/info";
|
||||
import { integrationRouter } from "./router/integration/integration-router";
|
||||
import { inviteRouter } from "./router/invite";
|
||||
import { kubernetesRouter } from "./router/kubernetes/router/kubernetes-router";
|
||||
@@ -47,6 +48,7 @@ export const appRouter = createTRPCRouter({
|
||||
media: mediaRouter,
|
||||
updateChecker: updateCheckerRouter,
|
||||
certificates: certificateRouter,
|
||||
info: infoRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq, inArray, like } from "@homarr/db";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||
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) =>
|
||||
z.object({
|
||||
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);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { X509Certificate } from "node:crypto";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { zfd } from "zod-form-data";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { trustedCertificateHostnames } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates";
|
||||
import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||
|
||||
@@ -16,7 +16,7 @@ export const certificateRouter = createTRPCRouter({
|
||||
.requiresPermission("admin")
|
||||
.input(
|
||||
zfd.formData({
|
||||
file: zfd.file().superRefine(superRefineCertificateFile),
|
||||
file: zfd.file().check(checkCertificateFile),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
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 { DockerSingleton } from "@homarr/docker";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
@@ -277,12 +277,14 @@ export const groupRouter = createTRPCRouter({
|
||||
|
||||
await ctx.db.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId));
|
||||
|
||||
await ctx.db.insert(groupPermissions).values(
|
||||
input.permissions.map((permission) => ({
|
||||
groupId: input.groupId,
|
||||
permission,
|
||||
})),
|
||||
);
|
||||
if (input.permissions.length > 0) {
|
||||
await ctx.db.insert(groupPermissions).values(
|
||||
input.permissions.map((permission) => ({
|
||||
groupId: input.groupId,
|
||||
permission,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}),
|
||||
transferOwnership: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { analyseOldmarrImportForRouterAsync, analyseOldmarrImportInputSchema } from "@homarr/old-import/analyse";
|
||||
import {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import z from "zod/v4";
|
||||
|
||||
import packageJson from "../../../../package.json";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const infoRouter = createTRPCRouter({
|
||||
getInfo: protectedProcedure
|
||||
.input(z.void())
|
||||
.output(z.object({ version: z.string() }))
|
||||
.meta({ openapi: { method: "GET", path: "/api/info", tags: ["info"] } })
|
||||
.query(() => {
|
||||
return {
|
||||
version: packageJson.version,
|
||||
};
|
||||
}),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId, objectEntries } from "@homarr/common";
|
||||
import { decryptSecret, encryptSecret } from "@homarr/common/server";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq } from "@homarr/db";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import z from "zod";
|
||||
import z from "zod/v4";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { logLevels } from "@homarr/log/constants";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
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 { onboardingSteps } from "@homarr/definitions";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import { asc, eq, like } from "@homarr/db";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { and, eq } from "@homarr/db";
|
||||
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 type { ServerSettings } from "@homarr/server-settings";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
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 { radarrReleaseTypes } from "@homarr/integrations/types";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import type { Integration } from "@homarr/db/schema";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import type { Integration } from "@homarr/db/schema";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import type { StreamSession } from "@homarr/integrations";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import SuperJSON from "superjson";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { eq } from "@homarr/db";
|
||||
import { boards, items } from "@homarr/db/schema";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { escapeForRegEx } from "@tiptap/react";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
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";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
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";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
|
||||
|
||||