feat: built-in read-only dashboard at /ui + GET /runs
Build & push image / build-and-push (push) Successful in 26s
CI / test (push) Successful in 10m24s

Serves a self-contained vanilla-JS dashboard (embedded via go:embed): a per-model performance table — runs, minutes, findings, confirmed/false-positive/ungraded, points, points-per-minute, points-per-run, by-severity — with drill-down filters (date range, repo, provider, model, lens, grade/severity), free-text search, and a click-to-scope findings detail table.

Scoring stays client-side: the page has an editable points curve and computes points + value-per-minute in the browser, so the store remains point-free. Adds GET /runs (lists all runs, incl. zero-finding ones) so minutes/runs are filterable. The /ui shell is public (carries no data); data endpoints stay token-gated and the JS sends the token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 00:22:39 -04:00
parent 9458528b40
commit 35ebc53561
6 changed files with 460 additions and 12 deletions
+21 -6
View File
@@ -88,10 +88,12 @@ to the container's `:8090`. Then point `gadfly`'s `GADFLY_FINDINGS_URL` and `gad
| Method & path | Body / query | Purpose |
|---|---|---|
| `GET /healthz` | — | liveness (open even when a token is set) |
| `GET /` · `GET /ui` | — | **view-only dashboard** — HTML shell, public; its JS fetches the gated endpoints with the token |
| `POST /runs` | one run object | upsert a model's review of a PR (timing/tokens) |
| `POST /reports` | JSON **array** of report objects | record findings + which model reported each |
| `POST /findings/{id}/grade` | `{is_real, severity?, usefulness?, notes?, grader?}` | record a triage grade |
| `GET /export` | — | flat report×finding×run×latest-grade rows — the dashboard feed |
| `GET /runs` | — | list all runs (timing/tokens), oldest first |
| `GET /scoreboard` | — | points-free per-model rollup |
`POST /runs` body: `{run_id, repo, pr, model, provider, lenses, duration_secs, input_tokens?, output_tokens?, cost_usd?}`
@@ -101,7 +103,9 @@ to the container's `:8090`. Then point `gadfly`'s `GADFLY_FINDINGS_URL` and `gad
`GET /scoreboard` element: `{model, provider, runs, minutes, input_tokens, output_tokens, findings, confirmed, false_positive, ungraded, by_severity:{severity:count}}`.
If `GADFLY_REPORTS_TOKEN` is set, every route except `/healthz` requires `Authorization: Bearer <token>`.
If `GADFLY_REPORTS_TOKEN` is set, every route except the public view shell (`/healthz`, `/`, `/ui`)
requires `Authorization: Bearer <token>`. The `/ui` shell carries no data itself — its JS sends the
token on each fetch — so the public shell leaks nothing.
## Configuration
@@ -113,12 +117,23 @@ If `GADFLY_REPORTS_TOKEN` is set, every route except `/healthz` requires `Author
CLI flags `--addr` / `--db` / `--token` override the env.
## Dashboards
## Dashboard
Point anything at the JSON endpoints (or the SQLite file read-only). `GET /export` is the flat feed;
`GET /scoreboard` is the per-model rollup. Compute points and value-per-minute **in the dashboard**,
e.g. with a curve like `trivial=1, small=3, medium=5, high=8, critical=20`
`points = Σ weight[severity]·by_severity[severity]`, `value/min = points / minutes`.
A built-in **read-only dashboard** ships at **`/ui`** (hit the host root and you're redirected
there). It's a single self-contained page that pulls `/runs` + `/export` and does everything in your
browser: a **per-model performance table** — runs, minutes, findings, confirmed / false-positive /
ungraded, points, **points-per-minute**, points-per-run, by-severity — with **drill-down filters**
(date range, repo, provider, model, lens, grade/severity), free-text search, and a click-to-scope
findings detail table.
True to the store's "no points" rule, **scoring lives in the browser**: the page has an editable
points curve (default `trivial=1, small=3, medium=5, high=8, critical=20`) and computes
`points = Σ weight[severity]·count` and `value/min = points / minutes` on the fly — retune it without
touching stored data.
Auth: the `/ui` shell is public (it holds no data); paste the store token into its **connect** box,
or open `/ui?token=<token>` once (remembered in `localStorage`). Prefer your own dashboard? Point
Grafana/Metabase/etc. at the SQLite file or the same `/export` + `/scoreboard` + `/runs` JSON.
## How it fits together