6.2 KiB
Vantage
A desktop dashboard for a self-hosted homelab: per-host CPU / memory / disk / load / uptime,
Docker container control, and HTTP / TCP health checks. Built with C# and Avalonia 11,
targeting net6.0.
Vantage is designed to be dogfooded against a real VPS and lab hosts over SSH, but it runs fully offline out of the box using deterministic mock providers, so the UI and the whole data flow work without a single live host.
Architecture
Two projects plus two test projects:
src/Vantage.Core- models, provider abstractions, parsers, health checks, config store. No UI dependency, fully unit tested.src/Vantage.App- the Avalonia application: views, view models, a hand-rolled sparkline control, dependency injection wiring.tests/Vantage.Core.Tests- parsers, providers, health checks, config store.tests/Vantage.App.Tests- view models and a headless Avalonia smoke test that loads the real window and binds the real view model.
Provider abstractions
Everything that touches a remote host sits behind an interface, with a real SSH implementation and a mock implementation:
IMetricsProvider-SshMetricsProviderruns one batched shell command per poll (/proc/loadavg,/proc/uptime,/proc/meminfo,df, two/proc/statsnapshots for the CPU delta) and parses it;MockMetricsProvidersynthesizes time-varying, per-host-stable metrics so sparklines animate offline.IDockerProvider-SshDockerProvidershells out todocker ps/docker start|stop|restart;MockDockerProviderkeeps an in-memory container set whose state transitions on actions.IHealthChecker-HealthCheckeris a real implementation (HttpClient for HTTP, TcpClient for TCP) and works against any reachable endpoint right now.
The SSH transport is isolated in ISshCommandRunner / SshNetCommandRunner. Because of that,
the SSH providers are exercised in tests with a fake runner feeding captured command output -
no live host needed to test the parse path.
Metrics parse path
The remote command emits marker lines (@@vantage:loadavg, @@vantage:df, ...). Each section
has its own pure parser (LoadAvgParser, MemInfoParser, DfParser, ProcStatParser,
CpuUsage), and MetricsBundleParser stitches them into a HostMetrics. Every parser is a
pure function with a fixture-based test.
Wave 2 (depth)
Built additively on top of v1, all in Vantage.Core behind the same interface-plus-mock pattern:
- Metrics history -
IMetricsStore/SqliteMetricsStorewrites a time-series of cpu / memory / disk / load into SQLite (~/.config/vantage/history.db), with range queries and rolling retention (PruneOlderThanAsync). The monitor loop records every successful poll. - Alerts -
AlertRule(threshold + comparison +Forduration + severity) evaluated per host byAlertRuleEvaluator(Ok -> Pending -> Firing, with a Resolved transition on recovery).AlertEnginefans transitions out to sinks:LogAlertSink(in-memory alert log),DesktopAlertSink(via anIDesktopNotifierseam), andWebhookAlertSink(POSTs JSON). The app wires the engine into the poll loop and shows live alert state in an Alerts tab. - More collectors - each is parser +
ISomethingProvider+ SSH impl + mock: network rx/tx (/proc/net/dev+ throughput delta), top processes (ps), systemd services (systemctl list-units), temperatures (/sys/class/thermal) and GPU (nvidia-smi). - Log viewer -
LogCommandbuildsjournalctl -u ... -o short-isoortail -n ... file(with an optional shell-quotedgrepfilter),LogLineParsersplits timestamp / unit / message, behindILogProvider(SSH + mock). - Prometheus provider -
PrometheusTextParserparses the text exposition format (labels with escapes,+Inf/-Inf/NaN, optional timestamp);PrometheusMetricsProviderscrapes a node_exporter/metricsendpoint and maps it toHostMetrics(CPU from a two-scrape counter delta). Select it withVANTAGE_PROVIDERS=prometheus. - Multi-host view -
OverviewViewModelaggregates online/offline counts, average and peak CPU, and firing-alert count; surfaced as a Fleet grid tab.
Running
Requires the .NET 6 SDK.
dotnet build
dotnet test
dotnet run --project src/Vantage.App
By default the app uses the mock providers, so it comes up populated with demo hosts and health checks and needs nothing configured. To point it at real hosts over SSH:
VANTAGE_PROVIDERS=ssh dotnet run --project src/Vantage.App
VANTAGE_PROVIDERS accepts mock (default), ssh, or prometheus.
Configuration
Host and health-check configuration persists as JSON at
%APPDATA%/vantage/config.json (Linux: ~/.config/vantage/config.json). On first run with no
config file, the app seeds demo data. The persistence layer (JsonConfigStore) is done and
tested; for now edit that JSON file and restart to change hosts. An in-app editor is the next
piece of UI (see below).
Verification status
dotnet buildis clean (0 warnings, 0 errors).dotnet testis green (110 tests): v1 parsers and providers, plus wave-2 coverage - SQLite time-series write/read/retention, alert-rule evaluation (pending/firing/resolved and the counterfactual "never fires early" cases), alert sinks (webhook posts JSON to a stubbed handler, log/desktop sinks), the network / process / systemd / thermal / GPU parsers, log command building and line parsing, the Prometheus text parser and node_exporter mapping (including two-scrape CPU rate), and the app-side wiring test proving a poll records history and fires an alert.- A headless Avalonia test loads the actual
MainWindow(Fleet / Host / Alerts / Docker / Health tabs), binds the real view model, and asserts the visual tree materialized.
This environment is headless (no X server), so the window's appearance has not been visually verified. Screenshots need to be taken on a machine with a display.
What is intentionally not in v1
- In-app host / health-check editor forms (config is file-based for now).
- SSH connection pooling (a fresh connection per poll today) and SSH-agent auth
(
SshAuthMethod.Agentthrows a clear NotSupported until wired). - Packaging / installers, mobile, live charting library (the sparkline is hand-rolled).