# Homelab Dashboard A small, self-contained single-page dashboard that links all your homelab services. Static site (HTML + CSS + JS), served by a lightweight **nginx** image. The optional calendar widget needs the container backend to refresh Google/ICS feeds, but the rest of the dashboard is pure static files. ## Services Internal: Open WebUI, ComfyUI, Gitea, Jellyfin, Immich, Home Assistant, Audio Server / Dubplate, Nextcloud, Vaultwarden, Nginx Proxy Manager, Netbird. External: gitlab-ixsol, gem360. ## Features - Top widgets: **Today** (calendar events for today, with in-progress highlighting), **Netbird** status (reachability + latency), **Git activity** (recent commits from Gitea and GitLab — 3 rows visible, scroll for more), **GPU + Ollama** (live AMD ROCm stats: utilization, VRAM, temperature, power; plus loaded Ollama models with their GPU/CPU split) - Inline **Open WebUI prompt line with model selection** in the Open WebUI card (requires `OPENWEBUI_TOKEN`) - Responsive grid of service cards with icons, categories and **real online status** (server-side health checks every minute: green = online, amber = 5xx error, red = unreachable; hover the badge for latency/details) - **Open** (new tab) + **Copy URL** per service - Live search / filter across names, categories and URLs - Live local clock - Calendar section aggregating multiple ICS feeds (refreshed every 15 min in the container) - Dark, themeable design; respects `prefers-reduced-motion` ## Run with Docker ```bash docker compose up -d --build ``` Then open . The dashboard is static, but the widgets are populated by a small Node.js updater (`api/update-data.js`) that runs inside the container and aggregates ICS feeds, Netbird status, git activity and Open WebUI models every 15 minutes. A second Node process (`api/server.js`) proxies chat prompts to Open WebUI. ### Environment variables Secrets (API keys) live in **`.env`** (gitignored) — docker compose reads it automatically. Non-secret settings go directly in `docker-compose.yml`: | Variable | Purpose | | --- | --- | | `TZ` | Timezone used for the "Today" widget (e.g. `Europe/Vienna`) | | `OPENWEBUI_TOKEN` | Open WebUI API key — enables the prompt line + model list (**set in `.env`**) | | `GITEA_TOKEN` | Gitea API token — private repo stats (public repos work without it) | | `GITLAB_TOKEN` | GitLab API token — private project stats (public projects work without it) | | `GITEA_URL` / `GITLAB_URL` / `NETBIRD_URL` / `OPENWEBUI_URL` | Override the default service URLs | | `GPU_STATS_URL` | GPU stats upstream (default: the `gpu-stats` compose service) | | `OLLAMA_URL` | Ollama instance for loaded-model stats (default: `http://100.103.83.12:11435`) | ### GPU stats service The `gpu-stats` compose service runs [`gpu_stats.py`](./gpu_stats.py), a tiny HTTP server that queries `rocm-smi`. It needs the host's ROCm install and GPU device nodes, so the compose file mounts `/opt/rocm` and passes `/dev/kfd` + `/dev/dri`. Set `GPU_NAME` in `docker-compose.yml` to your card's name (rocm-smi can't read it inside the container without `libdrm_amdgpu`). The widget polls it every 5 s through `/api/gpu-stats`. To run it on the host instead: `python3 gpu_stats.py`, set `GPU_STATS_URL=http://host.docker.internal:9101/stats` on the dashboard service and add `extra_hosts: ["host.docker.internal:host-gateway"]`. ### Without Docker (quick dev) Any static file server works for the dashboard itself, but the widgets will show errors because the data fetcher and chat proxy run server-side: ```bash # from this directory python3 -m http.server 8888 # or: npx serve . -l 8888 ``` ## Edit services All services live in one array at the top of [`script.js`](./script.js). Add / remove / reorder entries there: ```js { section: "internal", name: "My Service", url: "http://10.0.0.5:9000", cat: "infra", icon: "⚙️" } ``` `section` is either `"internal"` or `"external"`. `cat` is any key from the `CATEGORIES` map (or add your own). ## Build the image manually ```bash docker build -t homelab-dashboard . docker run -d --name homelab-dashboard -p 8888:80 --restart unless-stopped homelab-dashboard ``` ## Notes - Online badges are real health checks: `api/update-health.js` (cron, every minute) pings each service URL from the container with a 5 s timeout and writes `health.json`; the frontend refreshes badges every 30 s. Any HTTP response below 500 counts as online (401/404 still means the server is up); 5xx shows as "Error", timeouts/DNS failures as "Offline". TLS certificate validity is ignored (reachability only). - Calendar dependencies are bundled in `api/node_modules` so the Docker build works offline. To update them, run `npm install` in the `api/` directory. - Port 8888 is published by the Compose file; change it there if needed.