# dashboard A single-host computer dashboard and combined task manager: live system metrics, disk IO, process explorer, journal tail, and a systemd unit manager — all in one browser page. Built with **FastAPI** (Python, managed by [uv]) and **htmx** + **Chart.js** on the front end (no build step, JS is vendored). ## Screenshots | Overview | Disks | | --------------- | --------------- | | ![Overview](screenshots/Overview_20260830.png) | ![Disks](screenshots/Disks_20260830.png) | | Processes | Journal | | ![Processes](screenshots/Processes_20260830.png) | ![Journal](screenshots/Journal_20260830.png) | | Services | Plugins | | ![Services](screenshots/Services_20260830.png) | ![Plugins](screenshots/Plugins_20260830.png) | ## Authorship This code was written by **Qwen3.8-27B** (an LLM) and corrected by Johannes Schriewer . If you want to see the OpenCode transcripts see the `opencode` sub-directory. I will include all transcripts when changing the software there. ## ⚠️ Security This app has **no authentication and no TLS**. It binds to `127.0.0.1` by default and is meant to be used from the machine itself. To use it remotely, tunnel it — e.g. `ssh -L 8501:127.0.0.1:8501 user@host` and open `http://127.0.0.1:8501` — or serve it through your own authenticated reverse proxy / VPN. Do **not** point `DASH_HOST` at a public interface without adding authentication yourself. The Services tab can start/stop/enable/disable systemd units; any user who can reach the dashboard can do that (the app uses `sudo` for privileged actions, so the dashboard user needs sudo rights for `systemctl`). ## Quick start Requirements: Python ≥ 3.12 and [uv]. On Linux (it is tested on Arch). ```sh uv sync uv run python main.py ``` Then open . ## Configuration All settings are environment variables with the `DASH_` prefix; a `.env` file in the project root is read automatically (see `.env.example`). | Variable | Default | Meaning | | ------------------------ | ----------------------- | ------------------------------------------------ | | `DASH_HOST` | `127.0.0.1` | Bind address | | `DASH_PORT` | `8501` | Port | | `DASH_SAMPLE_INTERVAL` | `2.0` | Seconds between samples | | `DASH_RETENTION_MINUTES` | `60` | In-memory history window (lost on restart) | | `DASH_LLAMA_BASE_URL` | `http://127.0.0.1:8080` | llama-server (router mode) base URL | | `DASH_LLAMA_API_KEY` | *(empty)* | Set if llama-server runs with `--api-key` | | `DASH_LLAMA_TIMEOUT` | `4.0` | Seconds for llama-server requests | ## Tabs - **Overview** — CPU + GPU (utilisation, CPU temperature, VRAM, GPU temperature when exposed by sysfs/hwmon), RAM + Swap + VRAM, active Wi-Fi connection (SSID via `iw`) and IP addresses of all up interfaces. History charts (CPU/GPU, memory/VRAM, disk I/O) are in-memory, sampled every `DASH_SAMPLE_INTERVAL` for `DASH_RETENTION_MINUTES`. - **Disks** — partitions with size/use and per-device read/write rates. - **Processes** — live table, filterable and sortable by CPU, memory, RSS, GPU and IO columns. - **Journal** — streaming `journalctl` tail with level filter, unit filter, free-text search and a "hide sudo" toggle (on by default, since the dashboard's own `sudo systemctl`/`journalctl` calls otherwise log noise). Uses journal cursors, so filtering does not re-dump history. - **Services** — all systemd service units with state and enabled-ness, sortable by name, state and enabled-ness; click a name for details (main PID, start time, restarts, recent journal lines) and run `start` / `stop` / `restart` / `enable` / `disable` actions. - **Plugins** — currently **llama.cpp**: model status, load/unload buttons and a rescan for a `llama-server` running in router mode. ### llama.cpp router mode The plugin talks to a `llama-server` started with a models directory (router mode), which exposes the native `/models`, `/models/load` and `/models/unload` endpoints: ```sh llama-server --models-dir /path/to/your/models --host 127.0.0.1 --port 8080 ``` The plugin polls `GET /health` and `GET /models`, posts `{"model": id}` to `/models/load` and `/models/unload`, and rescans with `GET /models?reload=1`. If the server is down the plugin shows *unreachable* and the rest of the dashboard keeps working. ## Running as a systemd service A ready-made unit is in [`deploy/dashboard.service`](deploy/dashboard.service): You have to edit that file to point to the checkout of the tool and change the user and group! ```sh cp deploy/dashboard.service /etc/systemd/system/dashboard.service # adjust User= and paths if needed uv sync # once, after changing dependencies systemctl daemon-reload systemctl enable --now dashboard journalctl -u dashboard -f ``` ## Project layout ``` main.py # uvicorn entry point app/ config.py # pydantic-settings (DASH_* env) main.py # app factory, lifespan sampler sampling.py # background sampler task state.py # in-memory ring buffers collect/ # cpu / mem / gpu / disks / procs / net collectors (psutil + sysfs) systemd/units.py # unit list / detail / whitelisted actions (sudo fallback) journal.py # journalctl -o export parser + cursors render.py # jinja env + filters routers/ # overview / disks / processes / journal / services / plugins plugins/ # base.Plugin + llamacpp plugin templates/ # htmx fragments static/ # css, js, vendored htmx + chart.js deploy/ # dashboard.service ``` Adding a plugin: create a module in `app/plugins/` defining a `Plugin` instance (id, title, skeleton function) and register it in `app/plugins/__init__.py`. The skeleton is the static card shell, rendered once; it embeds the dynamically polled regions (e.g. a div with `hx-get`/`hx-trigger="every Ns"` pointing at the plugin's own endpoints). ## License [The Unlicense](LICENSE) — public domain dedication, no conditions. [uv]: https://docs.astral.sh/uv/