Compare commits
246
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c1a5c92e8 | ||
|
|
0425bec2ca | ||
|
|
06da6191cc | ||
|
|
aec6bb18c5 | ||
|
|
f9da71a19b | ||
|
|
b83c8b6546 | ||
|
|
0e4189f693 | ||
|
|
1cf010e9fb | ||
|
|
d670b69396 | ||
|
|
7e58858353 | ||
|
|
9ed55d57dd | ||
|
|
8cac0b11c1 | ||
|
|
5f011e8456 | ||
|
|
9e1b529288 | ||
|
|
4ad115dd2d | ||
|
|
27209a5dde | ||
|
|
ed33748a49 | ||
|
|
7a21e2a9c3 | ||
|
|
f66469d797 | ||
|
|
c4a0c818d0 | ||
|
|
ef016061ae | ||
|
|
3492700e6b | ||
|
|
590d316671 | ||
|
|
6e9d20e651 | ||
|
|
ed829fddfa | ||
|
|
8747d5f93e | ||
|
|
ed8b8a40eb | ||
|
|
7bc3bbb8c0 | ||
|
|
af35a50fa0 | ||
|
|
74943ef9bc | ||
|
|
5dd7f839ee | ||
|
|
d8c6c0f978 | ||
|
|
75b2d1bd1b | ||
|
|
7c12a9ce48 | ||
|
|
9033acc66c | ||
|
|
a42dc34d34 | ||
|
|
db3d58821e | ||
|
|
076526293d | ||
|
|
2443f1cd3f | ||
|
|
3864727502 | ||
|
|
a56bf0f32d | ||
|
|
e687bc8c92 | ||
|
|
64c7512283 | ||
|
|
398c88f2c6 | ||
|
|
34d976eeea | ||
|
|
757d59854e | ||
|
|
2155f13f24 | ||
|
|
d893515b93 | ||
|
|
30107e4e76 | ||
|
|
8c51a3769e | ||
|
|
77884a3179 | ||
|
|
d8a7477039 | ||
|
|
8e1dcecf64 | ||
|
|
c892731479 | ||
|
|
62baaf7813 | ||
|
|
8a5ad5861b | ||
|
|
d18d07d2f1 | ||
|
|
fb348044fb | ||
|
|
f3608b26a0 | ||
|
|
73bed51df8 | ||
|
|
ab9aca8b60 | ||
|
|
dcde2819eb | ||
|
|
abd16354bc | ||
|
|
6ac352c520 | ||
|
|
19f9769d9e | ||
|
|
f0b2486c8a | ||
|
|
2443b89b46 | ||
|
|
915c8a3501 | ||
|
|
a2bb53a8b8 | ||
|
|
f56a05bd8e | ||
|
|
6051b17401 | ||
|
|
29eed08408 | ||
|
|
bd74fe4a61 | ||
|
|
b88e8861e1 | ||
|
|
39af3ec7e6 | ||
|
|
7f50be3dbb | ||
|
|
b6b7bea1dd | ||
|
|
9bde1dfbd6 | ||
|
|
c2712ed7f1 | ||
|
|
16be7ece33 | ||
|
|
b0a46186d1 | ||
|
|
d0385a4480 | ||
|
|
3d6a3594a3 | ||
|
|
641ee18278 | ||
|
|
2e538bf40d | ||
|
|
1c0a94a6dc | ||
|
|
3a3dc449de | ||
|
|
d6b156b8ee | ||
|
|
fc4e31fb42 | ||
|
|
b82b22431b | ||
|
|
510cba295a | ||
|
|
56d838dafc | ||
|
|
eae9f0438c | ||
|
|
b81f729bfd | ||
|
|
86b83c5425 | ||
|
|
bbc4589c9e | ||
|
|
ea9dd716f0 | ||
|
|
4383da38ef | ||
|
|
58cb65cb3a | ||
|
|
89380f81dd | ||
|
|
e01571b221 | ||
|
|
49c266374d | ||
|
|
577950d20e | ||
|
|
82bf71b073 | ||
|
|
e26883acfa | ||
|
|
8ee747cde8 | ||
|
|
faa7e55b1b | ||
|
|
ce021a89ac | ||
|
|
55fb227305 | ||
|
|
39e424563a | ||
|
|
e29c79a902 | ||
|
|
a19c9d88ca | ||
|
|
1fb97ee9c6 | ||
|
|
f69e545d64 | ||
|
|
52df56b19d | ||
|
|
0f66bfd6c1 | ||
|
|
842c5a0b4c | ||
|
|
ce9134dee2 | ||
|
|
9ce9be6b7e | ||
|
|
7ec22411a6 | ||
|
|
d3ae06e515 | ||
|
|
d09a4f8468 | ||
|
|
e1a245ff2d | ||
|
|
bf4396917e | ||
|
|
d059023b51 | ||
|
|
d9cbd5427d | ||
|
|
c56d9d6894 | ||
|
|
3ea49f0391 | ||
|
|
e2f8f5f081 | ||
|
|
f57037b568 | ||
|
|
84803aeefb | ||
|
|
5f47298f3d | ||
|
|
de858011fc | ||
|
|
12f8e2b83a | ||
|
|
78b6e520ac | ||
|
|
bcaa93f90b | ||
|
|
15e6612697 | ||
|
|
eef06dd5a3 | ||
|
|
84e044f427 | ||
|
|
609ab9976a | ||
|
|
28f570aa79 | ||
|
|
5b4f03da35 | ||
|
|
6960ac15e9 | ||
|
|
f1ee76c99c | ||
|
|
9a9ee5a9d7 | ||
|
|
7debe34e24 | ||
|
|
0d67c5a451 | ||
|
|
2db0fe15fb | ||
|
|
4b85afbb87 | ||
|
|
536f82af04 | ||
|
|
445e191490 | ||
|
|
52c624addb | ||
|
|
91f9329773 | ||
|
|
4f1fc4d616 | ||
|
|
8bee0174e9 | ||
|
|
45dfe3b9ab | ||
|
|
5078c300f8 | ||
|
|
065b78fa3e | ||
|
|
39900090b3 | ||
|
|
2af2d59b8f | ||
|
|
d0450b5153 | ||
|
|
5f588ae287 | ||
|
|
066fb038b5 | ||
|
|
ece436a1cc | ||
|
|
4da2feda35 | ||
|
|
0a05bbed8d | ||
|
|
420c0c30e4 | ||
|
|
8600c63bab | ||
|
|
2a5398bec0 | ||
|
|
c751feedf7 | ||
|
|
cbb5e301dc | ||
|
|
ea8f07e2bd | ||
|
|
fb18b975b2 | ||
|
|
4692e7ce3a | ||
|
|
dd57ee2102 | ||
|
|
2ae83458e6 | ||
|
|
5a1967bdd4 | ||
|
|
a4f0eda4a0 | ||
|
|
1e07ce5c96 | ||
|
|
176222f56b | ||
|
|
d6ce320eb2 | ||
|
|
094f23778c | ||
|
|
ec9f3cc5ad | ||
|
|
7ff115465a | ||
|
|
58058f2ea6 | ||
|
|
6881b8355c | ||
|
|
7837b6fa6b | ||
|
|
f263388f71 | ||
|
|
9aecb2ae13 | ||
|
|
1d691c0994 | ||
|
|
baf2b09327 | ||
|
|
3d93ae1246 | ||
|
|
df66cf25c7 | ||
|
|
15ed1e51fd | ||
|
|
9abce89a14 | ||
|
|
7ffb90abd2 | ||
|
|
4a65d2ae87 | ||
|
|
4857b72804 | ||
|
|
8134b168e1 | ||
|
|
583b1d2935 | ||
|
|
6189daf6a4 | ||
|
|
569293275d | ||
|
|
d6ced037df | ||
|
|
8d2b2ee74a | ||
|
|
c5b4cf9a0e | ||
|
|
2316f6e79d | ||
|
|
16094876fa | ||
|
|
6cb9024781 | ||
|
|
a68eee24eb | ||
|
|
2d4832dc96 | ||
|
|
e29c46cc5a | ||
|
|
85e74691f2 | ||
|
|
ed333e1cbb | ||
|
|
e0e493e2f9 | ||
|
|
d13effdda0 | ||
|
|
7e46c71187 | ||
|
|
4e32e81c54 | ||
|
|
c5266a7a42 | ||
|
|
680cbc69d8 | ||
|
|
e22eee271c | ||
|
|
441cc692f9 | ||
|
|
c9769bf530 | ||
|
|
5035c26dbd | ||
|
|
4d8beac7d3 | ||
|
|
824f31d6aa | ||
|
|
90ceaf5fe8 | ||
|
|
82eac2dcc1 | ||
|
|
ab41b76a66 | ||
|
|
1f474c9ee7 | ||
|
|
0c15dc13fa | ||
|
|
992c48fdde | ||
|
|
7ccaabbd97 | ||
|
|
030b84674f | ||
|
|
5fe43eb363 | ||
|
|
db8440bddf | ||
|
|
f324f58e79 | ||
|
|
ea30c818c6 | ||
|
|
c20522946c | ||
|
|
15e061cf03 | ||
|
|
93c008ff4f | ||
|
|
8598159894 | ||
|
|
2fb7bde900 | ||
|
|
7567842c4c | ||
|
|
d16571d4c4 | ||
|
|
905f795b56 | ||
|
|
28ceb37cbf |
No files matched your search
@@ -1,197 +0,0 @@
|
||||
---
|
||||
name: Revert Demo MCP
|
||||
overview: Revert the demo-specific MCP accommodations (API key bypass, read-only filtering, demo UI) since demo MCP is an edge case with poor ROI. Replace with a clear "MCP not available in demo mode" message. Keep the core MCP feature intact for real instances.
|
||||
todos:
|
||||
- id: revert-backend
|
||||
content: Revert demoSafe procedure, apiKeys demo bypass, isDemoMode export from mcp.ts
|
||||
status: pending
|
||||
- id: revert-mcp-route
|
||||
content: Remove isDemoMode filtering and demo SERVER_INSTRUCTIONS from MCP route
|
||||
status: pending
|
||||
- id: revert-ui
|
||||
content: Remove isDemoMode prop plumbing; add demo-not-supported Alert via info.isDemoMode
|
||||
status: pending
|
||||
- id: verify
|
||||
content: Run lint + typecheck; smoke-test demo vs non-demo MCP tab
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Revert Demo MCP Accommodations
|
||||
|
||||
## Why Docker works but integrations do not
|
||||
|
||||
Demo mode has **two different mock mechanisms**:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph dockerPath [Docker - works in demo]
|
||||
MCP1[MCP docker_getContainers]
|
||||
Handler[docker.ts request handler]
|
||||
MockContainers[Hardcoded mockContainers]
|
||||
MCP1 --> Handler
|
||||
Handler -->|"if DEMO_MODE"| MockContainers
|
||||
end
|
||||
|
||||
subgraph integrationPath [Integrations - confusing in demo]
|
||||
MCP2[MCP widget tools e.g. calendar_findAllEvents]
|
||||
TRPC[tRPC procedure]
|
||||
RH[cached-integration-request-handler]
|
||||
Kind{integration.kind}
|
||||
Mock[MockIntegration fake data]
|
||||
Real[Sonarr/Radarr HTTP - fails]
|
||||
MCP2 --> TRPC --> RH --> Kind
|
||||
Kind -->|mock| Mock
|
||||
Kind -->|sonarr/radarr| Real
|
||||
end
|
||||
```
|
||||
|
||||
- **Docker**:
|
||||
[`packages/request-handler/src/docker.ts`](packages/request-handler/src/docker.ts)
|
||||
checks `DEMO_MODE` directly and returns inline fake containers. No integration
|
||||
record needed.
|
||||
- **Everything else**: Demo seed creates **one** integration with `kind: "mock"`
|
||||
([`packages/db/migrations/seed.ts`](packages/db/migrations/seed.ts)), not
|
||||
Sonarr/Radarr/Overseerr.
|
||||
[`MockIntegration`](packages/integrations/src/mock/mock-integration.ts) can
|
||||
serve calendar, downloads, health, DNS, media server, etc. **if** callers pass
|
||||
that integration ID.
|
||||
- **Why it felt broken**: MCP tool descriptions tell AI clients to look for
|
||||
"Overseerr/Sonarr IDs". Demo has none. Tools like
|
||||
`integration_searchMediaRequests` hard-filter to `mediaSearch` kinds
|
||||
(`overseerr`, `jellyseerr`, `seerr`) — `mock` is excluded from that category
|
||||
in
|
||||
[`packages/definitions/src/integration.ts`](packages/definitions/src/integration.ts).
|
||||
|
||||
Fixing this properly means extending the mock integration system (new
|
||||
`searchAsync`, category changes, middleware relaxations, instruction rewrites)
|
||||
for a feature almost nobody will use on the public demo.
|
||||
|
||||
## Option comparison
|
||||
|
||||
| | **Revert (chosen)** | **Minimal fix** | **Full fix** |
|
||||
| -------------------- | --------------------------- | ---------------------------------------------- | --------------------------------------------------- |
|
||||
| **Effort** | ~1 hour, ~100 lines removed | ~2 hours | 1-2 days |
|
||||
| **Files touched** | 8 MCP/demo-specific files | 8 + route instructions | 15+ across integrations, definitions, seed, routers |
|
||||
| **Risk** | Low | Low-medium | Medium (mock/search semantics) |
|
||||
| **Demo MCP outcome** | Clearly unsupported | Partial (widget reads only, no search/request) | Near-full read coverage |
|
||||
| **Maintenance** | None ongoing | Instructions drift with tool changes | Mock parity with real integrations |
|
||||
|
||||
**Recommendation: Revert.** The demo accommodation code added special-case
|
||||
plumbing (`demoSafePermissionRequiredProcedure`, dual mutation guards, hardcoded
|
||||
UI strings) for a workflow that still cannot deliver a convincing "homelab AI"
|
||||
experience without substantial integration-layer work.
|
||||
|
||||
## What to revert (demo MCP accommodations only)
|
||||
|
||||
These changes were added specifically for demo + MCP. **Do not revert** the core
|
||||
MCP server, OAuth, Scalar docs, tool descriptions, or `normalizeSchema`.
|
||||
|
||||
### Backend (3 files)
|
||||
|
||||
1. [`packages/api/src/trpc.ts`](packages/api/src/trpc.ts)
|
||||
- Remove `demoSafePermissionRequiredProcedure` export (~12 lines)
|
||||
- Keep existing `enforceDemoModeReadOnly` (pre-dates MCP; used by whole app)
|
||||
|
||||
2. [`packages/api/src/router/apiKeys.ts`](packages/api/src/router/apiKeys.ts)
|
||||
- Change `create` back to `permissionRequiredProcedure` (mutations blocked in
|
||||
demo again)
|
||||
- Remove `isDemoMode` guard on `delete` (global demo middleware already
|
||||
blocks delete mutations)
|
||||
- Remove `TRPCError` / `isDemoMode` imports if unused
|
||||
|
||||
3. [`packages/api/src/mcp.ts`](packages/api/src/mcp.ts)
|
||||
- Stop exporting `isDemoMode`; keep only `createTRPCContext` and `mcpRouter`
|
||||
|
||||
### MCP route (1 file)
|
||||
|
||||
4. [`apps/nextjs/src/app/api/mcp/[transport]/route.ts`](apps/nextjs/src/app/api/mcp/[transport]/route.ts)
|
||||
- Remove `isDemoMode` import and all demo branches:
|
||||
- `ListToolsRequestSchema` mutation filter
|
||||
- `CallToolRequestSchema` mutation rejection
|
||||
- `SERVER_INSTRUCTIONS` demo section
|
||||
- Keep `getToolType` / `buildToolTypeMap` only if still needed elsewhere;
|
||||
otherwise remove dead code from demo-only filtering
|
||||
|
||||
### UI (4 files)
|
||||
|
||||
5. [`apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx`](apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx)
|
||||
- Remove `isDemoMode` import from `@homarr/api/mcp`
|
||||
- Stop passing `isDemoMode` to child components
|
||||
|
||||
6. [`apps/nextjs/src/app/[locale]/manage/tools/api/components/api-page-tabs.tsx`](apps/nextjs/src/app/[locale]/manage/tools/api/components/api-page-tabs.tsx)
|
||||
- Remove `isDemoMode` prop from interface and `McpInstructions` call
|
||||
|
||||
7. [`apps/nextjs/src/app/[locale]/manage/tools/api/components/mcp-instructions.tsx`](apps/nextjs/src/app/[locale]/manage/tools/api/components/mcp-instructions.tsx)
|
||||
- Remove `isDemoMode` prop
|
||||
- Replace current demo banner with a single **Alert**: MCP requires a
|
||||
self-hosted instance; demo mode does not support MCP (API key creation and
|
||||
write tools are disabled)
|
||||
- Use `clientApi.info.isDemoMode.useQuery()` or pass `isDemoMode` from server
|
||||
via existing `api.info.isDemoMode()` tRPC query (already exists in
|
||||
[`packages/api/src/router/info.ts`](packages/api/src/router/info.ts)) —
|
||||
avoids re-exporting from `@homarr/api/mcp`
|
||||
|
||||
8. [`apps/nextjs/src/app/[locale]/manage/tools/api/components/api-keys.tsx`](apps/nextjs/src/app/[locale]/manage/tools/api/components/api-keys.tsx)
|
||||
- Remove `isDemoMode` prop and conditional delete-column spread
|
||||
- Delete button hidden automatically in demo because `apiKeys.delete`
|
||||
mutation is blocked by global `enforceDemoModeReadOnly`
|
||||
|
||||
## What to add instead
|
||||
|
||||
- **MCP tab**: When `info.isDemoMode` is true, show a prominent notice at the
|
||||
top of
|
||||
[`mcp-instructions.tsx`](apps/nextjs/src/app/[locale]/manage/tools/api/components/mcp-instructions.tsx)
|
||||
explaining MCP is for self-hosted instances only.
|
||||
- **Optional**: Disable/hide the "Create API Key" button on the MCP tab in demo
|
||||
(mutations will fail anyway).
|
||||
- **No changes** to integrations, definitions, seed, or mock services.
|
||||
|
||||
## Files NOT impacted by revert
|
||||
|
||||
Core MCP feature stays as-is:
|
||||
|
||||
- [`apps/nextjs/src/app/api/mcp/[transport]/route.ts`](apps/nextjs/src/app/api/mcp/[transport]/route.ts)
|
||||
— auth, rate limiting, `normalizeSchema`, `SERVER_INSTRUCTIONS` (minus demo
|
||||
section)
|
||||
- OAuth routes under `apps/nextjs/src/app/api/mcp/oauth/`
|
||||
- All MCP-enabled routers in `packages/api/src/router/`
|
||||
- [`packages/api/src/mcp.ts`](packages/api/src/mcp.ts) — eager router (minus
|
||||
`isDemoMode` export)
|
||||
- Scalar UI, dynamic tools table,
|
||||
[`.cursor/rules/mcp-integration.mdc`](.cursor/rules/mcp-integration.mdc)
|
||||
|
||||
Pre-existing demo infrastructure (unchanged):
|
||||
|
||||
- [`packages/request-handler/src/docker.ts`](packages/request-handler/src/docker.ts)
|
||||
— Docker mock containers
|
||||
- [`packages/db/migrations/seed.ts`](packages/db/migrations/seed.ts) — mock
|
||||
integration + demo board
|
||||
- [`packages/integrations/src/mock/`](packages/integrations/src/mock/) — mock
|
||||
services for widgets on demo board UI
|
||||
|
||||
## Verification
|
||||
|
||||
1. `pnpm lint` and `pnpm typecheck` pass
|
||||
2. Demo mode (`DEMO_MODE=true`):
|
||||
- MCP tab shows "not supported" notice
|
||||
- API key create fails with existing "Mutations are disabled in demo mode"
|
||||
- `docker_getContainers` still works on demo board UI (unchanged; separate
|
||||
code path)
|
||||
3. Non-demo mode: MCP unchanged (all tools listed, mutations work with
|
||||
permissions)
|
||||
|
||||
## Commit strategy
|
||||
|
||||
Single focused commit on `feat/add-mcp-server`:
|
||||
|
||||
```
|
||||
revert(mcp): drop demo-mode accommodations for MCP
|
||||
|
||||
MCP is intended for self-hosted instances. Remove demo-specific API key
|
||||
bypass, read-only tool filtering, and demo UI plumbing. Show a clear
|
||||
notice on the MCP tab when demo mode is active.
|
||||
```
|
||||
|
||||
Then push to the existing PR (no force-push needed unless you want another
|
||||
squash).
|
||||
@@ -33,6 +33,11 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.68.0
|
||||
- 1.67.0
|
||||
- 1.66.1
|
||||
- 1.66.0
|
||||
- 1.65.0
|
||||
- 1.64.0
|
||||
- 1.62.0
|
||||
- 1.61.0
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
automerge: true,
|
||||
},
|
||||
{
|
||||
// disallow automerge for onlyBuiltDependencies
|
||||
// disallow automerge for allowBuilds dependencies (native builds need manual verification)
|
||||
matchPackageNames: [
|
||||
"@tree-sitter-grammars/tree-sitter-yaml",
|
||||
"bcrypt",
|
||||
@@ -46,7 +46,7 @@
|
||||
updateInternalDeps: true,
|
||||
rangeStrategy: "bump",
|
||||
automerge: false,
|
||||
baseBranches: ["dev"],
|
||||
baseBranchPatterns: ["dev"],
|
||||
dependencyDashboard: false,
|
||||
// prevent special characters in branch names (otherwise git errors occur on windows)
|
||||
branchNameStrict: true,
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
|
||||
|
||||
- name: Commit fixes
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "style: auto-fix lint and format"
|
||||
|
||||
@@ -163,3 +163,65 @@ jobs:
|
||||
run: cp .env.example .env
|
||||
- name: Build
|
||||
run: pnpm turbo build --affected
|
||||
|
||||
memory-regression:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: homarr-memory-test
|
||||
network: host
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Start container and measure memory
|
||||
run: |
|
||||
docker run -d --name homarr-mem-test -p 17575:7575 homarr-memory-test
|
||||
# Wait for container to be healthy (up to 60s)
|
||||
healthy=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:17575/api/health 2>/dev/null; then
|
||||
echo "Container healthy after $((i * 2))s"
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$healthy" != "true" ]; then
|
||||
echo "ERROR: Container never became healthy"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
# Warm up: hit the dashboard page a few times
|
||||
for i in $(seq 1 5); do
|
||||
curl -sf http://localhost:17575/ > /dev/null 2>&1 || true
|
||||
sleep 2
|
||||
done
|
||||
# Measure RSS after warmup
|
||||
MEM_KB=$(docker stats --no-stream --format '{{.MemUsage}}' homarr-mem-test | sed 's| /.*||')
|
||||
echo "Raw memory: $MEM_KB"
|
||||
# Convert to numeric MB (handles GiB and MiB suffixes)
|
||||
MEM_NUM=$(echo "$MEM_KB" | awk '{
|
||||
if ($0 ~ /GiB/) { gsub(/GiB/,""); print $1 * 1024 }
|
||||
else if ($0 ~ /MiB/) { gsub(/MiB/,""); print $1 }
|
||||
else if ($0 ~ /KiB/) { gsub(/KiB/,""); print $1 / 1024 }
|
||||
else { print $1 / 1048576 }
|
||||
}')
|
||||
echo "Memory usage: ${MEM_NUM} MB"
|
||||
MAX_MB=700
|
||||
if awk "BEGIN {exit !($MEM_NUM > $MAX_MB)}"; then
|
||||
echo "ERROR: Memory usage ${MEM_NUM} MB exceeds ${MAX_MB} MB threshold"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
echo "Memory check passed: ${MEM_NUM} MB <= ${MAX_MB} MB"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 24.16.0
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
|
||||
@@ -38,9 +38,9 @@ jobs:
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: "apps/docs/build"
|
||||
|
||||
@@ -53,4 +53,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -3,6 +3,7 @@ name: "[Deployment] Automatic Weekly Release"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 19 * * 5" # https://crontab.guru/#0_19_*_*_5
|
||||
timezone: Europe/Zurich
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
send-notifications:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run: pnpm api-dump-contributors
|
||||
|
||||
- name: Commit updated contributors
|
||||
uses: EndBug/add-and-commit@v9
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/static"
|
||||
default_author: github_actor
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "OpenAPI spec updated ($(jq '.paths | length' /tmp/openapi.json) paths)"
|
||||
|
||||
- name: Commit updated spec
|
||||
uses: EndBug/add-and-commit@v9
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/static/api/"
|
||||
default_author: github_actor
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnpm-store/
|
||||
.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
@@ -70,6 +71,8 @@ apps/nextjs/public/images/background.png
|
||||
|
||||
# personal agent config
|
||||
.opencode
|
||||
.agents/
|
||||
skills-lock.json
|
||||
|
||||
# docusaurus
|
||||
apps/docs/.docusaurus
|
||||
|
||||
+1
-2
@@ -1,3 +1,2 @@
|
||||
[tools]
|
||||
node = "24.16.0"
|
||||
pnpm = "10.34.1"
|
||||
node = "24.18.0"
|
||||
@@ -1,6 +1,2 @@
|
||||
# Expo doesn't play nice with pnpm by default.
|
||||
# The symbolic links of pnpm break the rules of Expo monorepos.
|
||||
# @link https://docs.expo.dev/guides/monorepos/#common-issues
|
||||
node-linker=hoisted
|
||||
strict-peer-dependencies=false
|
||||
engine-strict=true
|
||||
# pnpm v11: non-auth settings moved to pnpm-workspace.yaml
|
||||
# Keep this file for any future registry/auth configuration
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"oxc.oxc-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"million.million-lint",
|
||||
"lokalise.i18n-ally"
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"js/ts.tsdk.path": "node_modules\\typescript\\lib",
|
||||
"js/ts.implicitProjectConfig.experimentalDecorators": true,
|
||||
"cSpell.words": [
|
||||
"ajnart",
|
||||
|
||||
@@ -1,3 +1,77 @@
|
||||
## [1.68.1](https://github.com/homarr-labs/homarr/compare/v1.68.0...v1.68.1) (2026-06-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make source maps opt-in to restore v1.67.0 memory baseline ([57ef0af](https://github.com/homarr-labs/homarr/commit/57ef0af4a252ea39727caeba7e13587dabc6254e)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
|
||||
* revert production source maps to restore v1.67.0 memory baseline ([60a69e3](https://github.com/homarr-labs/homarr/commit/60a69e31efc7168b8f4309daf75954d7730d883b)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
|
||||
|
||||
## [1.68.0](https://github.com/homarr-labs/homarr/compare/v1.67.0...v1.68.0) (2026-06-26)
|
||||
|
||||
### Features
|
||||
|
||||
* add VPN status widget ([#5812](https://github.com/homarr-labs/homarr/issues/5812)) ([a2bb53a](https://github.com/homarr-labs/homarr/commit/a2bb53a8b8fd17da3d2e23851b75d4e179f66225))
|
||||
* re-add Docker quick-access button in user menu ([#6054](https://github.com/homarr-labs/homarr/issues/6054)) ([abd1635](https://github.com/homarr-labs/homarr/commit/abd16354bc9ef3dddbb497fd16b03476535046ac)), closes [#2165](https://github.com/homarr-labs/homarr/issues/2165)
|
||||
* ship source maps for readable production stack traces ([#6031](https://github.com/homarr-labs/homarr/issues/6031)) ([89380f8](https://github.com/homarr-labs/homarr/commit/89380f81ddd132f5b1041a8f2c3b6c6b138923dd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **beszel:** harden cache layer and prevent blank widgets ([#6052](https://github.com/homarr-labs/homarr/issues/6052)) ([49c2663](https://github.com/homarr-labs/homarr/commit/49c266374df12fea2404710232ced42e2de11dbf)), closes [#6030](https://github.com/homarr-labs/homarr/issues/6030)
|
||||
* **beszel:** improve stats date display, add click-to-stats modal, fix grid ordering ([#6026](https://github.com/homarr-labs/homarr/issues/6026)) ([55fb227](https://github.com/homarr-labs/homarr/commit/55fb227305fe51963e486ffb25c4ce6811a2f0e4))
|
||||
* **beszel:** leverage TanStack Query for stale-while-revalidate caching ([#6055](https://github.com/homarr-labs/homarr/issues/6055)) ([bbc4589](https://github.com/homarr-labs/homarr/commit/bbc4589c9e1608721fb58a6f9b03cca8d65c1c4a))
|
||||
* **beszel:** use 480m interval for 1w/30d stats views ([4383da3](https://github.com/homarr-labs/homarr/commit/4383da38ef634327c288626c58480cc1e5a2f805))
|
||||
* **beszel:** widget transparency, error handling, and cache invalidation ([#5993](https://github.com/homarr-labs/homarr/issues/5993)) ([39e4245](https://github.com/homarr-labs/homarr/commit/39e424563a3187dcf0dba5f654e2225be76aef51))
|
||||
* **icon-picker:** keep URL visible in edit mode, add SVG-first ordering ([#6051](https://github.com/homarr-labs/homarr/issues/6051)) ([e01571b](https://github.com/homarr-labs/homarr/commit/e01571b221812cd19a6d67ebe3cf44432104ec27))
|
||||
* **integrations:** prevent certificate trust button from submitting the form ([#6020](https://github.com/homarr-labs/homarr/issues/6020)) ([0f66bfd](https://github.com/homarr-labs/homarr/commit/0f66bfd6c128253ba644d80d7c908de5314cf997))
|
||||
* **manage/about:** bypass self-fetch + add manage error boundary (LLM exploration) ([#6009](https://github.com/homarr-labs/homarr/issues/6009)) ([19f9769](https://github.com/homarr-labs/homarr/commit/19f9769d9e669d56d793e9f6a0d265316113d743)), closes [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008)
|
||||
* **plex:** use series title for media releases instead of season label ([#6083](https://github.com/homarr-labs/homarr/issues/6083)) ([2443b89](https://github.com/homarr-labs/homarr/commit/2443b89b46f88fddbf41f0382a9f3bc5469c6fa8))
|
||||
* show app first in add item modal ([#6087](https://github.com/homarr-labs/homarr/issues/6087)) ([915c8a3](https://github.com/homarr-labs/homarr/commit/915c8a3501551fdccf786d0c34dc01107d873ce0))
|
||||
* **truenas:** report actual memory usage and pool free space ([#6017](https://github.com/homarr-labs/homarr/issues/6017)) ([f69e545](https://github.com/homarr-labs/homarr/commit/f69e545d6429aece0e55975c3b2426f20e91e545))
|
||||
* **unraid:** report disk free space instead of total size ([#6018](https://github.com/homarr-labs/homarr/issues/6018)) ([1fb97ee](https://github.com/homarr-labs/homarr/commit/1fb97ee9c6fbe656a7dd3a9d1e436125982533f4))
|
||||
* **widgets:** clean up health monitoring disk and size display ([#6019](https://github.com/homarr-labs/homarr/issues/6019)) ([e29c79a](https://github.com/homarr-labs/homarr/commit/e29c79a9028d9311dcb3d8323e0950a444589c7a))
|
||||
* **widgets:** hide health monitoring default tab option for non-proxmox integrations ([#6021](https://github.com/homarr-labs/homarr/issues/6021)) ([52df56b](https://github.com/homarr-labs/homarr/commit/52df56b19da8e0bcb6c2e5df47b7936ec63f3149))
|
||||
* **widgets:** hide system disk usage value when it overflows and show it in a tooltip ([#6022](https://github.com/homarr-labs/homarr/issues/6022)) ([a19c9d8](https://github.com/homarr-labs/homarr/commit/a19c9d88caacc86b42854c732818f0b5e5cc8dd5))
|
||||
|
||||
## [1.67.0](https://github.com/homarr-labs/homarr/compare/v1.66.1...v1.67.0) (2026-06-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** migrate from Umami to PostHog server-side analytics ([#5983](https://github.com/homarr-labs/homarr/issues/5983)) ([d059023](https://github.com/homarr-labs/homarr/commit/d059023b51a323df027c1cf033454440bd0d7e57))
|
||||
* Calculate and display total CPU and memory usage ([#5911](https://github.com/homarr-labs/homarr/issues/5911)) ([d9cbd54](https://github.com/homarr-labs/homarr/commit/d9cbd5427d0e5be3e5dbecca8d6069dc8074d810))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** isolate session cookies from other Auth.js apps on same host ([#5996](https://github.com/homarr-labs/homarr/issues/5996)) ([e1a245f](https://github.com/homarr-labs/homarr/commit/e1a245ff2d8520d08957c4a7dbe01d039607a973))
|
||||
* **deps:** update tailwindcss monorepo to ^4.3.1 ([#5988](https://github.com/homarr-labs/homarr/issues/5988)) ([78b6e52](https://github.com/homarr-labs/homarr/commit/78b6e520ac095129398d3da8f2441df408e93e9f))
|
||||
* **downloads:** sort stalled torrents last when sorting by finish time ([#6011](https://github.com/homarr-labs/homarr/issues/6011)) ([c56d9d6](https://github.com/homarr-labs/homarr/commit/c56d9d6894c717b5c70ab75d6a100d4d1071c2d3)), closes [#5416](https://github.com/homarr-labs/homarr/issues/5416)
|
||||
|
||||
## [1.65.0](https://github.com/homarr-labs/homarr/compare/v1.64.0...v1.65.0) (2026-06-12)
|
||||
|
||||
### Features
|
||||
|
||||
* add MCP server for AI assistant integration ([#5882](https://github.com/homarr-labs/homarr/issues/5882)) ([6baff7b](https://github.com/homarr-labs/homarr/commit/6baff7be3dc8f2473d93d17862b6d0b2df5c4d15))
|
||||
* add Technitium DNS integration, usable with DNS Hole widgets ([#5832](https://github.com/homarr-labs/homarr/issues/5832)) ([cd1a82a](https://github.com/homarr-labs/homarr/commit/cd1a82a640101cdca88d1e825edb11bd51f5ce66))
|
||||
* add Uptime Kuma, Audiobookshelf, Paperless-ngx, and Navidrome integrations ([#5910](https://github.com/homarr-labs/homarr/issues/5910)) ([d77c6f8](https://github.com/homarr-labs/homarr/commit/d77c6f8d4e50108f50831ee365e382a535acb254))
|
||||
* **beszel:** add Beszel integration with system monitoring widgets ([#5900](https://github.com/homarr-labs/homarr/issues/5900)) ([a4db9cf](https://github.com/homarr-labs/homarr/commit/a4db9cf0dcd8034015064f9d23f2d351b1d0c50a))
|
||||
* **board:** add widget hover overlay on dashboard ([#5888](https://github.com/homarr-labs/homarr/issues/5888)) ([29bd710](https://github.com/homarr-labs/homarr/commit/29bd7101b180acfb750c7d4414c30bd6450065e2))
|
||||
* custom widgets ([#5889](https://github.com/homarr-labs/homarr/issues/5889)) ([aecbca2](https://github.com/homarr-labs/homarr/commit/aecbca2b81ad1693c599e3168fd0c0723311b3b4))
|
||||
* **docs:** migrate documentation site into monorepo ([#5912](https://github.com/homarr-labs/homarr/issues/5912)) ([9e421c6](https://github.com/homarr-labs/homarr/commit/9e421c67bd87b5b7065118afedd08e7eb2140773))
|
||||
* **lang:** add Finnish language support ([#5892](https://github.com/homarr-labs/homarr/issues/5892)) ([2fb7bde](https://github.com/homarr-labs/homarr/commit/2fb7bde9002c2670aa49db804e352c3821fa8097))
|
||||
* search widgets by integration type when adding items ([#5909](https://github.com/homarr-labs/homarr/issues/5909)) ([48e1844](https://github.com/homarr-labs/homarr/commit/48e1844b5b9dc6d322bc22a972fdd29b6e03d3d2))
|
||||
* SQLite backup & restore with WASM preview ([#5887](https://github.com/homarr-labs/homarr/issues/5887)) ([5d1f268](https://github.com/homarr-labs/homarr/commit/5d1f268438b84bd19f5a79658a43931dd874cd41))
|
||||
* **ui:** add global query cache leveraging Redis ([#5811](https://github.com/homarr-labs/homarr/issues/5811)) ([74443a4](https://github.com/homarr-labs/homarr/commit/74443a49b2330b7d71c5ccdb873c61b270d1c72a))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **boards:** add permissions to board summary schema to fix manage page crash ([#5917](https://github.com/homarr-labs/homarr/issues/5917)) ([a42af0e](https://github.com/homarr-labs/homarr/commit/a42af0e678b58b6c63a8bac4978fb2951c6ddef8))
|
||||
* **ci:** rename baseBranches to baseBranchPatterns in renovate config ([15e061c](https://github.com/homarr-labs/homarr/commit/15e061cf03f2301996aaf11a25095194e5759fb5))
|
||||
* **db:** inline custom widget seed data to fix CJS migration crash ([f26007a](https://github.com/homarr-labs/homarr/commit/f26007a29a0b04765baba72122d38a9e5c639a0b))
|
||||
* **definitions:** gracefully handle sitemap fetch failure in codegen ([7f8b012](https://github.com/homarr-labs/homarr/commit/7f8b0121b303fa09a213c73e6a690463e9fc6b44))
|
||||
* **docs:** add trailingSlash for GitHub Pages compatibility ([28ceb37](https://github.com/homarr-labs/homarr/commit/28ceb37cbfe45f8ce45fd88e6a8c87ffe4c9b5c2))
|
||||
* **docs:** disable swcHtmlMinimizer and fix broken links ([905f795](https://github.com/homarr-labs/homarr/commit/905f795b5607744d0aeec4bb65d84aabd1505047))
|
||||
* gracefully skip widgets with unknown kind instead of crashing ([#5915](https://github.com/homarr-labs/homarr/issues/5915)) ([484b8cf](https://github.com/homarr-labs/homarr/commit/484b8cf9f18122a298dc7fae1027653cd287c09c))
|
||||
* **image-proxy:** use ArrayBuffer instead of Blob to prevent OOM on Node v24 ([#5926](https://github.com/homarr-labs/homarr/issues/5926)) ([3e464a7](https://github.com/homarr-labs/homarr/commit/3e464a76f2d1e5985f259270a4258f3db4f17c18))
|
||||
* **integrations:** support truenas json-rpc api, certificate trust, and api key auth ([#5861](https://github.com/homarr-labs/homarr/issues/5861)) ([f6d5f97](https://github.com/homarr-labs/homarr/commit/f6d5f97933cd257ee8851f2d6a27f56e2942076d))
|
||||
|
||||
## [1.64.0](https://github.com/homarr-labs/homarr/compare/v1.63.0...v1.64.0) (2026-06-05)
|
||||
|
||||
### Features
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
# Changes
|
||||
|
||||
## Goal
|
||||
|
||||
Remove widget-only services from Homarr's global Integration system.
|
||||
|
||||
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
|
||||
|
||||
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
|
||||
|
||||
## Why
|
||||
|
||||
These services do not need shared integration state.
|
||||
|
||||
- Release providers are selected per Releases widget repository.
|
||||
- Search.ch is only used by the Timetable widget.
|
||||
|
||||
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Release Providers
|
||||
|
||||
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
|
||||
- Removed release providers from `IntegrationKind`.
|
||||
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
|
||||
- Updated the Releases widget to store provider configuration directly in widget options:
|
||||
- `provider`
|
||||
- `identifier`
|
||||
- `versionFilter`
|
||||
- `providerUrl`
|
||||
- Updated the Releases widget editor to use a provider select instead of an integration select.
|
||||
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
|
||||
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
|
||||
|
||||
### Timetable / Search.ch
|
||||
|
||||
- Removed Search.ch as an integration.
|
||||
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
|
||||
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
|
||||
- Updated the Timetable API to receive `baseUrl` directly from widget options.
|
||||
|
||||
### ArchiveTeam Warrior
|
||||
|
||||
- Kept as a proper integration (not removed).
|
||||
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
|
||||
|
||||
### Integration Cleanup
|
||||
|
||||
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
|
||||
- Removed unused dependencies from `packages/integrations/package.json`.
|
||||
|
||||
### Database Migration
|
||||
|
||||
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
|
||||
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
|
||||
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
|
||||
|
||||
### Docs
|
||||
|
||||
- Updated Releases widget docs to describe providers instead of integrations.
|
||||
- Updated Timetable docs to remove Search.ch integration setup.
|
||||
- Removed obsolete integration docs pages for the deleted integration kinds.
|
||||
- Updated docs sitemap and integration docs mappings.
|
||||
|
||||
### Translations
|
||||
|
||||
- Added English labels for the new widget options:
|
||||
- `widget.timetable.option.baseUrl.label`
|
||||
- `widget.releases.option.repositories.providerUrl.label`
|
||||
|
||||
## How It Works Now
|
||||
|
||||
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
|
||||
|
||||
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
|
||||
|
||||
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
|
||||
|
||||
## Deliberate Non-Goals
|
||||
|
||||
- No provider secret/token support was added.
|
||||
- No new secret storage model was introduced.
|
||||
- No compatibility layer was kept in `packages/integrations`.
|
||||
|
||||
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
|
||||
|
||||
## Verification So Far
|
||||
|
||||
- Focused typechecks passed for affected packages.
|
||||
- Full `pnpm turbo typecheck` passed.
|
||||
- Full `pnpm lint` passed with existing warnings.
|
||||
- Focused release provider normalization tests passed.
|
||||
- Widget translation test passed.
|
||||
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
|
||||
- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
|
||||
+187
-1
@@ -1,7 +1,193 @@
|
||||
# ── CODEOWNERS ───────────────────────────────────────
|
||||
* @homarr-labs/maintainers
|
||||
|
||||
# Exempt Renovate‑managed files (no owners)
|
||||
# Specific features / integrations
|
||||
# These owners come from the PR that introduced each integration implementation.
|
||||
# Specific rules override the global fallback because CODEOWNERS uses the last
|
||||
# matching pattern; do not include @homarr-labs/maintainers here unless they
|
||||
# should also be requested for that integration.
|
||||
|
||||
# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy
|
||||
apps/docs/docs/integrations/adguard-home @hillaliy
|
||||
packages/integrations/src/adguard-home @hillaliy
|
||||
|
||||
# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi
|
||||
apps/docs/docs/integrations/anchor @ohmzi
|
||||
packages/integrations/src/anchor @ohmzi
|
||||
|
||||
# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack
|
||||
apps/docs/docs/integrations/archive-team-warrior @darbyjack
|
||||
packages/integrations/src/archive-team-warrior @darbyjack
|
||||
|
||||
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/audiobookshelf @ajnart
|
||||
packages/integrations/src/audiobookshelf @ajnart
|
||||
|
||||
# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart
|
||||
apps/docs/docs/integrations/beszel @ajnart
|
||||
packages/integrations/src/beszel @ajnart
|
||||
|
||||
# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/codeberg @Aandree5
|
||||
packages/integrations/src/codeberg @Aandree5
|
||||
|
||||
# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K
|
||||
apps/docs/docs/integrations/coolify @Rhiz3K
|
||||
packages/integrations/src/coolify @Rhiz3K
|
||||
|
||||
# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw
|
||||
apps/docs/docs/integrations/dash-dot @manuel-rw
|
||||
packages/integrations/src/dashdot @manuel-rw
|
||||
|
||||
# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/docker-hub @Aandree5
|
||||
packages/integrations/src/docker-hub @Aandree5
|
||||
|
||||
# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/emby @Meierschlumpf
|
||||
packages/integrations/src/emby @Meierschlumpf
|
||||
|
||||
# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/github @Aandree5
|
||||
packages/integrations/src/github @Aandree5
|
||||
|
||||
# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5
|
||||
apps/docs/docs/integrations/github-containerregistry @Aandree5
|
||||
packages/integrations/src/github-container-registry @Aandree5
|
||||
|
||||
# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/gitlab @Aandree5
|
||||
packages/integrations/src/gitlab @Aandree5
|
||||
|
||||
# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw
|
||||
apps/docs/docs/integrations/glances @manuel-rw
|
||||
packages/integrations/src/glances @manuel-rw
|
||||
|
||||
# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar
|
||||
apps/docs/docs/integrations/gluetun @Lok3sh-kumar
|
||||
packages/integrations/src/gluetun @Lok3sh-kumar
|
||||
|
||||
# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/gotify @Nerdy-Technician
|
||||
packages/integrations/src/gotify @Nerdy-Technician
|
||||
|
||||
# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw
|
||||
apps/docs/docs/integrations/home-assistant @manuel-rw
|
||||
packages/integrations/src/homeassistant @manuel-rw
|
||||
|
||||
# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman
|
||||
apps/docs/docs/integrations/ical @NicolasNewman
|
||||
packages/integrations/src/ical @NicolasNewman
|
||||
|
||||
# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw
|
||||
apps/docs/docs/integrations/immich @manuel-rw
|
||||
packages/integrations/src/immich @manuel-rw
|
||||
|
||||
# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyfin @manuel-rw
|
||||
packages/integrations/src/jellyfin @manuel-rw
|
||||
|
||||
# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyseerr @manuel-rw
|
||||
packages/integrations/src/jellyseerr @manuel-rw
|
||||
|
||||
# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/linux-server-io @Aandree5
|
||||
packages/integrations/src/linuxserverio @Aandree5
|
||||
|
||||
# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/navidrome @ajnart
|
||||
packages/integrations/src/navidrome @ajnart
|
||||
|
||||
# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw
|
||||
apps/docs/docs/integrations/nextcloud @manuel-rw
|
||||
packages/integrations/src/nextcloud @manuel-rw
|
||||
|
||||
# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/npm @Aandree5
|
||||
packages/integrations/src/npm @Aandree5
|
||||
|
||||
# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow
|
||||
apps/docs/docs/integrations/ntfy @ajnart @manuel-rw
|
||||
packages/integrations/src/ntfy @ajnart @manuel-rw
|
||||
|
||||
# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy
|
||||
apps/docs/docs/integrations/open-media-vault @hillaliy
|
||||
packages/integrations/src/openmediavault @hillaliy
|
||||
|
||||
# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman
|
||||
apps/docs/docs/integrations/opnsense @Oupsman
|
||||
packages/integrations/src/opnsense @Oupsman
|
||||
|
||||
# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/overseerr @manuel-rw
|
||||
packages/integrations/src/overseerr @manuel-rw
|
||||
|
||||
# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/paperless-ngx @ajnart
|
||||
packages/integrations/src/paperless-ngx @ajnart
|
||||
|
||||
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
|
||||
apps/docs/docs/integrations/peanut @afonsojramos
|
||||
packages/integrations/src/peanut @afonsojramos
|
||||
|
||||
# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/pi-hole @Meierschlumpf
|
||||
packages/integrations/src/pi-hole @Meierschlumpf
|
||||
|
||||
# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy
|
||||
apps/docs/docs/integrations/plex @hillaliy
|
||||
packages/integrations/src/plex @hillaliy
|
||||
|
||||
# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy
|
||||
apps/docs/docs/integrations/prowlarr @hillaliy
|
||||
packages/integrations/src/prowlarr @hillaliy
|
||||
|
||||
# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/proxmox @Meierschlumpf
|
||||
packages/integrations/src/proxmox @Meierschlumpf
|
||||
|
||||
# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/quay @Aandree5
|
||||
packages/integrations/src/quay @Aandree5
|
||||
|
||||
# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/search-ch @Meierschlumpf
|
||||
packages/integrations/src/search-ch @Meierschlumpf
|
||||
|
||||
# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder
|
||||
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
|
||||
packages/integrations/src/speedtest-tracker @kylegrieder
|
||||
|
||||
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
|
||||
apps/docs/docs/integrations/technitium-dns @lopsch
|
||||
packages/integrations/src/technitium @lopsch
|
||||
|
||||
# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle
|
||||
apps/docs/docs/integrations/tracearr @StrandedTurtle
|
||||
packages/integrations/src/tracearr @StrandedTurtle
|
||||
|
||||
# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw
|
||||
apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos
|
||||
packages/integrations/src/truenas @manuel-rw @afonsojramos
|
||||
|
||||
# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42
|
||||
apps/docs/docs/integrations/umami @febrile42
|
||||
packages/integrations/src/umami @febrile42
|
||||
|
||||
# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi
|
||||
apps/docs/docs/integrations/unifi-controller @pitschi
|
||||
packages/integrations/src/unifi-controller @pitschi
|
||||
|
||||
# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw
|
||||
apps/docs/docs/integrations/unraid @manuel-rw
|
||||
packages/integrations/src/unraid @manuel-rw
|
||||
|
||||
# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/uptime-kuma @ajnart
|
||||
packages/integrations/src/uptime-kuma @ajnart
|
||||
|
||||
# Exempt Renovate-managed files (no owners)
|
||||
package.json
|
||||
**/package.json
|
||||
package-lock.json
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:24.16.0-alpine AS base
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
@@ -42,6 +42,7 @@ RUN mkdir -p /var/cache/nginx && \
|
||||
|
||||
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
||||
COPY --from=builder /app/apps/nextjs/package.json .
|
||||
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
|
||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
};
|
||||
@@ -23,6 +23,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
| AUTH_PROVIDERS | Select Which provider to use between *credentials*, *ldap* and *oidc*. <br /> Multiple providers can be enabled with by separating them with `,`, (ex. `AUTH_PROVIDERS=credentials,oidc`, it is highly recommended to just enable one provider). | credentials |
|
||||
| AUTH_LOGOUT_REDIRECT_URL | URL to redirect to after clicking logging out. | --- |
|
||||
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
|
||||
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="credentials" label="Credentials Provider" default>
|
||||
@@ -124,6 +125,8 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
|
||||
Roles are fetched from group claims. This can also be changed to roles for example added to a azure app registration by using the `AUTH_OIDC_GROUPS_ATTRIBUTE`.
|
||||
|
||||
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
@@ -197,6 +200,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
| ``AUTH_OIDC_AUTO_LOGIN`` | Automatically redirect to OIDC login | false |
|
||||
| ``AUTH_OIDC_SCOPE_OVERWRITE`` | Overwrite default scopes (openid, profile, email) | openid email profile groups |
|
||||
| ``AUTH_OIDC_GROUPS_ATTRIBUTE`` | Attribute used for groups (roles) claim | groups |
|
||||
| ``AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT`` | Stop syncing group memberships of OIDC users from the groups claim and manage them locally via the group members page instead | false |
|
||||
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
|
||||
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
|
||||
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
|
||||
|
||||
@@ -22,4 +22,4 @@ Homarr is completely free to use. We'd highly appreciate if you consider donatin
|
||||
## Where to donate
|
||||
We accept donations on our official [OpenCollective](https://opencollective.com/homarr).
|
||||
You may also sponsor us directly by providing a useful service / product to us other means.
|
||||
You can get into contact with us [here](./get-in-touch).
|
||||
You can get into contact with us [here](/docs/community/get-in-touch).
|
||||
@@ -21,13 +21,14 @@ import manageImportDecryptionToken from "./img/old-homarr-import-decryption-toke
|
||||
|
||||
:::tip
|
||||
|
||||
Read our [glossary](./glossary) if you are new to Homarr.
|
||||
Read our [glossary](/docs/getting-started/glossary) if you are new to Homarr.
|
||||
|
||||
:::
|
||||
|
||||
Congratulations on installing Homarr. Now it's time to get yourself familiar with the concepts of Homarr and how to use it.
|
||||
|
||||
## Onboarding
|
||||
|
||||
The onboard will show the first time you start Homarr.
|
||||
This process will help you with your first steps and adding some initial data, such as the administrator account.
|
||||
|
||||
@@ -45,50 +46,68 @@ You can either import a previous dashboard using the ZIP archive or start from s
|
||||
You may refresh anytime and continue from a different device - the onboarding is saved immediately on the server for your convenience.
|
||||
|
||||
### Starting from scratch
|
||||
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
|
||||
If credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of it's boards.
|
||||
You must choose an username and password (both can later be changed).
|
||||
We enforce strict password requirements, when you enter your password, to protect your instance if you accidentially
|
||||
expose it to the Internet.
|
||||
|
||||
<img src={imageStartFromScratchStepAdminUser} alt={"Screenshot form to create admin user"} width={300} />
|
||||
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
|
||||
If the credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of its boards.
|
||||
You must choose a username and password (both can later be changed).
|
||||
We enforce strict password requirements when you enter your password to protect your instance if you accidentally
|
||||
expose it to the internet.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepAdminUser}
|
||||
alt={"Screenshot form to create admin user"}
|
||||
width={300}
|
||||
/>
|
||||
|
||||
If you selected external providers (such as LDAP or OIDC), you'll be prompted to create an admin group.
|
||||
This group will be used to synchronize users from your provider to Homarr.
|
||||
When a user is logged in through the provider the groups of him that match groups with any in Homarr will be added to the user.
|
||||
The name of the group has to match exactly the name of the group in the provider.
|
||||
This group will be used to synchronize users from your provider to Homarr.
|
||||
When a user logs in through the provider, any of their groups that match groups in Homarr will be assigned to the user.
|
||||
The name of the group must match exactly the name of the group in the provider.
|
||||
|
||||
<img src={imageStartFromScratchStepExternalAdminGroup} alt={"Screenshot form to create external admin group"} width={300} />
|
||||
<img
|
||||
src={imageStartFromScratchStepExternalAdminGroup}
|
||||
alt={"Screenshot form to create external admin group"}
|
||||
width={300}
|
||||
/>
|
||||
|
||||
Next, you can configure some basic settings for Homarr. We recommend to leave all settings at default for most users.
|
||||
Here you can also disable analytics. Please note that our analytics are completly anonymous and is fully compliant with GDPR and PECR.
|
||||
|
||||
<img src={imageStartFromScratchStepServerSettings} alt={"Screenshot of analytics and indexing settings in the onboarding"} width={300} />
|
||||
<img
|
||||
src={imageStartFromScratchStepServerSettings}
|
||||
alt={"Screenshot of analytics and indexing settings in the onboarding"}
|
||||
width={300}
|
||||
/>
|
||||
|
||||
### Setting up integrations
|
||||
|
||||
After configuring server settings, the onboarding guides you through setting up your integrations.
|
||||
|
||||
#### Docker service discovery
|
||||
|
||||
If Docker is available (socket mounted), Homarr automatically detects running containers and matches them to known integration types.
|
||||
Matching integrations are pre-selected in the integration list, saving you from manually picking each one.
|
||||
|
||||
#### URL templating
|
||||
|
||||
You can define a base URL template to auto-fill integration URLs in bulk.
|
||||
Two modes are supported:
|
||||
- **Subdomain mode** — e.g. `{app}.example.com` where `{app}` is replaced with the integration name
|
||||
- **Port mode** — e.g. `192.168.1.1:{port}` where `{port}` is replaced with the integration's default port
|
||||
|
||||
- **Subdomain mode** - e.g. `{app}.example.com` where `{app}` is replaced with the integration name
|
||||
- **Port mode** - e.g. `192.168.1.1:{port}` where `{port}` is replaced with the integration's default port
|
||||
|
||||
#### API key helper links
|
||||
|
||||
For integrations that require API keys (such as Sonarr, Radarr, SABnzbd, or Seerr), a helper link opens the relevant settings page in the target application so you can quickly copy your key.
|
||||
|
||||
#### Creating apps from Docker containers
|
||||
|
||||
You can optionally auto-create bookmark apps for all discovered Docker containers, making them immediately available on your board.
|
||||
|
||||
:::note
|
||||
|
||||
Some niche and registry integrations (GitHub, npm, Docker Hub, GitLab, Codeberg, LinuxServer.io, GHCR, Quay, Search.ch) are hidden from the onboarding selection grid but remain available on the manage integrations page.
|
||||
Release providers and Search.ch are configured directly in their widgets instead of the integrations page.
|
||||
|
||||
:::
|
||||
|
||||
@@ -97,30 +116,41 @@ The onboarding can no longer be started again.
|
||||
From here you can click on one of the provided links to continue your journey.
|
||||
We recommend you to continue with the section [creating your first board on this page](#creating-your-first-board).
|
||||
|
||||
<img src={imageStartFromScratchStepFinish} alt={"Screenshot of the finish onboarding process with links"} width={300} />
|
||||
<img
|
||||
src={imageStartFromScratchStepFinish}
|
||||
alt={"Screenshot of the finish onboarding process with links"}
|
||||
width={300}
|
||||
/>
|
||||
|
||||
### Importing a ZIP from version before 1.0.0
|
||||
|
||||
This is recommended when you used Homarr before and want to re-import your data.
|
||||
It is important that you first migrate correctly using [the list of breaking changes for 1.0](/blog/2024/09/23/version-1.0).
|
||||
|
||||
<img src={manageExportOldHomarr} alt="Export from old Homarr" style={{ maxWidth: 500 }} />
|
||||
<img
|
||||
src={manageExportOldHomarr}
|
||||
alt="Export from old Homarr"
|
||||
style={{ maxWidth: 500 }}
|
||||
/>
|
||||
|
||||
**How to export and import data**
|
||||
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open ``Migrate to 1.0``.
|
||||
|
||||
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open `Migrate to 1.0`.
|
||||
2. Check the contents that you want to export. We recommend you to export all data.
|
||||
3. Click on the export data button
|
||||
4. Save the ZIP file, that is being downloaded, on your device. If you selected "integrations" or "users", you also get an encryption / decryption key.
|
||||
Copy it and do not loose it!
|
||||
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose ``Import from Homarr before 1.0``
|
||||
Copy it and do not loose it!
|
||||
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose `Import from Homarr before 1.0`
|
||||
6. Drag and drop your previously downloaded ZIP archive into the file upload: <br/><img src={manageImportOldHomarrDropzone} style={{ maxWidth: 500 }} />
|
||||
7. Next, choose the data that you want to import and how to import it. Since screen sizes were removed in 1.0, we create a new board for every screen size by default. We recommend you to only keep the best layout and adjust it later in 1.0.0 - but you can also import all of them and pick later.<br/><img src={manageImportOldHomarrChooseData} style={{ maxWidth: 500 }} />
|
||||
8. If you previously exported integrations or users, you will be asked to decrypt the data safely using your obtained encryption token. Paste into the modal and confirm your action.<br/><img src={manageImportDecryptionToken} style={{ maxWidth: 500 }} />
|
||||
9. Next, you can [follow the same setup as when starting from scratch](#starting-from-scratch). After you completed the onboarding, all of your data will be migrated and available.
|
||||
|
||||
## Creating your first board
|
||||
|
||||
In Homarr, you organize your apps and widgets on boards.
|
||||
Boards can be organized using the interactive drag and drop.
|
||||
Let's create your first board by navigating here: ``Manage`` > ``Boards`` > ``New board``:
|
||||
Let's create your first board by navigating here: `Manage` > `Boards` > `New board`:
|
||||

|
||||
|
||||
A board must always have a name and can either be public or private.
|
||||
@@ -133,12 +163,13 @@ Congratulations - you've just created your first board. You can see boards, that
|
||||

|
||||
|
||||
## Add your first app to Homarr
|
||||
|
||||
To add links to your dashboard, you must first add them as an "app".
|
||||
Navigate to this page to add an app:
|
||||
|
||||

|
||||
|
||||
Next, click on the ``New app`` button. You'll need to provide a name and an icon to create an app.
|
||||
Next, click on the `New app` button. You'll need to provide a name and an icon to create an app.
|
||||
The name must be at least one character long but you can provide any image URL for the icon.
|
||||
Homarr ships with over 11K icons - so you can just search by entering a search text instead.
|
||||
|
||||
@@ -149,8 +180,9 @@ After you created the app, you'll see in on the apps page:
|
||||

|
||||
|
||||
## Arrange and organize your board
|
||||
|
||||
Let's add your freshly created app, from the previous step, to the board.
|
||||
To do this, edit your previously created board by clicking on the big ``Open board`` button:
|
||||
To do this, edit your previously created board by clicking on the big `Open board` button:
|
||||
|
||||

|
||||
|
||||
@@ -165,23 +197,38 @@ Here you have the following options in the header:
|
||||
|
||||
When entering the edit mode, you'll get some additional buttons to work with:
|
||||
|
||||
<img src={manageBoardHeaderEditMode} alt={"Screenshot of buttons in the edit mode"} width={200} />
|
||||
<img
|
||||
src={manageBoardHeaderEditMode}
|
||||
alt={"Screenshot of buttons in the edit mode"}
|
||||
width={200}
|
||||
/>
|
||||
|
||||
- **Add item**: Click this button to add new elements to your page.
|
||||
- **Exit edit mode**: Click this btuton to save your changes and exit edit mode.
|
||||
|
||||
To continue, click the option "New Item":
|
||||
|
||||
<img src={manageBoardHeaderAddItem} alt={"Screenshot of the add item menu"} width={200} />
|
||||
<img
|
||||
src={manageBoardHeaderAddItem}
|
||||
alt={"Screenshot of the add item menu"}
|
||||
width={200}
|
||||
/>
|
||||
|
||||
Next, you can choose what item you'd like to add.
|
||||
We will discuss the other options in the following sections - for now choose "App" to continue.
|
||||
|
||||
<img src={manageBoardHeaderChooseItem} alt={"Screenshot of choosing the app item in the modal"} width={300} />
|
||||
<img
|
||||
src={manageBoardHeaderChooseItem}
|
||||
alt={"Screenshot of choosing the app item in the modal"}
|
||||
width={300}
|
||||
/>
|
||||
|
||||
As soon as you chose the app, the modal will close and you'll get the following item on your board:
|
||||
|
||||
<img src={manageBoardNoApp} alt="Screenshot of the No app element on the board" />
|
||||
<img
|
||||
src={manageBoardNoApp}
|
||||
alt="Screenshot of the No app element on the board"
|
||||
/>
|
||||
|
||||
This is expected, because you haven't told Homarr what app it should show in this item.
|
||||
To configure this, click on the small 3 dots at the top of the item in the edit mode and choose "Edit item" in the menu.
|
||||
@@ -228,17 +275,18 @@ Be sure to save your changes by exiting edit mode!
|
||||

|
||||
|
||||
## Adding your first integration
|
||||
|
||||
Now it's time to integrate some basic data into your board.
|
||||
To do this, we first need to create an integration.
|
||||
An integration is a connection between Homarr and a third party application - usually something that you run and deploy yourself.
|
||||
|
||||
Navigate to ``Manage`` > ``Integrations`` (for example via your user menu).
|
||||
Navigate to `Manage` > `Integrations` (for example via your user menu).
|
||||
As an example, we are going to use the public demo instance of **Dash.** - a popular application to monitor CPU / hardware usage.
|
||||
Choose the **Dash.** integration from the list of available integrations:
|
||||
|
||||

|
||||
|
||||
Next, choose a name (or leave it at default), enter ``https://dash.mauz.dev/`` as the URL in the form and click on ``Test connection and create``.
|
||||
Next, choose a name (or leave it at default), enter `https://dash.mauz.dev/` as the URL in the form and click on `Test connection and create`.
|
||||
Pressing the save button will perform a test request to the integration, to confirm that the networking setup is working as expected.
|
||||
Sometimes invalid configurations, firewalls and other mistakes can lead to connectivity problems - [follow this guide here](/docs/management/integrations/#troubleshooting-connection) if you experience issues.
|
||||
|
||||
@@ -254,13 +302,14 @@ the target application or your router.
|
||||
:::
|
||||
|
||||
## Embed integration data using widgets
|
||||
|
||||
Let's add your integration, that you have configured in the section above, as a widget to your board.
|
||||
First, navigate back to your board that you want to add it to.
|
||||
Next, enter edit mode again and add another item to your board:
|
||||
|
||||

|
||||
|
||||
Instead of choosing an app, let's choose ``System Health Monitoring`` this time.
|
||||
Instead of choosing an app, let's choose `System Health Monitoring` this time.
|
||||
Click on "Add to board":
|
||||
|
||||

|
||||
@@ -290,10 +339,12 @@ As with every change, don't forget to save your changes using the exit edit mode
|
||||

|
||||
|
||||
## Grouping your items
|
||||
|
||||
As the last step, let's organize your freshly created items using two different options.
|
||||
You can choose either or both of them on your boards.
|
||||
|
||||
### Using dynamic sections
|
||||
|
||||
Dynamic sections are a simple, resizable visual group to group items.
|
||||
|
||||

|
||||
@@ -303,6 +354,7 @@ After adding a dynamic section, you can simply drag and drop items into the sect
|
||||

|
||||
|
||||
### Using categories
|
||||
|
||||
Categories are more advanced and complicated than dynamic sections.
|
||||
To create a category, open the add item modal and choose "new category":
|
||||
|
||||
@@ -329,28 +381,33 @@ You cannot reduce their horizontal width.
|
||||
After completing the onboarding, Homarr provides interactive guided tours to help you discover key features.
|
||||
|
||||
### Management tour
|
||||
|
||||
When you first visit the management area (`/manage`), Homarr walks you through the main pages: boards, apps, integrations, and users (admin only).
|
||||
Each page highlights the list view and the create action so you know where to find and add things.
|
||||
|
||||
### Board tour
|
||||
|
||||
When you first open a board, a tour highlights the board header elements: search, edit mode, settings, board switcher, and user menu.
|
||||
|
||||
### Tour controls
|
||||
|
||||
- Press **Enter** (or click **Next**) to advance through tour steps
|
||||
- Tours auto-start on first visit and do not repeat once completed
|
||||
- Tours are skipped on mobile screens (below `sm` breakpoint)
|
||||
- You can **reset tours** from the user settings page under **Manage > Users > [Your user] > General**
|
||||
|
||||
## Next steps
|
||||
|
||||
You're done with the basic setup 🔥. Now it's time to organize and customize even more.
|
||||
We highly recommend you to experiment with different options, styles and configurations.
|
||||
See these curated pages for your next steps:
|
||||
- Create new apps using the [apps management](../management/apps/)
|
||||
- Customize your board or add more boards using the [boards management](../management/boards/)
|
||||
- Integrate software that you use using the [integrations management](../management/integrations)
|
||||
|
||||
- Create new apps using the [apps management](/docs/management/apps/)
|
||||
- Customize your board or add more boards using the [boards management](/docs/management/boards/)
|
||||
- Integrate software that you use using the [integrations management](/docs/management/integrations/)
|
||||
- Choose from our beautiful and interactive [widgets](/docs/category/widgets)
|
||||
- See [all supported integrations](/docs/category/integrations)
|
||||
- Integrate with Homarr with your own application using [the API](../management/api/)
|
||||
- Integrate with Homarr with your own application using [the API](/docs/management/api/)
|
||||
|
||||
:::danger
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
A Helm chart to deploy homarr for Kubernetes
|
||||
|
||||
@@ -383,6 +383,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
|
||||
| containerPorts | object | `{"http":{"port":7575,"protocol":"TCP"}}` | containerPorts defines the ports to open on the container. It is a map where each entry specifies: - `port` (int) (required): The port number to expose inside the container. - `protocol` (string) (required): The network protocol (TCP or UDP) used for the port. - `disabled` (bool) : Optional flag to disable this port (defaults to false). Can be overridden via Helm values. By default, this configuration exposes TCP port 7575 with the name `http`. |
|
||||
| database.migrationEnabled | bool | `true` | Database migration configuration. DB_MIGRATIONS_DISABLED Set to `true` to disable database migrations. Migrations are enabled by default (`false`). |
|
||||
| database.type | string | `"sqlite"` | Database type: sqlite, mysql or postgresql |
|
||||
| env.AUTH_COOKIE_PREFIX | string | `"homarr"` | Prefix used for all authentication cookies. Change if you run another Auth.js/NextAuth app on the same hostname to avoid cookie name collisions. Only letters, numbers, hyphens and underscores. |
|
||||
| env.AUTH_LDAP_BASE | string | `nil` | Base dn of your LDAP server |
|
||||
| env.AUTH_LDAP_BIND_DN | string | `nil` | User used for finding users and groups |
|
||||
| env.AUTH_LDAP_GROUP_CLASS | string | `"groupOfUniqueNames"` | Class used for querying groups |
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: "ArchiveTeam Warrior"
|
||||
description: "ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { AddingIntegration } from "@site/src/components/integrations/adding";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
import { archiveTeamWarriorIntegration } from ".";
|
||||
|
||||
<IntegrationHeader integration={archiveTeamWarriorIntegration} categories={["archiving"]} />
|
||||
|
||||
The ArchiveTeam Warrior integration lets Homarr connect to your ArchiveTeam Warrior instance so you can manage and monitor your archiving tasks directly from your dashboard.
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
|
||||
ArchiveTeam Warrior does not require authentication by default. If your instance is protected with basic authentication, you can provide a username and password.
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const archiveTeamWarriorIntegration: IntegrationDefinition = {
|
||||
name: "ArchiveTeam Warrior",
|
||||
description:
|
||||
"ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/archiveteam-warrior.png",
|
||||
path: "../../integrations/archiveteam-warrior",
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 786 KiB After Width: | Height: | Size: 948 KiB |
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: 'Codeberg'
|
||||
description: "Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { codebergIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={codebergIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -1,9 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const codebergIntegration: IntegrationDefinition = {
|
||||
name: "Codeberg",
|
||||
description:
|
||||
"Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/codeberg.svg",
|
||||
path: "../../integrations/codeberg",
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: 'Docker Hub'
|
||||
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { dockerHubIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={dockerHubIntegration}
|
||||
categories={['Container Management', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['username', 'personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const dockerHubIntegration: IntegrationDefinition = {
|
||||
name: "Docker Hub",
|
||||
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/docker.svg",
|
||||
path: "../../integrations/docker-hub",
|
||||
};
|
||||
@@ -58,12 +58,12 @@ In Windows, you'll need to adjust the slashes: `-v //var/run/docker.sock:/var/ru
|
||||
### Managing your Docker containers within Homarr
|
||||
Homarr allows you to interact with Docker containers running on your system.
|
||||
Each container row has an action menu with the following options:
|
||||
- **View logs** — opens a dedicated logs page with a live terminal
|
||||
- **Start** — start a stopped container
|
||||
- **Stop** — stop a running container
|
||||
- **Restart** — restart a container
|
||||
- **Remove** — remove a container
|
||||
- **Add to Homarr** — create a bookmark app from the container
|
||||
- **View logs** - opens a dedicated logs page with a live terminal
|
||||
- **Start** - start a stopped container
|
||||
- **Stop** - stop a running container
|
||||
- **Restart** - restart a container
|
||||
- **Remove** - remove a container
|
||||
- **Add to Homarr** - create a bookmark app from the container
|
||||
|
||||
You can also search and filter containers by **container** or **image** name:
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
title: 'GitHub Container Registry'
|
||||
description: "GitHub Container Registry is a platform for hosting and managing packages."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { gitHubContainerRegistryIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gitHubContainerRegistryIntegration}
|
||||
categories={['Package Management', 'Software']}
|
||||
/>
|
||||
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ["personalAccessToken"],
|
||||
steps: []
|
||||
}, {
|
||||
tabLabel: "GitHub App",
|
||||
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
|
||||
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
|
||||
}]} />
|
||||
@@ -1,11 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gitHubContainerRegistryIntegration: IntegrationDefinition = {
|
||||
name: "GitHub Container Registry",
|
||||
description: "GitHub Container Registry is a platform for hosting and managing packages.",
|
||||
iconUrl: {
|
||||
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
|
||||
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
|
||||
},
|
||||
path: "../../integrations/github-packages",
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
title: 'GitHub'
|
||||
description: "GitHub is a web-based platform for version control and collaboration."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { githubIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={githubIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}, {
|
||||
tabLabel: "GitHub App",
|
||||
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
|
||||
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
|
||||
}]} />
|
||||
@@ -1,11 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const githubIntegration: IntegrationDefinition = {
|
||||
name: "GitHub",
|
||||
description: "GitHub is a web-based platform for version control and collaboration.",
|
||||
iconUrl: {
|
||||
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
|
||||
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
|
||||
},
|
||||
path: "../../integrations/github",
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: 'GitLab'
|
||||
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { gitlabIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gitlabIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gitlabIntegration: IntegrationDefinition = {
|
||||
name: "GitLab",
|
||||
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitlab.svg",
|
||||
path: "../../integrations/gitlab",
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: 'Gluetun'
|
||||
description: 'Connect Gluetun to Homarr as a VPN integration.'
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { gluetunIntegration } from '.';
|
||||
import { vpnWidget } from '@site/docs/widgets/vpn';
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gluetunIntegration}
|
||||
categories={['VPN', 'Network']}
|
||||
/>
|
||||
|
||||
[Gluetun](https://github.com/qdm12/gluetun) exposes a control server that Homarr can query to report the VPN tunnel
|
||||
status, DNS status, public IP address, and active provider details in the VPN widget.
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: vpnWidget,
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Setting up the Gluetun control server
|
||||
|
||||
Refer to the [Gluetun control server documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication) to enable and configure authentication.
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
|
||||
Gluetun's control server can be left unprotected or secured with either an API key or HTTP Basic authentication.
|
||||
Choose the option that matches how your Gluetun instance integration is set up.
|
||||
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
},{
|
||||
credentials: ['apiKey'],
|
||||
steps: [],
|
||||
},,]} />
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gluetunIntegration: IntegrationDefinition = {
|
||||
name: "Gluetun",
|
||||
description: "VPN control server integration for Homarr's VPN widget.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gluetun.svg",
|
||||
path: "../../integrations/gluetun",
|
||||
};
|
||||
+11
-12
@@ -1,26 +1,25 @@
|
||||
---
|
||||
title: 'NPM'
|
||||
description: "NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages."
|
||||
title: 'Gotify'
|
||||
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { npmIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { gotifyIntegration } from '.';
|
||||
|
||||
<IntegrationHeader
|
||||
integration={npmIntegration}
|
||||
categories={['Package Management', 'Software']}
|
||||
integration={gotifyIntegration}
|
||||
categories={['Notifications', 'Messaging']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
widget: notificationsWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
@@ -29,6 +28,6 @@ import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
credentials: ['username', 'password'],
|
||||
steps: []
|
||||
}]} />
|
||||
}]} />
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gotifyIntegration: IntegrationDefinition = {
|
||||
name: "Gotify",
|
||||
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gotify.svg",
|
||||
path: "../../integrations/gotify",
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: 'LinuxServer.io'
|
||||
description: "LinuxServer.io is a platform for managing and deploying containerized applications."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { linuxServerIoIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={linuxServerIoIntegration}
|
||||
categories={['Container Management', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const linuxServerIoIntegration: IntegrationDefinition = {
|
||||
name: "LinuxServer.io",
|
||||
description: "LinuxServer.io is a platform for managing and deploying containerized applications.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
|
||||
path: "../../integrations/linux-server-io",
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const npmIntegration: IntegrationDefinition = {
|
||||
name: "NPM",
|
||||
description:
|
||||
"NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/npm.svg",
|
||||
path: "../../integrations/npm",
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: 'Quay'
|
||||
description: "Quay is a container registry for storing and managing container images."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { quayIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={quayIntegration}
|
||||
categories={['Container Registry', 'Software']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ["personalAccessToken"],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const quayIntegration: IntegrationDefinition = {
|
||||
name: "Quay",
|
||||
description: "Quay is a container registry for storing and managing container images.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png",
|
||||
path: "../../integrations/quay",
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
title: 'Search.ch'
|
||||
description: "Search.ch supports searching for timetables in Switzerland."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
import { searchChIntegration } from '.';
|
||||
import Alert from '@theme/Admonition';
|
||||
import { timetableWidget } from '@site/docs/widgets/timetable';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={searchChIntegration}
|
||||
categories={['Timetable']}
|
||||
/>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: timetableWidget }]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
@@ -1,7 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
export const searchChIntegration: IntegrationDefinition = {
|
||||
name: "Search.ch",
|
||||
description: "Search.ch supports searching for timetables in Switzerland.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/search-ch.png",
|
||||
path: "../../integrations/search-ch",
|
||||
};
|
||||
@@ -19,7 +19,7 @@ import { dnsHoleControlsWidget } from '@site/docs/widgets/dns-hole-controls';
|
||||
/>
|
||||
|
||||
<Alert type="note" className='my-2'>
|
||||
Homarr supports Technitium DNS v11 and above. The correct API version is detected automatically — no manual configuration is required.
|
||||
Homarr supports Technitium DNS v11 and above. The correct API version is detected automatically - no manual configuration is required.
|
||||
</Alert>
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
@@ -44,7 +44,7 @@ import Alert from '@theme/Admonition';
|
||||
steps: [
|
||||
"In the TrueNAS web interface, click your user avatar in the top-right corner and select \"API Keys\".",
|
||||
"Click \"Add\", give the key a name such as \"Homarr\", and confirm.",
|
||||
"Copy the generated key immediately — TrueNAS only shows it once — and paste it as the API Key when creating the integration in Homarr.",
|
||||
"Copy the generated key immediately - TrueNAS only shows it once - and paste it as the API Key when creating the integration in Homarr.",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ import { umamiWidget } from '../../widgets/umami';
|
||||
credentials: ['username', 'password'],
|
||||
steps: [
|
||||
'Use the username and password of your Umami account',
|
||||
'Note: username/password auth is for self-hosted instances only — Umami Cloud requires an API key',
|
||||
'Note: username/password auth is for self-hosted instances only - Umami Cloud requires an API key',
|
||||
]
|
||||
}
|
||||
]} />
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 533 KiB |
@@ -6,8 +6,6 @@ tags:
|
||||
- Interface
|
||||
---
|
||||
|
||||
import { ApiReferenceReact } from "@scalar/api-reference-react"
|
||||
|
||||
Using the Homarr API, you can interface with the application with your own code and business logic.
|
||||
It enables you to trigger almost all actions that you can perform from the Homarr UI.
|
||||
With the API, you can build resilient systems that automatically update Homarr with data - for example syncing it with
|
||||
@@ -15,20 +13,25 @@ your deployments in a cluster or dynamically adding integrations depending on ho
|
||||
|
||||
This page is language-neutral, and you must implement the API on your own in your desired language.
|
||||
It requires you to know your programming language well and have a basic understanding of HTTP, JSON and networking.
|
||||
You can test out API endpoints using any OpenAPI client - for example [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/) or [Hoppscotch](https://hoppscotch.io/).
|
||||
You can test out API endpoints using any OpenAPI client - for example [Yaak](https://yaak.app/).
|
||||
|
||||
## Opening the API specification
|
||||
|
||||
The specification is implemented according to the OpenAPI standard.
|
||||
To access the documentation, you'll need to spin up an instance of Homarr.
|
||||
Navigate to ``Management`` > ``Tools`` > ``API``.
|
||||
Navigate to `Management` > `Tools` > `API`.
|
||||
You can copy the JSON url into your OpenAPI client or try out the endpoints directly from your browser.
|
||||
|
||||
Alternatively, you can [open the full interactive API reference](/api-reference) directly in your browser.
|
||||
|
||||
## Authentication
|
||||
|
||||
Many endpoints will require you to authenticate. At the top of the API reference, you'll find a button to authenticate yourself with an API key.
|
||||
You can obtain API keys from the "Authentication" tab at the top of the page.
|
||||
API keys will not expire and can be used indefinitely.
|
||||
|
||||
### API Key Format
|
||||
|
||||
API keys are provided in the format `<id>.<token>`. When making requests, include the key in the `ApiKey` header:
|
||||
|
||||
```
|
||||
@@ -36,49 +39,35 @@ ApiKey: abc123def456.xyz789tokenvalue...
|
||||
```
|
||||
|
||||
### Supported Endpoints
|
||||
|
||||
API key authentication works with both endpoint types:
|
||||
|
||||
| Endpoint Type | Path Pattern | API Key Support |
|
||||
|--------------|--------------|-----------------|
|
||||
| OpenAPI | `/api/...` | ✅ Supported |
|
||||
| tRPC | `/api/trpc/...` | ✅ Supported |
|
||||
| Endpoint Type | Path Pattern | API Key Support |
|
||||
| ------------- | --------------- | --------------- |
|
||||
| OpenAPI | `/api/...` | ✅ Supported |
|
||||
| tRPC | `/api/trpc/...` | ✅ Supported |
|
||||
|
||||
If no API key is provided, Homarr falls back to session cookie authentication (for browser-based access).
|
||||
|
||||
### Usage Examples
|
||||
|
||||
**OpenAPI endpoint:**
|
||||
|
||||
```bash
|
||||
curl -H 'ApiKey: <id>.<token>' http://localhost:7575/api/health/live
|
||||
```
|
||||
|
||||
**tRPC endpoint:**
|
||||
|
||||
```bash
|
||||
curl -H 'ApiKey: <id>.<token>' http://localhost:7575/api/trpc/app.all
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
API key authentication enables programmatic access for:
|
||||
|
||||
- Automation scripts and CI/CD pipelines
|
||||
- Third-party integrations
|
||||
- Custom dashboards and monitoring tools
|
||||
- Container orchestration systems that need to sync app definitions
|
||||
|
||||
## Specification
|
||||
|
||||
:::info
|
||||
|
||||
This section is automatically being updated hourly.
|
||||
Last update:
|
||||
``TEST`` <!-- OPEN_API_SPEC_LAST_UPDATED -->
|
||||
|
||||
:::
|
||||
|
||||
<ApiReferenceReact
|
||||
configuration={{
|
||||
url: "/api/open-api-schema.json",
|
||||
theme: "kepler",
|
||||
hideModels: false,
|
||||
hideDownloadButton: false,
|
||||
}}
|
||||
/>
|
||||
- Container orchestration systems that need to sync app definitions
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
tags:
|
||||
- Backup
|
||||
- SQLite
|
||||
- Management
|
||||
- Data
|
||||
---
|
||||
|
||||
# Backup & Restore
|
||||
|
||||
Homarr provides a built-in backup and restore system for **SQLite** instances. You can export your entire database - including apps, integrations, boards, users, and encryption keys - as a ZIP archive, and restore it on the same or a different Homarr instance.
|
||||
|
||||
:::warning
|
||||
|
||||
Backup and restore is only available when using the **SQLite** database driver. MySQL and PostgreSQL instances do not show this page.
|
||||
|
||||
:::
|
||||
|
||||
## Accessing the Backup Page
|
||||
|
||||
Navigate to `Management` → `Tools` → `Backup`. You must have **admin** permissions to access this page.
|
||||
|
||||
## Exporting a Backup
|
||||
|
||||
Click the **Export** button in the export card. You'll be prompted to type `I understand` to confirm. Once confirmed, a ZIP file downloads containing:
|
||||
|
||||
| File | Description |
|
||||
| -------------------- | ------------------------------------------------------------------- |
|
||||
| `db.sqlite` | Full SQLite database with all entities |
|
||||
| `metadata.json` | Backup metadata: Homarr version, creation timestamp, encryption key |
|
||||
|
||||
The backup includes everything: apps, integrations, secrets, boards, users, groups, permissions, search engines, custom widgets, and server settings. (It just backs-up the whole database)
|
||||
|
||||
## Restoring a Backup
|
||||
|
||||
### 1. Upload
|
||||
|
||||
Drag and drop or select a `.zip` backup file. The import card validates that the file contains a valid `db.sqlite` and metadata.
|
||||
|
||||
### 2. Preview
|
||||
|
||||
Before restoring, Homarr uses **sql.js WASM** to analyze the backup directly in your browser. You'll see:
|
||||
|
||||
- **Entity counts** - how many apps, integrations, boards, users, etc. are in the backup
|
||||
- **Board names** - which boards will be restored
|
||||
- **Migration status** - Drizzle migrations are applied in-browser with a visual timeline showing each migration step
|
||||
|
||||
### 3. Confirm
|
||||
|
||||
Type `I understand` to acknowledge that restoring will replace your current database. This cannot be undone.
|
||||
|
||||
### 4. Restore
|
||||
|
||||
The backup ZIP is sent to the server, validated, and applied. After a successful restore:
|
||||
|
||||
- **The Next.js server restarts automatically** to reload the new database state
|
||||
- Nginx and Redis (if running in Docker) stay up - only the Next.js process restarts
|
||||
- The page reloads once the server is ready
|
||||
|
||||
## Cross-Instance Restore
|
||||
|
||||
You can restore a backup from one Homarr instance onto another. Homarr handles:
|
||||
|
||||
- **Encryption key re-encryption** - If the target instance has a different `SECRET_ENCRYPTION_KEY`, integration secrets are decrypted with the old key and re-encrypted with the new one during import
|
||||
- **Version compatibility** - Backups include the Homarr version they were created with. Backward compatibility is maintained for older backup formats
|
||||
|
||||
## Backup During Onboarding
|
||||
|
||||
When setting up a new SQLite Homarr instance, the onboarding flow includes a **Restore from SQLite backup** step. You can upload a backup ZIP directly during onboarding to skip manual setup.
|
||||
|
||||
## Security
|
||||
|
||||
- Export requires admin permissions
|
||||
- The `I understand` confirmation dialog prevents accidental exports and overwrites
|
||||
- Backups contain encrypted integration secrets - they cannot be read without the `SECRET_ENCRYPTION_KEY`
|
||||
- Import only works on SQLite instances - the API routes refuse requests from MySQL/PostgreSQL instances
|
||||
@@ -1,100 +1,208 @@
|
||||
---
|
||||
title: Custom Widgets
|
||||
tags:
|
||||
- Management
|
||||
- Widgets
|
||||
- Custom
|
||||
- Custom Widgets
|
||||
- API
|
||||
- Widgets
|
||||
- JSX
|
||||
---
|
||||
|
||||
Custom widgets let you create your own dashboard widgets that fetch data from any external API and render it with configurable displays. They are managed from the **Management → Custom Widgets** page (admin only).
|
||||
# Custom Widgets
|
||||
|
||||
:::warning
|
||||
|
||||
Custom widgets are a new feature. Share feedback, ask questions, and discuss ideas in our [GitHub Discussions](https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets).
|
||||
|
||||
:::note Beta
|
||||
Custom widgets are currently in beta. Share feedback and report issues in the [GitHub discussion](https://github.com/homarr-labs/homarr/discussions/5916).
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Custom widgets connect to any HTTP API endpoint and display the response data using one of several built-in display types. You can create multiple widget definitions, each with its own API configuration, display settings, and optional secrets for authentication.
|
||||
Custom widgets allow you to fetch data from any HTTP API and display it on your Homarr dashboard. You define the API endpoint, authentication, and how the response should be rendered - no code compilation or server restart required.
|
||||
|
||||
## Creating a Custom Widget
|
||||
|
||||
Navigate to **Management → Custom Widgets** and click **New Custom Widget**. You'll need to configure:
|
||||
To create a custom widget, navigate to the **Custom Widgets** section in the management sidebar and click the **Add** button. Fill in the configuration:
|
||||
|
||||
### API Configuration
|
||||
- **Name** - Display name shown in the widget picker and on the dashboard
|
||||
- **Description** - Optional description shown in tooltips and management UI
|
||||
- **Icon URL** - Optional URL to an icon image for the widget
|
||||
- **URL** - The API endpoint to fetch data from (must include protocol, e.g. `https://api.example.com/stats`)
|
||||
- **Authentication** - How the API request is authenticated:
|
||||
- `none` - Public APIs with no authentication
|
||||
- `bearer` - Bearer token authentication
|
||||
- `basic` - HTTP Basic auth (username + password)
|
||||
- `apiKeyHeader` - API key sent as a custom HTTP header
|
||||
- `apiKeyQuery` - API key sent as a query parameter
|
||||
- **Header Name** - Custom header or query parameter name for API key auth (e.g. `X-API-Key`)
|
||||
- **HTTP Method** - `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`
|
||||
- **Request Body** - JSON string body for `POST`/`PUT`/`PATCH` requests
|
||||
- **Display Type** - How the API response is rendered (see below)
|
||||
|
||||
- **URL** — The full URL of the API endpoint to fetch
|
||||
- **Method** — HTTP method (`GET`, `POST`, etc.)
|
||||
- **Headers** — Custom HTTP headers (e.g. `Authorization`, `Content-Type`)
|
||||
- **Body** — Request body for POST/PUT methods
|
||||
- **Auth type** — Authentication method (`none`, `bearer`, `basic`, `API key in header`, `API key in query`)
|
||||
## Display Types
|
||||
|
||||
### Secrets
|
||||
| Display Type | Description | Best For |
|
||||
| -------------------- | ---------------------------------------------------- | ---------------------------------------- |
|
||||
| **Single Value** | Extracts a single value with a label and unit | Metrics: temperature, uptime, user count |
|
||||
| **Key Value** | Multiple labeled key-value pairs with optional units | System stats: CPU, memory, disk |
|
||||
| **Table** | API array data rendered as a sortable table | Lists of items with columns |
|
||||
| **Stat Grid** | Labeled values in colored stat cards | Counts: movies, series, albums |
|
||||
| **Progress Bars** | Values as progress bars with percentages | Usage: disk, quota, capacity |
|
||||
| **Status Indicator** | Green/red status dots for health checks | Service monitoring |
|
||||
| **Count Grid** | Simple counts in a grid (no colors) | Library counts |
|
||||
| **Raw** | Pretty-prints the raw JSON response | Debugging |
|
||||
| **Action Button** | Clickable button executing the API request | Triggers: restart, clear cache |
|
||||
| **Custom JSX** | Custom JSX template with Mantine v9 components | Full layout control |
|
||||
|
||||
Custom widgets support encrypted secret storage for API keys and passwords. Secrets are stored using AES-256-CBC encryption (same as integration secrets) and are **excluded from exports** for security.
|
||||
### AI-Powered JSX Generation
|
||||
|
||||
### Display Configuration
|
||||
When editing or creating a custom widget with the **Custom JSX** display type, you'll find a **Copy Prompt** button. Clicking it copies a pre-formatted prompt describing your widget's name, API endpoint, and available Mantine components. Paste this into your preferred AI assistant (ChatGPT, Claude, Copilot, etc.) to generate a JSX template tailored to your data.
|
||||
|
||||
Choose from the following display types:
|
||||
<details>
|
||||
<summary>Example: Pokédex widget using Custom JSX</summary>
|
||||
|
||||
| Display Type | Description |
|
||||
|---|---|
|
||||
| **Single Value** | Shows a single extracted value prominently |
|
||||
| **Key-Value** | Displays data as labeled key-value pairs |
|
||||
| **Table** | Renders data in a table layout |
|
||||
| **Stat Grid** | Shows multiple statistics in a grid |
|
||||
| **Progress Bars** | Visualizes values as progress bars |
|
||||
| **Status Indicator** | Shows status with colored indicators |
|
||||
| **Count Grid** | Displays count values in a grid layout |
|
||||
| **Raw** | Shows the raw API response |
|
||||
| **Action Button** | A button that triggers an API call when clicked |
|
||||
| **Custom JSX** | Write custom JSX templates using whitelisted Mantine components |
|
||||
```json
|
||||
{
|
||||
"$schema": "homarr-custom-widget-v2",
|
||||
"name": "Pokédex",
|
||||
"description": "Browseable Pokémon list",
|
||||
"iconUrl": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/poke-ball.png",
|
||||
"url": "https://pokeapi.co/api/v2/pokemon?limit=75",
|
||||
"authType": "none",
|
||||
"method": "GET",
|
||||
"displayType": "customJsx",
|
||||
"displayConfig": {
|
||||
"type": "customJsx",
|
||||
"template": "<Stack gap=\"md\" p=\"xs\">\n <Card withBorder radius=\"xl\" p=\"md\" shadow=\"md\">\n <Group justify=\"space-between\">\n <Title order={3}>Pokédex</Title>\n </Group>\n </Card>\n <PaginatedList pageSize={12}>\n <Grid gutter=\"sm\">\n {data.results.map((pokemon, i) =>\n <Grid.Col span={1.5}>\n <Anchor href={pokemon.url} target=\"_blank\" underline=\"never\">\n <Card withBorder radius=\"xl\" p=\"xs\" shadow=\"md\">\n <Stack gap=\"sm\" align=\"center\">\n <Group justify=\"space-between\" style={{ width: \"100%\" }}>\n <Badge size=\"sm\" color=\"red\">#{i + 1}</Badge>\n <Text fw={800} tt=\"capitalize\">{pokemon.name}</Text>\n </Group>\n <Avatar\n src={\"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/\" + (i + 1) + \".png\"}\n size={92}\n radius=\"xl\"\n />\n </Stack>\n </Card>\n </Anchor>\n </Grid.Col>\n )}\n </Grid>\n </PaginatedList>\n</Stack>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip Refresh interval
|
||||
When adding a Custom API widget to a board, you can configure its **refresh interval** (1–3600 seconds, default 30s) in the widget's board options.
|
||||
:::
|
||||
</details>
|
||||
|
||||
## Custom JSX Display
|
||||
## Importing and Exporting
|
||||
|
||||
The Custom JSX display type lets you write JSX templates to create rich, interactive displays. For security, only whitelisted components are allowed:
|
||||
Custom widgets can be exported and imported as JSON files. To export, select the widget in the list and click the export button. To import, click the import button and paste the JSON. Secrets (API keys, passwords) are never included in exports and must be configured after import.
|
||||
|
||||
- **Mantine components** — `Text`, `Badge`, `Group`, `Stack`, `Paper`, `Progress`, `RingProgress`, `ThemeIcon`, `Anchor`, `Image`, `Divider`, `Code`, `Table`, and more
|
||||
- **Interactive components** — `PaginatedList`, `TabsContainer`, `Collapsible`, `StatBar`, `TypeBadge`
|
||||
The import format uses `"$schema": "homarr-custom-widget-v2"` to identify the widget version.
|
||||
|
||||
Templates have access to the API response data and can use JSONPath expressions to extract values.
|
||||
## Available Mantine Components for Custom JSX
|
||||
|
||||
:::warning Security
|
||||
The JSX sandbox blocks dangerous patterns like `eval`, `Function`, `__proto__`, and `javascript:` URLs. However, only admins can create custom widgets.
|
||||
:::
|
||||
When using the `customJsx` display type, your template has access to a whitelist of [Mantine v9](https://mantine.dev) components. Unrecognized components, event handlers, and dangerous tags are stripped for security.
|
||||
|
||||
## Live Preview
|
||||
### Layout
|
||||
|
||||
The widget editor includes a live preview panel that shows how the widget will look with real API data. Use the **Debug** panel to inspect the raw API response and test JSONPath expressions.
|
||||
| Component | Doc |
|
||||
| ------------------- | --------------------------------------------- |
|
||||
| `Stack` | [docs](https://mantine.dev/core/stack) |
|
||||
| `Group` | [docs](https://mantine.dev/core/group) |
|
||||
| `Flex` | [docs](https://mantine.dev/core/flex) |
|
||||
| `Grid` / `Grid.Col` | [docs](https://mantine.dev/core/grid) |
|
||||
| `SimpleGrid` | [docs](https://mantine.dev/core/simple-grid) |
|
||||
| `Center` | [docs](https://mantine.dev/core/center) |
|
||||
| `Container` | [docs](https://mantine.dev/core/container) |
|
||||
| `Space` | [docs](https://mantine.dev/core/space) |
|
||||
| `AspectRatio` | [docs](https://mantine.dev/core/aspect-ratio) |
|
||||
|
||||
## Copy AI Prompt
|
||||
### Typography & Content
|
||||
|
||||
Click the **Copy AI Prompt** button to copy a prompt containing the API's JSON Schema and sample response. Paste this into an LLM to get help writing JSONPath expressions or Custom JSX templates.
|
||||
| Component | Doc |
|
||||
| ----------------- | ------------------------------------------------- |
|
||||
| `Text` | [docs](https://mantine.dev/core/text) |
|
||||
| `Title` | [docs](https://mantine.dev/core/title) |
|
||||
| `Code` | [docs](https://mantine.dev/core/code) |
|
||||
| `Highlight` | [docs](https://mantine.dev/core/highlight) |
|
||||
| `Mark` | [docs](https://mantine.dev/core/mark) |
|
||||
| `Kbd` | [docs](https://mantine.dev/core/kbd) |
|
||||
| `Blockquote` | [docs](https://mantine.dev/core/blockquote) |
|
||||
| `Anchor` | [docs](https://mantine.dev/core/anchor) |
|
||||
| `NumberFormatter` | [docs](https://mantine.dev/core/number-formatter) |
|
||||
|
||||
## Import & Export
|
||||
### Data Display
|
||||
|
||||
Custom widget definitions can be exported as JSON files and imported on other Homarr instances:
|
||||
| Component | Doc |
|
||||
| ------------------------- | ------------------------------------------------- |
|
||||
| `Badge` | [docs](https://mantine.dev/core/badge) |
|
||||
| `Card` / `Card.Section` | [docs](https://mantine.dev/core/card) |
|
||||
| `Paper` | [docs](https://mantine.dev/core/paper) |
|
||||
| `Alert` | [docs](https://mantine.dev/core/alert) |
|
||||
| `ThemeIcon` | [docs](https://mantine.dev/core/theme-icon) |
|
||||
| `ColorSwatch` | [docs](https://mantine.dev/core/color-swatch) |
|
||||
| `Avatar` / `Avatar.Group` | [docs](https://mantine.dev/core/avatar) |
|
||||
| `Image` | [docs](https://mantine.dev/core/image) |
|
||||
| `BackgroundImage` | [docs](https://mantine.dev/core/background-image) |
|
||||
| `Indicator` | [docs](https://mantine.dev/core/indicator) |
|
||||
| `Pill` | [docs](https://mantine.dev/core/pill) |
|
||||
| `Spoiler` | [docs](https://mantine.dev/core/spoiler) |
|
||||
|
||||
- **Export** — Downloads a JSON file with the widget definition (secrets are excluded)
|
||||
- **Import** — Upload a previously exported JSON file to create a new widget definition
|
||||
- The export format uses `$schema: "homarr-custom-widget-v2"`
|
||||
### Tables
|
||||
|
||||
## Using Custom Widgets on a Board
|
||||
| Component | Doc |
|
||||
| --------------------------------------- | -------------------------------------- |
|
||||
| `Table` / `Table.Thead` / `Table.Tbody` | [docs](https://mantine.dev/core/table) |
|
||||
| `Table.Tr` / `Table.Th` / `Table.Td` | [docs](https://mantine.dev/core/table) |
|
||||
|
||||
Once created, custom widgets appear in the widget picker when editing a board. Each custom widget definition can be added to any board like a built-in widget.
|
||||
### Lists
|
||||
|
||||
## Seed Fixtures
|
||||
| Component | Doc |
|
||||
| ---------------------------- | ----------------------------------------- |
|
||||
| `List` / `List.Item` | [docs](https://mantine.dev/core/list) |
|
||||
| `Timeline` / `Timeline.Item` | [docs](https://mantine.dev/core/timeline) |
|
||||
|
||||
Homarr ships with example custom widget definitions (disabled by default) to help you get started:
|
||||
### Feedback & Loading
|
||||
|
||||
- **Currency Exchange (JPY)** — Live exchange rates
|
||||
- **Random Dog Fact** — Random dog facts
|
||||
- **Jellyfin library** — Jellyfin server statistics
|
||||
| Component | Doc |
|
||||
| ------------------------------- | ---------------------------------------------- |
|
||||
| `Progress` / `Progress.Section` | [docs](https://mantine.dev/core/progress) |
|
||||
| `RingProgress` | [docs](https://mantine.dev/core/ring-progress) |
|
||||
| `Skeleton` | [docs](https://mantine.dev/core/skeleton) |
|
||||
| `Loader` | [docs](https://mantine.dev/core/loader) |
|
||||
|
||||
Enable them from the custom widgets management page or use them as templates for your own widgets.
|
||||
### Navigation & Overlay
|
||||
|
||||
Additional examples like the Pokédex are available as importable JSON files in the repository under `packages/db/seed/custom-widgets/`.
|
||||
| Component | Doc |
|
||||
| ------------------------------------------------------------------------ | -------------------------------------------- |
|
||||
| `TabsContainer` / `TabPanel` | Custom interactive component |
|
||||
| `Accordion` / `Accordion.Item` / `Accordion.Control` / `Accordion.Panel` | [docs](https://mantine.dev/core/accordion) |
|
||||
| `ScrollArea` | [docs](https://mantine.dev/core/scroll-area) |
|
||||
| `Tooltip` | [docs](https://mantine.dev/core/tooltip) |
|
||||
|
||||
### Charts
|
||||
|
||||
| Component | Doc |
|
||||
| ---------------- | --------------------------------------------------- |
|
||||
| `AreaChart` | [docs](https://mantine.dev/charts/area-chart) |
|
||||
| `BarChart` | [docs](https://mantine.dev/charts/bar-chart) |
|
||||
| `LineChart` | [docs](https://mantine.dev/charts/line-chart) |
|
||||
| `DonutChart` | [docs](https://mantine.dev/charts/donut-chart) |
|
||||
| `PieChart` | [docs](https://mantine.dev/charts/pie-chart) |
|
||||
| `RadarChart` | [docs](https://mantine.dev/charts/radar-chart) |
|
||||
| `RadialBarChart` | [docs](https://mantine.dev/charts/radial-bar-chart) |
|
||||
| `Sparkline` | [docs](https://mantine.dev/charts/sparkline) |
|
||||
|
||||
### Interactive Components
|
||||
|
||||
| Component | Description |
|
||||
| --------------- | ---------------------------------------- |
|
||||
| `PaginatedList` | Client-side pagination wrapper |
|
||||
| `Collapsible` | Expandable/collapsible section |
|
||||
| `StatBar` | Horizontal stat bar |
|
||||
| `TypeBadge` | Type-styled badge |
|
||||
| `Divider` | [docs](https://mantine.dev/core/divider) |
|
||||
|
||||
## Template Bindings
|
||||
|
||||
Your template receives:
|
||||
|
||||
- `data` - The sanitized API response object. Access fields via `data.fieldName` or `data.results[0].name`.
|
||||
- `String(v)` - Type-safe string conversion
|
||||
- `Number(v)` - Type-safe number conversion
|
||||
- `Boolean(v)` - Type-safe boolean conversion
|
||||
- `Math` - Safe subset: `round`, `floor`, `ceil`, `abs`, `min`, `max`, `pow`, `sqrt`, `PI`
|
||||
- `JSON.stringify(v)` - JSON serialization
|
||||
- `Array.isArray(v)` - Array type check
|
||||
- `Object.keys(v)`, `Object.values(v)`, `Object.entries(v)` - Object utilities
|
||||
|
||||
## Security Constraints
|
||||
|
||||
- Event handlers (`onClick`, `onChange`, etc.) are stripped
|
||||
- Dangerous HTML attributes (`dangerouslySetInnerHTML`) are stripped
|
||||
- `script`, `iframe`, `object`, `embed`, `form`, `style`, `link`, `meta`, `base` tags are forbidden
|
||||
- Template strings containing `eval`, `Function`, `import`, `require`, `globalThis`, `window`, `document`, `fetch` are rejected
|
||||
- Anchor `href` attributes are validated to only allow `https://`, relative (`/`), and hash (`#`) URLs
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
tags:
|
||||
- MCP
|
||||
- API
|
||||
- AI
|
||||
- Automation
|
||||
---
|
||||
|
||||
# Model Context Protocol (MCP)
|
||||
|
||||
Homarr exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at `/api/mcp/mcp`, enabling AI assistants like Claude, Cursor, VS Code Copilot, and Claude Code to interact with your Homarr instance using natural language.
|
||||
|
||||
With MCP, you can ask your AI assistant to manage apps, integrations, users, boards, Docker containers, media servers, DNS settings, and more - all through Homarr's existing tRPC API, now exposed as 50+ AI-callable tools.
|
||||
|
||||
## Getting Started
|
||||
|
||||
The MCP endpoint lives under the **API** page at `Management` → `Tools` → `API` → **MCP** tab.
|
||||
|
||||

|
||||
|
||||
### 1. Create an API Key
|
||||
|
||||
Navigate to the **Authentication** tab in the API page and create an API key. API keys follow the format `<id>.<token>`.
|
||||
You can also create one directly from the MCP tab if you don't have any keys yet.
|
||||
|
||||
### 2. Choose a Transport
|
||||
|
||||
| Transport | Best For | Requirements |
|
||||
| ------------------- | ------------------------------------------------------------ | ------------------------------ |
|
||||
| **Streamable HTTP** | Cursor, Claude Desktop, VS Code Copilot, Claude Code | Direct HTTP access to Homarr |
|
||||
| **STDIO** | Firewall-restricted environments, offline-compatible clients | `npx` and `mcp-remote` package |
|
||||
|
||||
**Streamable HTTP** is recommended for most setups. It works with all major AI clients.
|
||||
|
||||
### 3. Configure Your Client
|
||||
|
||||
Add the following to your client's MCP configuration file:
|
||||
|
||||
**Streamable HTTP** (recommended):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"homarr": {
|
||||
"url": "https://your-homarr-instance.com/api/mcp/mcp",
|
||||
"headers": {
|
||||
"ApiKey": "<your-api-key>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Where to place this config:
|
||||
|
||||
| Client | Config File |
|
||||
| --------------- | ----------------------------------------------- |
|
||||
| Cursor | `.cursor/mcp.json` |
|
||||
| Claude Desktop | `claude_desktop_config.json` |
|
||||
| Claude Code | `.claude/mcp.json` (or `claude mcp add homarr`) |
|
||||
| VS Code Copilot | `.vscode/mcp.json` |
|
||||
|
||||
:::warning
|
||||
|
||||
Replace `<your-api-key>` with your actual API key. Never commit API keys to version control.
|
||||
|
||||
:::
|
||||
|
||||
**STDIO** (alternative):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"homarr": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"mcp-remote",
|
||||
"https://your-homarr-instance.com/api/mcp/mcp",
|
||||
"--header",
|
||||
"ApiKey:<your-api-key>"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This option tunnels MCP communication through STDIO using the `mcp-remote` package. Useful when the AI client cannot make HTTP requests directly.
|
||||
|
||||
## Authentication
|
||||
|
||||
MCP supports two authentication methods:
|
||||
|
||||
| Method | Description | Best For |
|
||||
| -------------------- | ----------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| **API Key** | Include `ApiKey: <id>.<token>` header in all requests | Scripts, CI/CD, direct client configs |
|
||||
| **OAuth 2.1 + PKCE** | Full OAuth flow with dynamic client registration | MCP clients with OAuth support (e.g. `opencode mcp auth homarr`) |
|
||||
|
||||
API key authentication is simpler and recommended for most setups. The API key inherits the permissions of the user who created it. Tools that require higher privileges will return permission errors if the key lacks access.
|
||||
|
||||
## Testing the Connection
|
||||
|
||||
Verify your MCP endpoint is working:
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-H "ApiKey: <your-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
|
||||
https://your-homarr-instance.com/api/mcp/mcp
|
||||
```
|
||||
|
||||
A successful response includes the server capabilities and available tools. Use `tools/list` to see all available tools:
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-H "ApiKey: <your-api-key>" \
|
||||
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
|
||||
https://your-homarr-instance.com/api/mcp/mcp
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
Homarr exposes 50+ tools organized by namespace. Each tool is either a **query** (GET, read-only) or **mutation** (POST, write action).
|
||||
|
||||
| Namespace | Example Tools | Type |
|
||||
| ------------------ | -------------------------------------------------------- | ---------- |
|
||||
| `app` | List all apps, get app by ID, create/update/delete app | GET + POST |
|
||||
| `apiKeys` | List API keys | GET |
|
||||
| `beszel` | Systems status, alerts, historical system metrics | GET |
|
||||
| `board` | List boards, get board by ID, create/update/delete board | GET + POST |
|
||||
| `docker` | List containers, inspect container, start/stop/restart | GET + POST |
|
||||
| `icon` | Search icons | GET |
|
||||
| `info` | Server health, version info | GET |
|
||||
| `integration` | List integrations, test connection, delete integration | GET + POST |
|
||||
| `invite` | List invitations, delete invite | GET + POST |
|
||||
| `calendar` | Calendar events from media servers | GET |
|
||||
| `dnsHole` | DNS hole summary, toggle blocking | GET + POST |
|
||||
| `downloads` | Active downloads, pause/resume/delete | GET + POST |
|
||||
| `healthMonitoring` | Resource usage stats | GET |
|
||||
| `mediaRequests` | Approve/decline media requests | GET + POST |
|
||||
| `mediaServer` | Media server sessions and stats | GET |
|
||||
| `smartHome` | Execute automations, device states | GET + POST |
|
||||
|
||||
The tools table on the MCP tab is dynamically generated - it always reflects the latest available procedures from your Homarr instance.
|
||||
|
||||
## Security
|
||||
|
||||
- API keys are required for authentication - unauthenticated requests are rejected
|
||||
- Tools inherit the permissions of the API key's owner
|
||||
- OAuth endpoints are rate-limited to prevent abuse
|
||||
- Tool execution errors are contained - a failing tool call does not crash the MCP server
|
||||
- All communication uses HTTPS (configure HTTP → HTTPS redirects on your reverse proxy)
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **"Show me all apps on my Homarr dashboard"** - AI lists all apps with their names and URLs
|
||||
- **"Restart the Plex Docker container"** - AI finds and restarts the container via Docker integration
|
||||
- **"What's downloading right now?"** - AI fetches active download status across all download integrations
|
||||
- **"Create a new app pointing to my Grafana instance"** - AI creates an app with the correct icon and URL
|
||||
- **"How many users are on my Homarr instance?"** - AI lists all users (admin only)
|
||||
- **"Toggle Pi-hole blocking for 5 minutes"** - AI interacts with DNS controls
|
||||
- **"How are my servers doing?"** - AI fetches Beszel system stats including CPU, memory, disk, and alerts
|
||||
@@ -42,7 +42,7 @@ Homarr will replace the ``%s`` with the term that the user entered.
|
||||
## Media request search
|
||||
|
||||
Jellyseerr, Overseerr, and Seerr integrations have a dedicated **Spotlight mode** for searching and requesting media.
|
||||
This mode does not require creating a search engine entry — it is available automatically when you have a compatible integration configured.
|
||||
This mode does not require creating a search engine entry - it is available automatically when you have a compatible integration configured.
|
||||
|
||||
You can access it from:
|
||||
- The **Modes list** in Spotlight (opened with `CTRL + K`)
|
||||
@@ -51,10 +51,10 @@ You can access it from:
|
||||
### Availability indicators
|
||||
|
||||
Search results display colored status badges showing the media's current state in your integration:
|
||||
- **Available** — fully available in your media library
|
||||
- **Partial** — some content (e.g. seasons) is available
|
||||
- **Processing** — currently being downloaded or processed
|
||||
- **Requested** — requested but not yet available
|
||||
- **Available** - fully available in your media library
|
||||
- **Partial** - some content (e.g. seasons) is available
|
||||
- **Processing** - currently being downloaded or processed
|
||||
- **Requested** - requested but not yet available
|
||||
|
||||
### Requesting TV show seasons
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"label": "Tools",
|
||||
"collapsed": true,
|
||||
"position": 10
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
title: SQLite Backup & Restore
|
||||
tags:
|
||||
- Management
|
||||
- Backup
|
||||
- Restore
|
||||
- SQLite
|
||||
---
|
||||
|
||||
The SQLite Backup & Restore feature allows you to export and import your entire Homarr database as a portable ZIP file. This is useful for creating backups, migrating between instances, or recovering from failures.
|
||||
|
||||
:::info SQLite only
|
||||
This feature is only available when using the SQLite database driver (`DB_DRIVER=better-sqlite3`). MySQL and PostgreSQL users should use their respective database backup tools.
|
||||
:::
|
||||
|
||||
## Accessing Backup & Restore
|
||||
|
||||
Navigate to **Management → Tools → Backup** from the sidebar. This page is only visible when running with SQLite.
|
||||
|
||||
## Exporting a Backup
|
||||
|
||||
Click the **Export** button to download a ZIP archive containing:
|
||||
|
||||
- **`db.sqlite`** — A consistent snapshot of your database (created via `VACUUM INTO`)
|
||||
- **`metadata.json`** — Contains:
|
||||
- `homarrVersion` — The version of Homarr that created the backup
|
||||
- `exportedAt` — Timestamp of the export
|
||||
- `dbDialect` — The database dialect (`sqlite`)
|
||||
- `encryptionKey` — Your `SECRET_ENCRYPTION_KEY` (needed to decrypt integration secrets)
|
||||
|
||||
:::caution
|
||||
The exported ZIP contains your encryption key and database. Store it securely and do not share it publicly.
|
||||
:::
|
||||
|
||||
A confirmation dialog will ask you to type **"I understand"** before the export proceeds.
|
||||
|
||||
## Importing a Backup
|
||||
|
||||
1. Click the **Import** button and upload a previously exported ZIP file
|
||||
2. Homarr analyzes the backup **entirely in your browser** using WebAssembly (sql.js) — no data is sent to the server during preview
|
||||
3. The preview shows:
|
||||
- **Entity counts** — How many boards, apps, users, integrations, etc. are in the backup
|
||||
- **Board names** — A list of all boards contained in the backup
|
||||
- **Migration status** — If the backup is from an older version, Homarr shows each pending database migration as animated timeline steps
|
||||
4. Review the preview and type **"I understand"** to confirm
|
||||
5. Homarr applies any pending migrations, re-encrypts integration secrets if the `SECRET_ENCRYPTION_KEY` differs between instances, and replaces the live database
|
||||
6. The server automatically restarts to pick up the new database
|
||||
|
||||
:::tip
|
||||
The import process is **backward-compatible** with older backups that used a separate `encryption-key.txt` file instead of embedding the key in `metadata.json`.
|
||||
:::
|
||||
|
||||
## Onboarding Integration
|
||||
|
||||
When setting up a fresh Homarr instance with SQLite, the onboarding wizard includes a **"Restore from SQLite backup"** option. This lets you upload a backup ZIP to bootstrap your new instance with data from a previous installation.
|
||||
|
||||
## Docker Restart Behavior
|
||||
|
||||
After a successful import, only the Next.js process restarts — nginx and Redis remain running. The `run.sh` script automatically restarts Next.js in a loop, so the application comes back online within seconds. Running `docker stop` still works gracefully.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Back up regularly** — Create exports before major updates or configuration changes
|
||||
- **Test restores** — Periodically verify your backups by importing them on a test instance
|
||||
- **Secure storage** — Keep backups in an encrypted or access-controlled location since they contain your encryption key
|
||||
- **Check versions** — Backups from newer versions cannot be imported into older Homarr instances; always import into the same or newer version
|
||||
@@ -8,12 +8,19 @@ export const beszelSystemStatsWidget: WidgetDefinition = {
|
||||
path: "../../widgets/beszel-system-stats",
|
||||
configuration: {
|
||||
items: [
|
||||
{
|
||||
name: "System",
|
||||
description: "The Beszel system to display stats for. Searchable select populated from the integration.",
|
||||
values: { type: "string" },
|
||||
defaultValue: "First available system",
|
||||
},
|
||||
{
|
||||
name: "Time period",
|
||||
description: "The time range for chart data",
|
||||
description:
|
||||
"The time range for chart data. '1 Minute' enables live streaming via PocketBase SSE (requires Beszel agent >= 0.13.0)",
|
||||
values: {
|
||||
type: "select",
|
||||
options: ["1 Hour", "12 Hours", "24 Hours", "1 Week", "30 Days"],
|
||||
options: ["1 Minute", "1 Hour", "12 Hours", "24 Hours", "1 Week", "30 Days"],
|
||||
},
|
||||
defaultValue: "1 Hour",
|
||||
},
|
||||
|
||||
@@ -63,7 +63,9 @@ import withoutIntegrations from './img/without-integrations.png';
|
||||
|
||||
### Interactions
|
||||
|
||||
- View calendar events by clicking on the marked dates.
|
||||
- Hover over a marked date to preview its events in a popover.
|
||||
- Click on a marked date to pin the popover open (it will stay visible even after moving the mouse away).
|
||||
- Click the date again or click outside to close the pinned popover.
|
||||
|
||||
### Adding the widget
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { WidgetDefinition } from "@site/src/types";
|
||||
import { IconDeviceGamepad } from "@tabler/icons-react";
|
||||
import { integrations } from "../dns-hole-summary";
|
||||
|
||||
export const dnsHoleControlsWidget: WidgetDefinition = {
|
||||
icon: IconDeviceGamepad,
|
||||
name: "DNS Hole Controls",
|
||||
description: "Control PiHole or AdGuard from your dashboard",
|
||||
description: `Control the blocking feature of your ${integrations} from your dashboard`,
|
||||
path: "../../widgets/dns-hole-controls",
|
||||
configuration: {
|
||||
items: [
|
||||
{
|
||||
name: "Show Toggle All Buttons",
|
||||
description: "Show or not the 2 buttons that may be redundant when having only one DNS-hole.",
|
||||
description: "Show or not the 2 buttons that may be redundant when having only one DNS Hole.",
|
||||
values: { type: "boolean" },
|
||||
defaultValue: "yes",
|
||||
},
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { WidgetDefinition } from "@site/src/types";
|
||||
import { IconAd } from "@tabler/icons-react";
|
||||
|
||||
export const integrations = "Pi-hole, AdGuard Home or Technitium DNS";
|
||||
|
||||
export const dnsHoleSummaryWidget: WidgetDefinition = {
|
||||
icon: IconAd,
|
||||
name: "DNS Hole Summary",
|
||||
description: "Displays the summary of your DNS Hole",
|
||||
description: `Displays the summary of your ${integrations} blocks and queries statistics`,
|
||||
path: "../../widgets/dns-hole-summary",
|
||||
configuration: {
|
||||
items: [
|
||||
@@ -19,7 +21,7 @@ export const dnsHoleSummaryWidget: WidgetDefinition = {
|
||||
},
|
||||
{
|
||||
name: "Use Pi-Hole colors",
|
||||
description: "Color the tiles using the background color, that is used by default in PiHole.",
|
||||
description: `Color the tiles using the background color, that is used by default in ${integrations}.`,
|
||||
values: { type: "boolean" },
|
||||
defaultValue: "yes",
|
||||
},
|
||||
|
||||
@@ -41,10 +41,10 @@ import dockerTable from './img/table.png';
|
||||
### Interactions
|
||||
|
||||
Each container row has an action menu (accessible via the three-dot icon) with the following options:
|
||||
- **View logs** — opens a dedicated terminal page streaming the container's logs
|
||||
- **Start** / **Stop** / **Restart** — manage the container's state
|
||||
- **Remove** — remove the container
|
||||
- **Add to Homarr** — create a bookmark app from the container
|
||||
- **View logs** - opens a dedicated terminal page streaming the container's logs
|
||||
- **Start** / **Stop** / **Restart** - manage the container's state
|
||||
- **Remove** - remove the container
|
||||
- **Add to Homarr** - create a bookmark app from the container
|
||||
|
||||
### Adding the widget
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ export const immichAlbumCarouselWidget: WidgetDefinition = {
|
||||
configuration: {
|
||||
items: [
|
||||
{
|
||||
name: "Album ID",
|
||||
description:
|
||||
"This is a unique ID which refers to your Immich album. You can find this id by opening an album and copying the unique string from the URL, for example 'a21aca09-9f05-47dc-b1a1-7b53c547226e'",
|
||||
name: "Album",
|
||||
description: "Select the Immich album to show in the slideshow.",
|
||||
values: { type: "string" },
|
||||
defaultValue: "",
|
||||
},
|
||||
@@ -25,7 +24,13 @@ export const immichAlbumCarouselWidget: WidgetDefinition = {
|
||||
name: "Show photo info",
|
||||
description: "Display date of the picture and the current index at the bottom of the slideshow",
|
||||
values: { type: "boolean" },
|
||||
defaultValue: "yes",
|
||||
defaultValue: "no",
|
||||
},
|
||||
{
|
||||
name: "Randomize photos",
|
||||
description: "Show photos in a random order instead of always starting with the same album order.",
|
||||
values: { type: "boolean" },
|
||||
defaultValue: "no",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -13,7 +13,8 @@ import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { notificationsWidget } from '.';
|
||||
import { ntfyIntegration } from '@site/docs/integrations/ntfy';
|
||||
import { nextcloudIntegration } from '@site/docs/integrations/nextcloud'
|
||||
import { nextcloudIntegration } from '@site/docs/integrations/nextcloud';
|
||||
import { gotifyIntegration } from '@site/docs/integrations/gotify';
|
||||
|
||||
<WidgetHeader
|
||||
widget={notificationsWidget}
|
||||
@@ -33,7 +34,8 @@ import notificationsScreenshot from './img/notifications.png';
|
||||
<WidgetIntegrations
|
||||
items={[
|
||||
{ integration: ntfyIntegration },
|
||||
{ integration: nextcloudIntegration }
|
||||
{ integration: nextcloudIntegration },
|
||||
{ integration: gotifyIntegration }
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,69 +1,44 @@
|
||||
---
|
||||
title: 'Releases'
|
||||
description: 'Displays a list of the current version of the given repositories with the given version regex.'
|
||||
title: "Releases"
|
||||
description: "Displays a list of the current version of the given repositories with the given version regex."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { AddingWidget } from '@site/src/components/widgets/adding';
|
||||
import { WidgetConfig } from '@site/src/components/widgets/configuration';
|
||||
import { WidgetHeader } from '@site/src/components/widgets/header';
|
||||
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
|
||||
import { releasesWidget } from '.';
|
||||
import { githubIntegration } from '@site/docs/integrations/github';
|
||||
import { gitlabIntegration } from '@site/docs/integrations/gitlab';
|
||||
import { dockerHubIntegration } from '@site/docs/integrations/docker-hub';
|
||||
import { npmIntegration } from '@site/docs/integrations/npm';
|
||||
import { codebergIntegration } from '@site/docs/integrations/codeberg';
|
||||
import { AddingWidget } from "@site/src/components/widgets/adding";
|
||||
import { WidgetConfig } from "@site/src/components/widgets/configuration";
|
||||
import { WidgetHeader } from "@site/src/components/widgets/header";
|
||||
import { releasesWidget } from ".";
|
||||
|
||||
<WidgetHeader
|
||||
widget={releasesWidget}
|
||||
categories={['Releases', 'Software']}
|
||||
/>
|
||||
<WidgetHeader widget={releasesWidget} categories={["Releases", "Software"]} />
|
||||
|
||||
The releases widget enables you to track a list of repositories and see their respective latest release.
|
||||
It also supports importing releases from your local docker containers.
|
||||
The releases widget enables you to track a list of repositories and see their respective latest release.
|
||||
It also supports importing releases from your local docker containers.
|
||||
|
||||
### Screenshots
|
||||
|
||||
import collapsibleCard from './img/collapsible.png';
|
||||
import fullCard from './img/full.png';
|
||||
import noDetailsCard from './img/no-details.png';
|
||||
import { quayIntegration } from '@site/docs/integrations/quay';
|
||||
import { gitHubContainerRegistryIntegration } from '@site/docs/integrations/github-containerregistry';
|
||||
import { linuxServerIoIntegration } from '@site/docs/integrations/linux-server-io';
|
||||
import collapsibleCard from "./img/collapsible.png";
|
||||
import fullCard from "./img/full.png";
|
||||
import noDetailsCard from "./img/no-details.png";
|
||||
|
||||
<div className='flex gap-1'>
|
||||
<div className="flex gap-1">
|
||||
<img src={fullCard} alt="releases full" width={128 * 3} />
|
||||
<img src={collapsibleCard} alt="releases collapsible" width={128 * 3} />
|
||||
<img src={noDetailsCard} alt="releases no details" width={128 * 3} />
|
||||
</div>
|
||||
|
||||
### Supported Integrations
|
||||
### Supported Providers
|
||||
|
||||
<WidgetIntegrations items={[{
|
||||
integration: githubIntegration,
|
||||
}, {
|
||||
integration: gitlabIntegration,
|
||||
}, {
|
||||
integration: dockerHubIntegration,
|
||||
}, {
|
||||
integration: npmIntegration,
|
||||
}, {
|
||||
integration: codebergIntegration,
|
||||
}, {
|
||||
integration: gitHubContainerRegistryIntegration,
|
||||
note: "Use if release is only available in ghcr.io"
|
||||
}, {
|
||||
integration: quayIntegration
|
||||
}, {
|
||||
integration: linuxServerIoIntegration
|
||||
}]} />
|
||||
GitHub, Docker Hub, GitLab, NPM, Codeberg, GitHub Container Registry, Quay, and LinuxServer.io are configured per repository in the widget.
|
||||
|
||||
For GitHub Container Registry, use `owner/package` identifiers such as `homarr-labs/homarr`.
|
||||
Full image references such as `ghcr.io/homarr-labs/homarr:latest` are also accepted.
|
||||
|
||||
### Adding the widget
|
||||
|
||||
<AddingWidget />
|
||||
|
||||
### Configuration
|
||||
|
||||
<WidgetConfig configuration={releasesWidget.configuration} />
|
||||
|
||||
:::note
|
||||
@@ -74,4 +49,3 @@ Setting the Prefix to `v`, precision to `2` and the Suffix to `-alpha`, will mat
|
||||
Setting the Prefix to `v`, precision to `3`, will match `v5.2.0` but not `v1.0.2-alpha` or `Beta v5.2.1`.
|
||||
|
||||
:::
|
||||
|
||||
@@ -54,16 +54,23 @@ export const releasesWidget: WidgetDefinition = {
|
||||
items: [
|
||||
{
|
||||
name: "Provider",
|
||||
description: "Integration to fetch the repository from.",
|
||||
values: "Select from the available integrations.",
|
||||
description: "Provider to fetch the repository or package releases from.",
|
||||
values: "Select from the available providers.",
|
||||
defaultValue: "-",
|
||||
},
|
||||
{
|
||||
name: "Identifier",
|
||||
description: 'The identifier of the repository, e.g. "homarr-labs/homarr".',
|
||||
values: "Any valid repository identifier.",
|
||||
description: 'The identifier of the repository or package, e.g. "homarr-labs/homarr".',
|
||||
values:
|
||||
'Any valid provider identifier. GHCR also accepts full image refs like "ghcr.io/owner/package:tag".',
|
||||
defaultValue: "-",
|
||||
},
|
||||
{
|
||||
name: "Provider URL",
|
||||
description: "Base URL for the provider.",
|
||||
values: "Any valid URL.",
|
||||
defaultValue: "Provider default",
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
description: "Display name of the repository.",
|
||||
|
||||
@@ -1,44 +1,40 @@
|
||||
---
|
||||
title: 'Timetable'
|
||||
description: 'Displays departure times for a station.'
|
||||
title: "Timetable"
|
||||
description: "Displays departure times for a station."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { AddingWidget } from '@site/src/components/widgets/adding';
|
||||
import { WidgetConfig } from '@site/src/components/widgets/configuration';
|
||||
import { WidgetHeader } from '@site/src/components/widgets/header';
|
||||
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
|
||||
import { timetableWidget } from '.';
|
||||
import { searchChIntegration } from '@site/docs/integrations/search-ch';
|
||||
import { AddingWidget } from "@site/src/components/widgets/adding";
|
||||
import { WidgetConfig } from "@site/src/components/widgets/configuration";
|
||||
import { WidgetHeader } from "@site/src/components/widgets/header";
|
||||
import { timetableWidget } from ".";
|
||||
|
||||
|
||||
<WidgetHeader
|
||||
widget={timetableWidget}
|
||||
categories={['Timetable']}
|
||||
/>
|
||||
<WidgetHeader widget={timetableWidget} categories={["Timetable"]} />
|
||||
|
||||
The Timetable widget provides real-time departure times for a selected station.
|
||||
|
||||
### Screenshots
|
||||
|
||||
import trainDepartures from './img/train-departures.png';
|
||||
import streetDepartures from './img/street-departures.png';
|
||||
import trainDepartures from "./img/train-departures.png";
|
||||
import streetDepartures from "./img/street-departures.png";
|
||||
|
||||
<div className="flex gap-2">
|
||||
<img src={trainDepartures} alt="timetable-widget-train-departures" width={128 * 2} />
|
||||
<img src={streetDepartures} alt="timetable-widget-bus-and-tram-departures" width={128 * 2} />
|
||||
<img
|
||||
src={trainDepartures}
|
||||
alt="timetable-widget-train-departures"
|
||||
width={128 * 2}
|
||||
/>
|
||||
<img
|
||||
src={streetDepartures}
|
||||
alt="timetable-widget-bus-and-tram-departures"
|
||||
width={128 * 2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
### Adding the widget
|
||||
|
||||
<AddingWidget />
|
||||
|
||||
### Supported Integrations
|
||||
|
||||
<WidgetIntegrations items={[{
|
||||
integration: searchChIntegration,
|
||||
note: 'Switzerland only'
|
||||
}]} />
|
||||
|
||||
### Configuration
|
||||
|
||||
<WidgetConfig configuration={timetableWidget.configuration} />
|
||||
@@ -8,6 +8,12 @@ export const timetableWidget: WidgetDefinition = {
|
||||
path: "../../widgets/timetable",
|
||||
configuration: {
|
||||
items: [
|
||||
{
|
||||
name: "Search.ch URL",
|
||||
description: "Base URL for the Search.ch timetable API.",
|
||||
values: "Any valid URL.",
|
||||
defaultValue: "https://search.ch",
|
||||
},
|
||||
{
|
||||
name: "Station",
|
||||
description: "Select the station",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { umamiIntegration } from '@site/docs/integrations/umami';
|
||||
categories={['Analytics']}
|
||||
/>
|
||||
|
||||
The Umami Analytics widget displays key visitor metrics from a website tracked by your [Umami](https://umami.is) instance — pageviews, unique visitors, visits, bounce rate, avg. visit duration, and real-time active visitor count. The widget body can be switched between a visitor/event chart, a multi-event chart, and ranked top pages or referrers lists.
|
||||
The Umami Analytics widget displays key visitor metrics from a website tracked by your [Umami](https://umami.is) instance - pageviews, unique visitors, visits, bounce rate, avg. visit duration, and real-time active visitor count. The widget body can be switched between a visitor/event chart, a multi-event chart, and ranked top pages or referrers lists.
|
||||
|
||||
### Supported Integrations
|
||||
|
||||
@@ -37,7 +37,7 @@ The Umami Analytics widget displays key visitor metrics from a website tracked b
|
||||
| View | Description |
|
||||
|---|---|
|
||||
| **Chart** | Bar chart or sparkline of unique visitors over the selected time frame. Optionally overlay a single named event as a second series. |
|
||||
| **Events chart** | Multi-select one or more named events and chart each as its own colored series — no visitor baseline. Supports bar and sparkline. |
|
||||
| **Events chart** | Multi-select one or more named events and chart each as its own colored series - no visitor baseline. Supports bar and sparkline. |
|
||||
| **Top pages** | Ranked list of URLs by pageview count for the selected time frame. |
|
||||
| **Top referrers** | Ranked list of referrer sources by session count. Direct traffic (no referrer) is labeled "Direct". |
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: 'VPN'
|
||||
description: 'Monitor the status of your VPN integrations.'
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { WidgetHeader } from '@site/src/components/widgets/header';
|
||||
import { AddingWidget } from '@site/src/components/widgets/adding';
|
||||
import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
|
||||
import { vpnWidget } from '.';
|
||||
import { gluetunIntegration } from '@site/docs/integrations/gluetun';
|
||||
|
||||
<WidgetHeader
|
||||
widget={vpnWidget}
|
||||
categories={['Network', 'Security']}
|
||||
/>
|
||||
|
||||
The VPN widget provides a compact overview of each configured VPN integration. It shows whether the tunnel and DNS
|
||||
services are up, the currently exposed public IP address, and the VPN provider and protocol being used.
|
||||
|
||||
When multiple VPN integrations are attached, the widget renders one card per integration so you can compare their
|
||||
current status at a glance.
|
||||
|
||||
### Screenshots
|
||||
|
||||
import singleIntegrationImage from './img/single.png';
|
||||
|
||||
<div>
|
||||
<img src={singleIntegrationImage} alt="VPN widget showing a single integration" width={128 * 3} />
|
||||
</div>
|
||||
|
||||
### Supported Integrations
|
||||
|
||||
<WidgetIntegrations items={[{
|
||||
integration: gluetunIntegration,
|
||||
}]} />
|
||||
|
||||
### Adding the widget
|
||||
|
||||
<AddingWidget />
|
||||
@@ -0,0 +1,9 @@
|
||||
import { WidgetDefinition } from "@site/src/types";
|
||||
import { IconShieldLock } from "@tabler/icons-react";
|
||||
|
||||
export const vpnWidget: WidgetDefinition = {
|
||||
icon: IconShieldLock,
|
||||
name: "VPN",
|
||||
description: "Monitor the connection status, public IP, and provider details of your VPN integrations.",
|
||||
path: "../../widgets/vpn",
|
||||
};
|
||||
@@ -8,6 +8,7 @@ const config: Config = {
|
||||
tagline: "A simple yet powerful dashboard for your server.",
|
||||
url: "https://homarr.dev",
|
||||
baseUrl: "/",
|
||||
trailingSlash: true,
|
||||
favicon: "img/logo.png",
|
||||
organizationName: "homarr-labs",
|
||||
projectName: "homarr",
|
||||
@@ -16,7 +17,6 @@ const config: Config = {
|
||||
locales: ["en"],
|
||||
},
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "throw",
|
||||
onBrokenAnchors: "throw",
|
||||
onDuplicateRoutes: "throw",
|
||||
|
||||
@@ -28,11 +28,17 @@ const config: Config = {
|
||||
fasterByDefault: true,
|
||||
mdx1CompatDisabledByDefault: false,
|
||||
},
|
||||
faster: {
|
||||
swcHtmlMinimizer: false,
|
||||
},
|
||||
},
|
||||
|
||||
markdown: {
|
||||
mermaid: true,
|
||||
format: "detect",
|
||||
hooks: {
|
||||
onBrokenMarkdownLinks: "throw",
|
||||
},
|
||||
},
|
||||
|
||||
themes: ["@docusaurus/theme-mermaid"],
|
||||
@@ -56,7 +62,7 @@ const config: Config = {
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
editUrl: ({ docPath }) => `https://github.com/homarr-labs/homarr/edit/dev/apps/docs/docs/${docPath}`,
|
||||
remarkPlugins: [a11yEmoji],
|
||||
exclude: ["**/custom-widget.mdx"],
|
||||
exclude: [],
|
||||
showLastUpdateAuthor: false,
|
||||
showLastUpdateTime: false,
|
||||
},
|
||||
@@ -285,13 +291,11 @@ const config: Config = {
|
||||
};
|
||||
},
|
||||
"@signalwire/docusaurus-plugin-llms-txt",
|
||||
async function tailwindCssPlugin(context, options) {
|
||||
async function tailwindCssPlugin() {
|
||||
return {
|
||||
name: "docusaurus-tailwindcss",
|
||||
configurePostCss(postcssOptions) {
|
||||
// Appends TailwindCSS and AutoPrefixer.
|
||||
postcssOptions.plugins.push(require("tailwindcss"));
|
||||
postcssOptions.plugins.push(require("autoprefixer"));
|
||||
postcssOptions.plugins.push(require("@tailwindcss/postcss"));
|
||||
return postcssOptions;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,25 +32,25 @@
|
||||
"@scalar/api-reference-react": "catalog:",
|
||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"@tailwindcss/postcss": "^4.3.1",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"docusaurus-plugin-image-zoom": "^3.0.1",
|
||||
"leader-line-new": "^1.1.9",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss": "^8.5.15",
|
||||
"posthog-docusaurus": "^2.0.5",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"random-word-slugs": "^0.1.7",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"swiper": "^12.2.0",
|
||||
"tailwindcss": "^3.4.19"
|
||||
"tailwindcss": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
||||
"@docusaurus/tsconfig": "^3.10.1",
|
||||
"@docusaurus/types": "^3.10.1",
|
||||
"@fec/remark-a11y-emoji": "^3.1.0",
|
||||
"@fec/remark-a11y-emoji": "^4.0.2",
|
||||
"@tsconfig/docusaurus": "^2.0.9",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
|
||||
@@ -32,7 +32,7 @@ export const IntegrationCapabilites = ({ items }: IntegrationCapabilitesProps) =
|
||||
className="flex gap-6 rounded-xl border border-solid dark:border-[#333] border-[#e5e7eb] p-4 shadow-sm w-full items-center justify-between"
|
||||
>
|
||||
<div className="flex gap-6 items-center">
|
||||
<div className="w-10 h-10 flex justify-center items-center bg-red-500 bg-opacity-10 rounded-md">
|
||||
<div className="w-10 h-10 flex justify-center items-center bg-red-500/10 rounded-md">
|
||||
<capability.icon size={24} stroke={1.5} className="stroke-red-500" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import type { AnyApiReferenceConfiguration } from "@scalar/types/api-reference";
|
||||
import { ApiReferenceReact } from "@scalar/api-reference-react";
|
||||
import { useColorMode } from "@docusaurus/theme-common";
|
||||
|
||||
import "@scalar/api-reference-react/style.css";
|
||||
|
||||
export function ThemedApiReference({ configuration }: { configuration: AnyApiReferenceConfiguration }) {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<ApiReferenceReact
|
||||
configuration={{
|
||||
...configuration,
|
||||
forceDarkModeState: colorMode === "dark" ? "dark" : "light",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const WidgetHeader = (props: WidgetHeaderProps) => {
|
||||
title={props.widget.name}
|
||||
description={props.widget.description}
|
||||
icon={
|
||||
<div className="w-12 h-12 flex justify-center items-center bg-red-500 bg-opacity-10 rounded-md">
|
||||
<div className="w-12 h-12 flex justify-center items-center bg-red-500/10 rounded-md">
|
||||
<props.widget.icon size={32} stroke={1.5} className="stroke-red-500" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss/theme" layer(theme);
|
||||
@import "tailwindcss/utilities" layer(utilities);
|
||||
|
||||
@import url(https://api.fontshare.com/v2/css?f[]=work-sans@2,1&display=swap);
|
||||
|
||||
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
||||
|
||||
@theme {
|
||||
--spacing-128: 32rem;
|
||||
--breakpoint-3xl: 1697px;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ifm-color-primary: #fa5252;
|
||||
--ifm-color-primary-dark: #f93232;
|
||||
@@ -87,7 +93,7 @@
|
||||
}
|
||||
|
||||
.menu__list {
|
||||
transition: all 0.s ease-in-out !important;
|
||||
transition: all 0.5s ease-in-out !important;
|
||||
}
|
||||
|
||||
.announcementBar_node_modules-\@docusaurus-theme-classic-lib-theme-AnnouncementBar-styles-module {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
import Layout from "@theme/Layout";
|
||||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||
|
||||
export default function ApiReferencePage() {
|
||||
return (
|
||||
<Layout title="API Reference" description="Homarr OpenAPI Reference">
|
||||
<BrowserOnly fallback={<div style={{ height: "100vh" }} />}>
|
||||
{() => {
|
||||
const { ThemedApiReference } = require("@site/src/components/themed-api-reference");
|
||||
return (
|
||||
<ThemedApiReference
|
||||
configuration={{
|
||||
url: "/api/open-api-schema.json",
|
||||
theme: "kepler",
|
||||
hideModels: false,
|
||||
hideDownloadButton: false,
|
||||
mcp: { disabled: true },
|
||||
customCss: `
|
||||
.scalar-sidebar-footer a[href="https://www.scalar.com"] { display: none !important; }
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</BrowserOnly>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
spacing: {
|
||||
128: "32rem",
|
||||
},
|
||||
screens: {
|
||||
"3xl": "1697px",
|
||||
},
|
||||
},
|
||||
},
|
||||
darkMode: ["class", '[data-theme="dark"]'],
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
};
|
||||
+28
-34
@@ -1,35 +1,29 @@
|
||||
{
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.jsx",
|
||||
"**/*.js",
|
||||
"**/*.tsx",
|
||||
"**/*.md",
|
||||
"**/*.mdx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": ".",
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.jsx",
|
||||
"**/*.js",
|
||||
"**/*.tsx",
|
||||
"**/*.md",
|
||||
"**/*.mdx",
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
||||
@@ -89,6 +89,7 @@
|
||||
"mcp-handler": "catalog:",
|
||||
"next": "catalog:",
|
||||
"postcss-preset-mantine": "catalog:",
|
||||
"posthog-js": "catalog:",
|
||||
"prismjs": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
|
||||
@@ -16,11 +16,7 @@ export const InitSqliteRestore = () => {
|
||||
if (expanded) {
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
onClick={() => setExpanded(false)}
|
||||
>
|
||||
<Button variant="subtle" size="xs" onClick={() => setExpanded(false)}>
|
||||
{t("backToOptions")}
|
||||
</Button>
|
||||
<Text size="sm" c="dimmed">
|
||||
|
||||
@@ -139,11 +139,11 @@ export default async function Layout(props: {
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<Analytics />
|
||||
<SearchEngineOptimization />
|
||||
<CrowdinLiveTranslation locale={locale} />
|
||||
</head>
|
||||
<body className={["font-sans", fontSans.variable].join(" ")}>
|
||||
<Analytics enabled={serverSettings.analytics.enableGeneral} />
|
||||
<StackedProvider>
|
||||
<Notifications pauseResetOnHover="notification" />
|
||||
<ServiceWorkerRegistration />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { headers } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
Accordion,
|
||||
@@ -41,11 +40,10 @@ import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
|
||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import type { PackageJsonDependencies } from "~/versions/package-reader";
|
||||
import { getPackageVersion } from "~/versions/package-reader";
|
||||
import type githubContributorsJson from "../../../../../../../static-data/contributors.json";
|
||||
import type openCollectiveContributorsJson from "../../../../../../../static-data/opencollective-contributors.json";
|
||||
import type crowdinContributorsJson from "../../../../../../../static-data/translators.json";
|
||||
import { getDependenciesAsync, getPackageVersion } from "~/versions/package-reader";
|
||||
import githubContributors from "@static-data/contributors.json";
|
||||
import openCollectiveContributors from "@static-data/opencollective-contributors.json";
|
||||
import crowdinContributors from "@static-data/translators.json";
|
||||
import classes from "./about.module.css";
|
||||
|
||||
export async function generateMetadata() {
|
||||
@@ -56,32 +54,10 @@ export async function generateMetadata() {
|
||||
};
|
||||
}
|
||||
|
||||
const getHostAsync = async () => {
|
||||
if (process.env.HOSTNAME) {
|
||||
return `${process.env.HOSTNAME}:3000`;
|
||||
}
|
||||
|
||||
return (await headers()).get("host");
|
||||
};
|
||||
|
||||
export default async function AboutPage() {
|
||||
const baseServerUrl = `http://${await getHostAsync()}`;
|
||||
const t = await getScopedI18n("management.page.about");
|
||||
const version = getPackageVersion();
|
||||
const dependencies = (await fetch(`${baseServerUrl}/api/about/dependencies`).then((res) =>
|
||||
res.json(),
|
||||
)) as PackageJsonDependencies;
|
||||
const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>
|
||||
res.json(),
|
||||
)) as typeof githubContributorsJson;
|
||||
|
||||
const crowdinContributors = (await fetch(`${baseServerUrl}/api/about/contributors/crowdin`).then((res) =>
|
||||
res.json(),
|
||||
)) as typeof crowdinContributorsJson;
|
||||
|
||||
const openCollectiveContributors = (await fetch(`${baseServerUrl}/api/about/contributors/opencollective`).then(
|
||||
(res) => res.json(),
|
||||
)) as typeof openCollectiveContributorsJson;
|
||||
const dependencies = await getDependenciesAsync();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -185,7 +161,7 @@ export default async function AboutPage() {
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing="md">
|
||||
{Object.entries(dependencies)
|
||||
.filter(([, value]) => !value.includes("workspace:"))
|
||||
.sort(([key1], [key2]) => key1.localeCompare(key2))
|
||||
.toSorted(([key1], [key2]) => key1.localeCompare(key2))
|
||||
.map(([name, version]) => (
|
||||
<UnstyledButton
|
||||
key={name}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Alert, Anchor, Text } from "@mantine/core";
|
||||
import { IconFlask } from "@tabler/icons-react";
|
||||
|
||||
const FEEDBACK_URL = "https://github.com/homarr-labs/homarr/discussions/5916";
|
||||
const FEEDBACK_URL = "https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets";
|
||||
|
||||
export function CustomWidgetBetaBanner() {
|
||||
return (
|
||||
|
||||
@@ -150,7 +150,13 @@ const requiredFieldValidators: Record<string, (data: Record<string, unknown>, ct
|
||||
});
|
||||
},
|
||||
statusIndicator: (data, ctx) => {
|
||||
(data.statusItems as Array<{ label: string; jsonPath: string; goodValues: string }>)?.forEach((item, i) => {
|
||||
(
|
||||
data.statusItems as Array<{
|
||||
label: string;
|
||||
jsonPath: string;
|
||||
goodValues: string;
|
||||
}>
|
||||
)?.forEach((item, i) => {
|
||||
if (!item.label)
|
||||
ctx.addIssue({
|
||||
code: "too_small",
|
||||
@@ -260,15 +266,34 @@ const formSchema = z
|
||||
columns: z.array(z.object({ header: z.string(), jsonPath: z.string() })),
|
||||
striped: z.boolean(),
|
||||
compact: z.boolean(),
|
||||
statGridItems: z.array(z.object({ label: z.string(), jsonPath: z.string(), unit: z.string(), color: z.string() })),
|
||||
statGridItems: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
jsonPath: z.string(),
|
||||
unit: z.string(),
|
||||
color: z.string(),
|
||||
}),
|
||||
),
|
||||
statGridColumns: z.number(),
|
||||
cardStyle: z.string(),
|
||||
progressBars: z.array(
|
||||
z.object({ label: z.string(), valuePath: z.string(), maxPath: z.string(), unit: z.string(), color: z.string() }),
|
||||
z.object({
|
||||
label: z.string(),
|
||||
valuePath: z.string(),
|
||||
maxPath: z.string(),
|
||||
unit: z.string(),
|
||||
color: z.string(),
|
||||
}),
|
||||
),
|
||||
showPercentage: z.boolean(),
|
||||
barSize: z.string(),
|
||||
statusItems: z.array(z.object({ label: z.string(), jsonPath: z.string(), goodValues: z.string() })),
|
||||
statusItems: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
jsonPath: z.string(),
|
||||
goodValues: z.string(),
|
||||
}),
|
||||
),
|
||||
statusLayout: z.string(),
|
||||
dotSize: z.string(),
|
||||
countGridItems: z.array(z.object({ label: z.string(), jsonPath: z.string(), unit: z.string() })),
|
||||
@@ -281,7 +306,13 @@ const formSchema = z
|
||||
confirmText: z.string(),
|
||||
successMessage: z.string(),
|
||||
template: z.string(),
|
||||
secrets: z.array(z.object({ kind: z.string(), value: z.string(), hasValue: z.boolean().optional() })),
|
||||
secrets: z.array(
|
||||
z.object({
|
||||
kind: z.string(),
|
||||
value: z.string(),
|
||||
hasValue: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
const validator = requiredFieldValidators[data.displayType];
|
||||
@@ -362,12 +393,31 @@ const displayConfigBuilders: Record<string, (values: z.infer<typeof formSchema>)
|
||||
valueSize: v.valueSize,
|
||||
labelPosition: v.labelPosition,
|
||||
}),
|
||||
keyValue: (v) => ({ type: "keyValue", mappings: v.mappings, layout: v.kvLayout, columns: v.kvColumns }),
|
||||
table: (v) => ({ type: "table", tablePath: v.tablePath, columns: v.columns, striped: v.striped, compact: v.compact }),
|
||||
statGrid: (v) => ({ type: "statGrid", items: v.statGridItems, columns: v.statGridColumns, cardStyle: v.cardStyle }),
|
||||
keyValue: (v) => ({
|
||||
type: "keyValue",
|
||||
mappings: v.mappings,
|
||||
layout: v.kvLayout,
|
||||
columns: v.kvColumns,
|
||||
}),
|
||||
table: (v) => ({
|
||||
type: "table",
|
||||
tablePath: v.tablePath,
|
||||
columns: v.columns,
|
||||
striped: v.striped,
|
||||
compact: v.compact,
|
||||
}),
|
||||
statGrid: (v) => ({
|
||||
type: "statGrid",
|
||||
items: v.statGridItems,
|
||||
columns: v.statGridColumns,
|
||||
cardStyle: v.cardStyle,
|
||||
}),
|
||||
progressBars: (v) => ({
|
||||
type: "progressBars",
|
||||
bars: v.progressBars.map((b) => ({ ...b, maxPath: b.maxPath || undefined })),
|
||||
bars: v.progressBars.map((b) => ({
|
||||
...b,
|
||||
maxPath: b.maxPath || undefined,
|
||||
})),
|
||||
showPercentage: v.showPercentage,
|
||||
barSize: v.barSize,
|
||||
}),
|
||||
@@ -389,7 +439,11 @@ const displayConfigBuilders: Record<string, (values: z.infer<typeof formSchema>)
|
||||
columns: v.countGridColumns,
|
||||
valueSize: v.countValueSize,
|
||||
}),
|
||||
raw: (v) => ({ type: "raw", jsonPath: v.rawJsonPath, maxHeight: v.rawMaxHeight }),
|
||||
raw: (v) => ({
|
||||
type: "raw",
|
||||
jsonPath: v.rawJsonPath,
|
||||
maxHeight: v.rawMaxHeight,
|
||||
}),
|
||||
actionButton: (v) => ({
|
||||
type: "actionButton",
|
||||
buttonLabel: v.buttonLabel,
|
||||
@@ -415,7 +469,10 @@ const serverToFormFieldMap: Record<string, Record<string, string>> = {
|
||||
|
||||
function extractServerErrors(err: unknown, displayType: string): Record<string, string> {
|
||||
const errors: Record<string, string> = {};
|
||||
const trpcErr = err as { data?: { zodError?: { fieldErrors?: Record<string, string[]> } }; message?: string };
|
||||
const trpcErr = err as {
|
||||
data?: { zodError?: { fieldErrors?: Record<string, string[]> } };
|
||||
message?: string;
|
||||
};
|
||||
|
||||
if (trpcErr?.data?.zodError?.fieldErrors) {
|
||||
for (const [field, messages] of Object.entries(trpcErr.data.zodError.fieldErrors)) {
|
||||
@@ -427,7 +484,10 @@ function extractServerErrors(err: unknown, displayType: string): Record<string,
|
||||
}
|
||||
|
||||
try {
|
||||
const issues = JSON.parse(trpcErr?.message ?? "[]") as Array<{ path: (string | number)[]; message: string }>;
|
||||
const issues = JSON.parse(trpcErr?.message ?? "[]") as Array<{
|
||||
path: (string | number)[];
|
||||
message: string;
|
||||
}>;
|
||||
const fieldMap = serverToFormFieldMap[displayType] ?? {};
|
||||
|
||||
for (const issue of issues) {
|
||||
@@ -451,7 +511,13 @@ const listItemDefaults = {
|
||||
mapping: { label: "", jsonPath: "$", unit: "" },
|
||||
column: { header: "", jsonPath: "$" },
|
||||
statGridItem: { label: "", jsonPath: "$", unit: "", color: "blue" },
|
||||
progressBar: { label: "", valuePath: "$", maxPath: "", unit: "", color: "blue" },
|
||||
progressBar: {
|
||||
label: "",
|
||||
valuePath: "$",
|
||||
maxPath: "",
|
||||
unit: "",
|
||||
color: "blue",
|
||||
},
|
||||
statusItem: { label: "", jsonPath: "$", goodValues: "online,true" },
|
||||
countGridItem: { label: "", jsonPath: "$", unit: "" },
|
||||
} as const;
|
||||
@@ -521,7 +587,10 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi
|
||||
displayConfig: displayConfig as never,
|
||||
secrets: values.secrets
|
||||
.filter((s) => s.value)
|
||||
.map((s) => ({ kind: s.kind as "apiKey" | "username" | "password", value: s.value })),
|
||||
.map((s) => ({
|
||||
kind: s.kind as "apiKey" | "username" | "password",
|
||||
value: s.value,
|
||||
})),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -535,7 +604,10 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi
|
||||
router.push(`/manage/custom-widgets/edit/${result.id}`);
|
||||
} else if (definitionId) {
|
||||
await updateMutation.mutateAsync({ id: definitionId, ...payload });
|
||||
showSuccessNotification({ title: t("action.save"), message: t("notification.updated", { name: values.name }) });
|
||||
showSuccessNotification({
|
||||
title: t("action.save"),
|
||||
message: t("notification.updated", { name: values.name }),
|
||||
});
|
||||
setPreviewRefreshSignal((n) => n + 1);
|
||||
await utils.customWidget.all.invalidate();
|
||||
await utils.customWidget.byId.invalidate({ id: definitionId });
|
||||
@@ -547,7 +619,10 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi
|
||||
form.setErrors(serverErrors);
|
||||
}
|
||||
const errorKey = mode === "create" ? "notification.createError" : "notification.updateError";
|
||||
showErrorNotification({ title: t("action.save"), message: t(errorKey as never) });
|
||||
showErrorNotification({
|
||||
title: t("action.save"),
|
||||
message: t(errorKey as never),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -566,7 +641,10 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi
|
||||
displayConfig: buildConfig?.(values) as z.infer<typeof displayConfigSchema>,
|
||||
secrets: values.secrets
|
||||
.filter((s) => s.value)
|
||||
.map((s) => ({ kind: s.kind as CustomWidgetSecretKind, value: s.value })),
|
||||
.map((s) => ({
|
||||
kind: s.kind as CustomWidgetSecretKind,
|
||||
value: s.value,
|
||||
})),
|
||||
definitionId,
|
||||
};
|
||||
}, [form.values, definitionId]);
|
||||
@@ -749,7 +827,18 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Box w={340} style={{ flexShrink: 0, position: "sticky", top: 100, alignSelf: "start" }} visibleFrom="md">
|
||||
<Box
|
||||
w={480}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
position: "sticky",
|
||||
top: 80,
|
||||
alignSelf: "start",
|
||||
maxHeight: "calc(100vh - 100px)",
|
||||
overflow: "auto",
|
||||
}}
|
||||
visibleFrom="md"
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Button type="submit" fullWidth loading={createMutation.isPending || updateMutation.isPending}>
|
||||
{mode === "create" ? t("action.create") : t("action.save")}
|
||||
@@ -817,13 +906,19 @@ function DisplayTypeFields({
|
||||
<Group grow>
|
||||
<Select
|
||||
label={t("field.valueSize")}
|
||||
data={["sm", "md", "lg", "xl"].map((v) => ({ value: v, label: t(`sizeOption.${v}` as never) }))}
|
||||
data={["sm", "md", "lg", "xl"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`sizeOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("valueSize")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
<Select
|
||||
label={t("field.labelPosition")}
|
||||
data={["above", "below"].map((v) => ({ value: v, label: t(`labelPositionOption.${v}` as never) }))}
|
||||
data={["above", "below"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`labelPositionOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("labelPosition")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
@@ -838,7 +933,10 @@ function DisplayTypeFields({
|
||||
<Group grow>
|
||||
<Select
|
||||
label={t("field.kvLayout")}
|
||||
data={["list", "grid"].map((v) => ({ value: v, label: t(`layoutOption.${v}` as never) }))}
|
||||
data={["list", "grid"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`layoutOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("kvLayout")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
@@ -958,7 +1056,10 @@ function DisplayTypeFields({
|
||||
<NumberInput label={t("field.gridColumns")} min={1} max={4} {...form.getInputProps("statGridColumns")} />
|
||||
<Select
|
||||
label={t("field.cardStyle")}
|
||||
data={["filled", "outline", "subtle"].map((v) => ({ value: v, label: t(`cardStyleOption.${v}` as never) }))}
|
||||
data={["filled", "outline", "subtle"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`cardStyleOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("cardStyle")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
@@ -1027,7 +1128,10 @@ function DisplayTypeFields({
|
||||
<Switch label={t("field.showPercentage")} {...form.getInputProps("showPercentage", { type: "checkbox" })} />
|
||||
<Select
|
||||
label={t("field.barSize")}
|
||||
data={["sm", "md", "lg"].map((v) => ({ value: v, label: t(`sizeOption.${v}` as never) }))}
|
||||
data={["sm", "md", "lg"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`sizeOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("barSize")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
@@ -1103,13 +1207,19 @@ function DisplayTypeFields({
|
||||
<Group grow>
|
||||
<Select
|
||||
label={t("field.kvLayout")}
|
||||
data={["list", "grid"].map((v) => ({ value: v, label: t(`layoutOption.${v}` as never) }))}
|
||||
data={["list", "grid"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`layoutOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("statusLayout")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
<Select
|
||||
label={t("field.dotSize")}
|
||||
data={["sm", "md", "lg"].map((v) => ({ value: v, label: t(`sizeOption.${v}` as never) }))}
|
||||
data={["sm", "md", "lg"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`sizeOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("dotSize")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
@@ -1171,7 +1281,10 @@ function DisplayTypeFields({
|
||||
<NumberInput label={t("field.gridColumns")} min={2} max={4} {...form.getInputProps("countGridColumns")} />
|
||||
<Select
|
||||
label={t("field.valueSize")}
|
||||
data={["sm", "md", "lg"].map((v) => ({ value: v, label: t(`sizeOption.${v}` as never) }))}
|
||||
data={["sm", "md", "lg"].map((v) => ({
|
||||
value: v,
|
||||
label: t(`sizeOption.${v}` as never),
|
||||
}))}
|
||||
{...form.getInputProps("countValueSize")}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
|
||||
@@ -57,13 +57,13 @@ export const CustomWidgetList = ({ definitions }: CustomWidgetListProps) => {
|
||||
{definitions.map((def) => (
|
||||
<Table.Tr key={def.id} style={{ opacity: def.enabled ? undefined : 0.5 }}>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{def.iconUrl ? (
|
||||
<Avatar size={20} radius="sm" src={def.iconUrl} styles={{ image: { objectFit: "contain" } }} />
|
||||
) : (
|
||||
<IconApi size={16} />
|
||||
)}
|
||||
<Text size="sm" fw={500} lineClamp={1} c={def.enabled ? undefined : "dimmed"}>
|
||||
<Text size="sm" fw={500} lineClamp={1} c={def.enabled ? undefined : "dimmed"} style={{ minWidth: 0 }}>
|
||||
{def.name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -109,7 +109,7 @@ export function CustomWidgetPreview({
|
||||
</Group>
|
||||
)}
|
||||
|
||||
<Card withBorder p="xs" bg="var(--mantine-color-dark-7)" mih={120}>
|
||||
<Card withBorder p="xs" bg="var(--mantine-color-dark-7)" mah={320} style={{ overflow: "auto" }}>
|
||||
<PreviewDisplay data={displayData} />
|
||||
</Card>
|
||||
|
||||
@@ -119,7 +119,9 @@ export function CustomWidgetPreview({
|
||||
variant="subtle"
|
||||
leftSection={<IconExternalLink size={14} />}
|
||||
onClick={() => {
|
||||
const blob = new Blob([fetchResult.rawResponse ?? ""], { type: "application/json" });
|
||||
const blob = new Blob([fetchResult.rawResponse ?? ""], {
|
||||
type: "application/json",
|
||||
});
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl);
|
||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
|
||||
|
||||
@@ -163,7 +163,7 @@ export default async function EditCustomWidgetPage(props: EditCustomWidgetPagePr
|
||||
return (
|
||||
<>
|
||||
<DynamicBreadcrumb dynamicMappings={new Map([[params.id, definition.name]])} nonInteractable={["edit"]} />
|
||||
<Container size="xl">
|
||||
<Container fluid>
|
||||
<Stack>
|
||||
<Title>{definition.name}</Title>
|
||||
<CustomWidgetBetaBanner />
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function NewCustomWidgetPage() {
|
||||
return (
|
||||
<>
|
||||
<DynamicBreadcrumb />
|
||||
<Container size="xl">
|
||||
<Container fluid>
|
||||
<Stack>
|
||||
<div>
|
||||
<Title>{t("page.create.title")}</Title>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { Button, Center, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
export default function ManageErrorPage({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
unstable_retry: () => void;
|
||||
}) {
|
||||
const tError = useScopedI18n("management.error");
|
||||
const t = useI18n();
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<Center h="100%">
|
||||
<Stack align="center" gap="xs">
|
||||
<IconAlertTriangle size={48} stroke={1.5} />
|
||||
<Title order={3}>{tError("title")}</Title>
|
||||
<Text size="sm" c="dimmed" maw={480} ta="center">
|
||||
{tError("text")}
|
||||
</Text>
|
||||
{error.digest && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{tError("digest", { digest: error.digest })}
|
||||
</Text>
|
||||
)}
|
||||
<Button variant="light" onClick={unstable_retry}>
|
||||
{t("common.action.tryAgain")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
+2
@@ -131,6 +131,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
{(error.data.reason === "untrusted" && rootCertificate.isSelfSigned) ||
|
||||
error.data.reason === "hostnameMismatch" ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={error.data.reason === "hostnameMismatch" ? handleTrustHostname : handleTrustSelfSigned}
|
||||
@@ -140,6 +141,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
) : null}
|
||||
{error.data.reason === "untrusted" && !rootCertificate.isSelfSigned ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={() =>
|
||||
|
||||
@@ -19,6 +19,7 @@ export const DeleteIntegrationActionButton = ({ count, integration }: DeleteInte
|
||||
const t = useScopedI18n("integration.page.delete");
|
||||
const router = useRouter();
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const utils = clientApi.useUtils();
|
||||
const { mutateAsync, isPending } = clientApi.integration.delete.useMutation();
|
||||
|
||||
return (
|
||||
@@ -43,6 +44,7 @@ export const DeleteIntegrationActionButton = ({ count, integration }: DeleteInte
|
||||
router.replace("/manage/integrations");
|
||||
}
|
||||
void revalidatePathActionAsync("/manage/integrations");
|
||||
void utils.integration.invalidate();
|
||||
},
|
||||
onError: () => {
|
||||
showErrorNotification({
|
||||
|
||||
@@ -54,6 +54,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
|
||||
const hasUrlSecret = initialSecretsKinds.includes("url");
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
const router = useRouter();
|
||||
const form = useZodForm(formSchema, {
|
||||
initialValues: {
|
||||
@@ -102,6 +103,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
||||
title: t("integration.page.edit.notification.success.title"),
|
||||
message: t("integration.page.edit.notification.success.message"),
|
||||
});
|
||||
void utils.integration.invalidate();
|
||||
void revalidatePathActionAsync("/manage/integrations").then(() => router.push("/manage/integrations"));
|
||||
},
|
||||
onError: () => {
|
||||
|
||||
@@ -97,13 +97,19 @@ export const NewIntegrationForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const utils = clientApi.useUtils();
|
||||
const { mutateAsync: createIntegrationAsync, isPending: isCreatePending } = clientApi.integration.create.useMutation({
|
||||
async onSuccess() {
|
||||
await revalidatePathActionAsync("/manage/integrations");
|
||||
await utils.integration.invalidate();
|
||||
},
|
||||
});
|
||||
const { mutateAsync: createOnboardingIntegrationAsync, isPending: isOnboardingCreatePending } =
|
||||
clientApi.onboard.createIntegration.useMutation();
|
||||
clientApi.onboard.createIntegration.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.integration.invalidate();
|
||||
},
|
||||
});
|
||||
const isPending = isCreatePending || isOnboardingCreatePending;
|
||||
const [error, setError] = useState<null | AnyMappedTestConnectionError>(null);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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 { WidgetIntegrationSelect } from "@homarr/widgets/widget-integration-select";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
Loaded 100 of 373 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user