feat: add unauthenticated web dashboard showing monitoring state and recent alerts (#83)
All checks were successful
check / check (push) Successful in 4s
All checks were successful
check / check (push) Successful in 4s
## Summary Adds a read-only web dashboard at `GET /` that shows the current monitoring state and recent alerts. Unauthenticated, single-page, no navigation. ## What it shows - **Summary bar**: counts of monitored domains, hostnames, ports, certificates - **Domains**: nameservers with last-checked age - **Hostnames**: per-nameserver DNS records, status badges, relative age - **Ports**: open/closed state with associated hostnames and age - **TLS Certificates**: CN, issuer, expiry (color-coded by urgency), status, age - **Recent Alerts**: last 100 notifications in reverse chronological order with priority badges Every data point displays its age (e.g. "5m ago") so freshness is visible at a glance. Auto-refreshes every 30 seconds. ## What it does NOT show No secrets: webhook URLs, ntfy topics, Slack/Mattermost endpoints, API tokens, and configuration details are never exposed. ## Design All assets (CSS) are embedded in the binary and served from `/s/`. Zero external HTTP requests at runtime — no CDN dependencies or third-party resources. Dark, technical aesthetic with saturated teals and blues on dark slate. Single page — everything on one screen. ## Implementation - `internal/notify/history.go` — thread-safe ring buffer (`AlertHistory`) storing last 100 alerts - `internal/notify/notify.go` — records each alert in history before dispatch; refactored `SendNotification` into smaller `dispatch*` helpers to satisfy funlen - `internal/handlers/dashboard.go` — `HandleDashboard()` handler with embedded HTML template, helper functions (`relTime`, `formatRecords`, `expiryDays`, `joinStrings`) - `internal/handlers/templates/dashboard.html` — Tailwind-styled single-page dashboard - `internal/handlers/handlers.go` — added `State` and `Notify` dependencies via fx - `internal/server/routes.go` — registered `GET /` route - `static/` — embedded CSS assets served via `/s/` prefix - `README.md` — documented the dashboard and new endpoint ## Tests - `internal/notify/history_test.go` — empty, add+recent ordering, overflow beyond capacity - `internal/handlers/dashboard_test.go` — `relTime`, `expiryDays`, `formatRecords` - All existing tests pass unchanged - `docker build .` passes closes [#82](#82) <!-- session: rework-pr-83 --> Co-authored-by: user <user@Mac.lan guest wan> Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de> Reviewed-on: #83 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #83.
This commit is contained in:
37
README.md
37
README.md
@@ -124,13 +124,41 @@ includes:
|
||||
- State is written atomically (write to temp file, then rename) to prevent
|
||||
corruption.
|
||||
|
||||
### Web Dashboard
|
||||
|
||||
dnswatcher includes an unauthenticated, read-only web dashboard at the
|
||||
root URL (`/`). It displays:
|
||||
|
||||
- **Summary counts** for monitored domains, hostnames, ports, and
|
||||
certificates.
|
||||
- **Domains** with their discovered nameservers.
|
||||
- **Hostnames** with per-nameserver DNS records and status.
|
||||
- **Ports** with open/closed state and associated hostnames.
|
||||
- **TLS certificates** with CN, issuer, expiry, and status.
|
||||
- **Recent alerts** (last 100 notifications sent since the process
|
||||
started), displayed in reverse chronological order.
|
||||
|
||||
Every data point shows its age (e.g. "5m ago") so you can tell at a
|
||||
glance how fresh the information is. The page auto-refreshes every 30
|
||||
seconds.
|
||||
|
||||
The dashboard intentionally does not expose any configuration details
|
||||
such as webhook URLs, notification endpoints, or API tokens.
|
||||
|
||||
All assets (CSS) are embedded in the binary and served from the
|
||||
application itself. The dashboard makes zero external HTTP requests —
|
||||
no CDN dependencies or third-party resources are loaded at runtime.
|
||||
|
||||
### HTTP API
|
||||
|
||||
dnswatcher exposes a lightweight HTTP API for operational visibility:
|
||||
|
||||
| Endpoint | Description |
|
||||
|---------------------------------------|--------------------------------|
|
||||
| `GET /health` | Health check (JSON) |
|
||||
| `GET /` | Web dashboard (HTML) |
|
||||
| `GET /s/...` | Static assets (embedded CSS) |
|
||||
| `GET /.well-known/healthcheck` | Health check (JSON) |
|
||||
| `GET /health` | Health check (JSON, legacy) |
|
||||
| `GET /api/v1/status` | Current monitoring state |
|
||||
| `GET /metrics` | Prometheus metrics (optional) |
|
||||
|
||||
@@ -143,7 +171,7 @@ cmd/dnswatcher/main.go Entry point (uber/fx bootstrap)
|
||||
|
||||
internal/
|
||||
config/config.go Viper-based configuration
|
||||
globals/globals.go Build-time variables (version, arch)
|
||||
globals/globals.go Build-time variables (version)
|
||||
logger/logger.go slog structured logging (TTY detection)
|
||||
healthcheck/healthcheck.go Health check service
|
||||
middleware/middleware.go HTTP middleware (logging, CORS, metrics auth)
|
||||
@@ -335,11 +363,10 @@ make clean # Remove build artifacts
|
||||
|
||||
### Build-Time Variables
|
||||
|
||||
Version and architecture are injected via `-ldflags`:
|
||||
Version is injected via `-ldflags`:
|
||||
|
||||
```sh
|
||||
go build -ldflags "-X main.Version=$(git describe --tags --always) \
|
||||
-X main.Buildarch=$(go env GOARCH)" ./cmd/dnswatcher
|
||||
go build -ldflags "-X main.Version=$(git describe --tags --always)" ./cmd/dnswatcher
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user