Files
arsam 009652ce3e Import documentation: README.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:20:34 +03:30

158 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dash — uptime observatory
A self-hosted uptime monitoring dashboard in a single binary. A Go check
engine with SQLite persistence streams live status over SSE into a
hand-built React frontend: three bespoke SVG circular visualizations with
spring physics, full RTL (Farsi) support, and a shareable public status mode.
![Dash](docs/dashboard.png)
## What it does
- **Five check types** — HTTP (status + latency), TCP port, TLS certificate
expiry (days remaining), ICMP ping, and Docker (container running +
healthcheck status via the daemon socket). Each target runs on its own
interval.
- **Flap detection** — N consecutive failures before a target is marked
down, M consecutive successes before it recovers. No alert noise from a
single dropped packet.
- **Incident grouping** — one incident spans the entire down period, from
the first failed check of the streak until recovery.
- **Live stream** — `/api/stream` pushes every probe and every confirmed
state transition over Server-Sent Events. The UI never polls.
- **Every result persisted** — timestamp, latency, status, error string in
SQLite (pure Go driver, no CGO), pruned after `history_days`.
## The frontend
Three circular instruments, all hand-built SVG + Framer Motion (no charting
libraries):
1. **Status rings** — one per target. The outer arc fills to the uptime
percentage over the selected window (24H/7D) and springs to every new
value; the inner brass gauge encodes latency on a log scale (1ms–2.5s);
each completed probe lands as a visible pulse; confirmed state changes
ripple outward. Click a ring and it travels into a detail view (shared
layout transition) with a latency sparkline, a per-target day dial, and
recent incidents.
2. **Orbital overview** — every target orbits the system core at
Kepler-flavored speeds (inner orbits run faster). Healthy bodies drift
calmly with motion trails, degraded ones breathe, a down body breaks its
orbit outward and flares red over the scar of its old path. The core
carries fleet-wide uptime and a brass scanner hand.
3. **Day dial** — a 24-hour clock face. Each of the last 24 hours is one
segment at its true wall-clock position, colored by status. Hours
without data stay hairline-thin. A brass hand marks now. One glance, one
full day.
Plus: a boot sequence where the instruments draw themselves in, staggered;
`prefers-reduced-motion` collapses all motion to fully-formed static
rendering; **فارسی / RTL** is a first-class layout mode (mirrored layout,
Vazirmatn type, Persian digits and the Persian solar calendar via `fa-IR`);
**dark and light themes** (persisted, both tuned variants of the same brass
/ ink design language); and a **Status** mode — the same instruments
stripped to a clean, shareable public status page.
Continuous motion (the orbital field, the scanner hand) runs outside React
entirely — one animation-frame loop mutates SVG attributes directly, so the
page stays at full frame rate regardless of target count. No SVG filters,
no blend modes on hot layers.
## Build
Requires Go ≥ 1.22 and Node ≥ 20. The frontend must be built first because
the binary embeds it via `go:embed`.
```sh
# 1. build the frontend into web/dist
cd web && npm install && npm run build && cd ..
# 2. compile the single binary (web/dist is embedded)
go build -o dash .
```
The result is one self-contained file, `dash`. Copy it and a `targets.yaml`
to any Linux/macOS/Windows box — nothing else to install.
## Run
```sh
./dash # uses ./targets.yaml, ./dash.db, :8080
./dash -config /etc/dash/targets.yaml -db /var/lib/dash.db -listen :9090
```
Open http://localhost:8080.
> **ICMP checks** need raw-socket privileges. Run as root, or grant the
> binary `CAP_NET_RAW` (`setcap cap_net_raw+ep ./dash`), or allow
> unprivileged ping (`sysctl net.ipv4.ping_group_range="0 2147483647"`).
> Other check types work without any privileges.
## Configuration — targets.yaml
```yaml
settings:
listen: ":8080" # bind address
db: "dash.db" # sqlite file
history_days: 30 # raw check retention
targets:
- id: example-web # unique slug (required)
name: "Example Website"
type: http # http | tcp | tls | icmp
url: https://example.com # http → url, tcp/tls → address, icmp → host
interval: 15s # time between checks (default 30s)
timeout: 5s # per-check timeout (default 5s)
fail_threshold: 3 # consecutive fails → down (default 3)
recover_threshold: 2 # consecutive oks → up (default 2)
expect_status: 200 # http only: require exact status
degraded_latency_ms: 1500 # mark degraded above this latency
- id: github-tls
name: "GitHub TLS"
type: tls
address: github.com:443
interval: 5m
warn_days: 21 # tls only: degraded when cert expires sooner
- id: my-container
name: "My Container"
type: docker
container: my-container # docker container name
interval: 30s
# socket: /var/run/docker.sock (default)
```
> **Docker checks** read the daemon socket. Run the binary as a user in the
> `docker` group (or `SupplementaryGroups=docker` in the systemd unit). A
> container is *up* when running, *degraded* while its healthcheck reports
> `starting`, and failing when stopped or `unhealthy`.
## API
| Endpoint | Description |
| --- | --- |
| `GET /api/targets` | Current state of all targets: flap-filtered status, last check, 24h/7d uptime, latency percentiles, open incident |
| `GET /api/targets/{id}/history?range=24h` | Time series: downsampled points plus 24 aggregate buckets (`range`: `30m`, `24h`, `7d`, …) |
| `GET /api/incidents?range=7d` | Grouped incidents, ongoing first |
| `GET /api/stream` | SSE: `snapshot` on connect, then `check` per probe and `state` per confirmed transition |
## Development
```sh
go run . & # backend on :8080
cd web && npm run dev # vite on :5173, proxies /api → :8080
```
## Layout
```
main.go, webfs.go entrypoint + embedded frontend
internal/config targets.yaml parsing, defaults, validation
internal/checker the four probe implementations
internal/engine scheduler, flap detection, incidents, SSE hub
internal/store sqlite persistence (modernc.org/sqlite, no CGO)
internal/api REST + SSE handlers
web/ React + TypeScript + Vite + Tailwind v4 + Motion
```