Files
Homelab-manager/README.md
T
bobbanandClaude Sonnet 5 f4274c1ad8 Make every table sortable and exportable to CSV
Applies the useSortable/SortableTh infrastructure introduced in the
Diagnostic Log commit to the rest of the app's tables: Audit Log,
Users, Servers (manage table), server task tables (grouped by
schedule type, on both the all-servers and per-server views), DNS
providers/zones/records, Docker containers, Gitea repos, Integrations
(manage table), IP Addresses, Proxmox guests, Secrets (already had
CSV, gained sorting), Semaphore templates, Synology volumes/disks, and
Tailscale devices.

Every column header is now click-to-sort (again on the raw field, not
its formatted display -- a byte count sorts numerically even though
the cell shows "1.2 GB", a date sorts chronologically even though the
cell shows "5d 15h 2m"), and every table got an "Export CSV" button
next to its Refresh button, exporting whatever's currently
sorted/filtered via the existing downloadCsv util.

Grouped tables (ServerTaskTable renders one sub-table per schedule
type) can't call the useSortable hook per group without breaking the
Rules of Hooks, so extracted its comparison core as a standalone
sortItems() function, driven by one shared sort-state pair at the
component's top level and applied per group.

Deliberately left two small (1-6 row) tables embedded inside stat
cards unsorted -- Proxmox's per-node storage list and ServerDetail's
agent-reported disk list -- since they're secondary detail inside an
already-scannable card, not primary list content; happy to add if
useful in practice.

Verified the shared sort core (sortItems) directly: numeric-aware
string compare (so "item2" sorts before "item10"), numbers, booleans,
and that null/undefined always sort to the end regardless of
direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:54:36 +02:00

7.1 KiB

Homelab Manager

Repository: git@10.200.5.13:bobban/Homelab-manager.git (gitea.labsconnect.se/bobban/Homelab-manager externally).

A single dashboard for a homelab: Proxmox, Synology DSM, Semaphore, Tailscale, Gitea, and Dockhand/Docker status and basic actions, plus DNS record management, an IP address inventory (IPAM), and a secret-expiry tracker (ported from Sloth Manager) and scheduled-task tracking across Debian/Raspbian hosts (ported from Schedule Task Manager). Looks and feels like a Tabler admin dashboard. Sign-in is delegated to Authentik (OIDC), with local admin/operator/viewer roles.

Status

All modules from the original plan are built:

  • Monorepo scaffold, Tabler-themed app shell/navigation

  • Authentik OIDC login, roles (first user to sign in becomes admin), audit log

  • Diagnostic Log (admin-only) — every call this app makes to a DNS provider or integration (Tailscale, Proxmox, Synology, Semaphore, Gitea, Dockhand), success or failure, with latency and the error message if it failed — the last 500 calls, filterable by source/result, for troubleshooting connectivity issues (ported from Sloth Manager's provider-diagnostics log, generalized to cover every integration this app has, not just DNS)

  • Secrets — expiry tracking for API tokens/certs/passwords

  • IP Addresses (IPAM) — inventory of IPs across vendors/locations, with "Sync from Tailscale" and "Sync from Proxmox" actions to pull in tailnet device IPs and VM/LXC IPs (never overwrites a manually-entered IP), and each entry now shows its matching DNS record(s) from the DNS module's cache

  • DNS — zone/record management across Cloudflare, Loopia, Pi-hole, Azure DNS, cPanel, and Technitium; providers are configured in-app (not via env vars) and their credentials are encrypted at rest

  • Servers — cron/systemd tracking across Debian/Raspbian servers via a lightweight push agent (agent/linux/), plus manual entries for things an agent can't see (Docker jobs, backups). The Servers page itself just lists registered servers and (admin-only) adds new ones / issues agent tokens; clicking a server opens its detail page with CPU/RAM/disk status, IP addresses, matching DNS names (looked up from the DNS module's cache), and its scheduled tasks — live hardware from Proxmox for VM/LXC-backed servers, or from the agent's own hardware report for everything else. Proxmox-linked servers also get start/stop/restart buttons right on the detail page.

  • Tailscale, Proxmox, Synology, Semaphore, Gitea, and Docker each get their own top-level page (backed by the matching integration) instead of living inside a shared Integrations browsing view:

    • Tailscale — device list with online/authorized status, and authorize/deauthorize/remove actions; a live device-count widget.
    • Proxmox — VM/LXC status across every node in the cluster, with start/restart/shutdown/stop actions; a live running/total widget. Each online node also gets its own host-stats card — uptime, CPU usage/cores/ load average, RAM and swap usage, and per-storage usage (local, LVM-thin, ZFS, NFS, etc). Supports self-signed certificates (common in homelab setups).
    • Synology — volume and disk health (read-only by design). Supports self-signed certificates.
    • Semaphore — Ansible run status per template across every project, with a "Run" action to trigger a template; a live template-count widget (with a last-failed warning).
    • Gitea — repo list with each repo's last CI run status, and re-running just the failed jobs in a run; a live repo-count widget (with a failing-build warning).
    • Docker — container status across every Docker host Dockhand manages (one credential covers all of them), with start/stop/restart actions, a host filter, and a live running/total widget.

    Add/edit/enable/disable/remove each integration's credential itself under Integrations → Manage integrations.

  • Every table in the app is click-to-sort on any column (numbers, booleans, and dates/text sort correctly regardless of how the column formats them) and has an "Export CSV" button next to it that exports whatever's currently sorted/filtered.

  • Settings (admin-only) — notification channels (Gotify, ntfy, SMTP, generic webhook) with per-channel test buttons, per-event toggles (DNS record added/updated/deleted, daily secret-expiry reminder and daily Tailscale key-expiry reminder — both sharing one configurable time/timezone), badge-color customization for both DNS providers and integration types, and a date/time display format (date order, 12/24-hour clock) applied consistently to every table in the app.

All six integrations follow the same config-in-UI + encrypted-credentials pattern, added (and edited — e.g. to rotate an expired API token without recreating the whole integration) through Integrations → Manage integrations.

Verified for real, end to end: every module above — including all six integrations, both their read-only views and their write actions (start/stop/restart, trigger-a-run, authorize/deauthorize) — has been exercised against the user's actual live homelab, not just built against specs. That pass also found and fixed two real bugs: the Synology adapter assumed HTTPS-only (the NAS is reached over plain HTTP), and the Tailscale adapter read online/isExitNode fields that don't actually exist in the real API response (fixed to derive them from connectedToControl and enabledRoutes). See the git log for the full verification notes per integration.

Requirements

  • Node.js 20+
  • An Authentik instance reachable from wherever this app runs

1. Set up an Authentik application

  1. Create an OAuth2/OpenID Provider:
    • Redirect URI: <APP_BASE_URL>/auth/callback
    • Scopes: openid, email, profile
  2. Create an Application using that provider, and assign the users/groups who should be able to sign in — Authentik controls who can authenticate; the app's own admin/operator/viewer roles control what they can do once in.
  3. Copy the provider's issuer URL, client ID, and client secret into .env.

2. Local development

cp .env.example .env   # fill in AUTHENTIK_*, SESSION_SECRET, CREDENTIALS_ENCRYPTION_KEY
npm install
npm run dev:server   # http://localhost:3000 (API)
npm run dev:web      # http://localhost:5173 (Vite dev server, proxies /api and /auth to :3000)

Visit http://localhost:5173 during development. Database migrations run automatically on server start. SQLite data lands in ./data (gitignored).

Generate SESSION_SECRET and CREDENTIALS_ENCRYPTION_KEY with:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

3. Run with Docker

cp .env.example .env
# edit .env
docker compose -f docker-compose.dev.yml up -d --build   # build locally
# or, once an image is published to your registry:
docker compose up -d

The app listens on HOST_PORT (default 3000); SQLite data persists in ./data on the host.