Cross Compile Go / build (push) Canceled after 0s
- embedded React admin UI at /admin/ui (go:embed), login with ADMIN_SECRET via signed HttpOnly session cookie, X-ADMIN-KEY still works for scripts - admin API: overview stats, indexing queue, addon management (priority, reindex, delete/restore/purge, bulk), whitelist CRUD, duplicate hashes, traffic, metrics explorer, services, events/audit log, system info - built-in metrics: request latency per route, visitors, top addons and searches, indexer/scraper counters; minute buckets in the DB with hourly rollups and retention; optional Prometheus /metrics (METRICS_TOKEN) - POST /back/telemetry for indexers/scrapers (metrics, events, heartbeat) and a stdlib-only Go client in telemetry/ - auth hardening: empty secrets no longer allow access, constant-time compare, CORS not applied to /admin and /back, TRUSTED_PROXIES setting - Dockerfile builds the UI and no longer bakes config.yaml into the image; CI builds the UI, runs tests and builds arm64 with CGO
125 lines
5.1 KiB
Markdown
125 lines
5.1 KiB
Markdown
# Admin dashboard, metrics and telemetry
|
|
|
|
The API binary serves an admin dashboard at **`/admin/ui/`** (`/admin` redirects there).
|
|
It is a React app in `web/`, built into `web/dist` and embedded with `go:embed`.
|
|
|
|
## Access
|
|
|
|
- Sign in with `ADMIN_SECRET`. The server sets an HttpOnly, `SameSite=Strict`
|
|
session cookie (`ADMIN_SESSION_TTL_HOURS`, default 7 days).
|
|
- Rotating `ADMIN_SECRET` signs every session out.
|
|
- If `ADMIN_SECRET` is empty, the dashboard and admin API are disabled.
|
|
- Set `TRUSTED_PROXIES` behind Traefik/Dokploy. Without it, client IPs can be
|
|
spoofed via `X-Forwarded-For`, which affects visitor counts and login throttling.
|
|
|
|
## Admin API
|
|
|
|
The JSON API lives under `/admin/api`. Scripts can call it with the
|
|
`X-ADMIN-KEY: <ADMIN_SECRET>` header instead of a cookie. Cookie requests that
|
|
change data must also send `X-Admin-CSRF: 1`.
|
|
|
|
| Area | Endpoints |
|
|
|---|---|
|
|
| Stats | `GET overview`, `GET overview/duplicates`, `GET traffic?from&to`, `GET top?kind=addon\|search&days` |
|
|
| Queue | `GET queue`, `POST queue/reset-stuck` |
|
|
| Addons | `GET addons?q&status&type&sort&order&limit&offset`, `GET addons/:id`, `GET addons/:id/files` |
|
|
| Addon actions | `POST addons/:id/{priority\|to-be-indexed\|reindex\|reset-indexing\|recheck\|restore}` with body `{"value": bool}`, `DELETE addons/:id`, `DELETE addons/:id/purge?confirm=<id>`, `POST bulk/addons` |
|
|
| Whitelist | `GET whitelist`, `POST whitelist` with body `{"hashes":[], "text":"", "note":""}`, `DELETE whitelist/:id`, `GET hashes/:hash` |
|
|
| Metrics | `GET metrics/catalog`, `GET metrics/query?service&name&agg&groupBy&match&from&to&step` |
|
|
| Services | `GET services`, `GET services/events`, `DELETE services/:id` |
|
|
| System | `GET system`, `POST system/maintenance` with body `{"action": "refresh-stats\|flush-metrics\|rollup\|purge-deleted-files"}` |
|
|
|
|
The legacy endpoint `POST /admin/addWhitelistedHash` (form field `hash`, `X-ADMIN-KEY`) still works.
|
|
|
|
## Metrics
|
|
|
|
- Every request is timed. Latency is recorded per route as a histogram, plus
|
|
status class, caller group and web vs direct client.
|
|
- The handlers also emit metrics for indexer claims and submissions, job
|
|
duration, scraper creates/updates/deletes, Discord webhooks and admin actions.
|
|
- Every minute, gauges sample the queue depth and total addon count.
|
|
|
|
Storage:
|
|
|
|
- Data is aggregated in memory per minute and flushed to the database every
|
|
minute (`metric_series` and `metric_points` tables).
|
|
- Minute data is rolled up hourly.
|
|
- Retention is configurable: `METRICS_MINUTE_RETENTION_DAYS` (default 7) and
|
|
`METRICS_HOUR_RETENTION_DAYS` (default 180).
|
|
|
|
Website visitors are counted as a salted hash of IP and user agent. The salt
|
|
rotates daily and only lives in memory, so the count is approximate. The most
|
|
looked-up addons and search queries are stored per day in `daily_top_items`.
|
|
|
|
Prometheus:
|
|
|
|
- Set `METRICS_TOKEN` to expose `GET /metrics` in the Prometheus text format.
|
|
- Authenticate with `Authorization: Bearer <token>`.
|
|
- This lets you add an external Prometheus/Grafana later.
|
|
|
|
## Telemetry from indexers and scrapers
|
|
|
|
Services report to `POST /back/telemetry` with `X-SECRET-KEY`. Every push is
|
|
also a heartbeat. An instance is shown as offline after three missed
|
|
`heartbeatIntervalSec`.
|
|
|
|
```json
|
|
{
|
|
"service": "indexer", "instance": "indexer-1", "version": "1.4.0",
|
|
"startedAt": 1727950000, "heartbeatIntervalSec": 15, "status": "ok",
|
|
"metrics": [
|
|
{"name": "jobs_total", "type": "counter", "value": 3, "labels": {"result": "ok"}},
|
|
{"name": "data_window_bytes", "type": "gauge", "value": 7.1e9},
|
|
{"name": "download_seconds", "type": "summary", "count": 3, "sum": 41.2, "min": 8.1, "max": 20.3}
|
|
],
|
|
"events": [{"level": "warn", "message": "data limit reached", "fields": {"limitGB": 50}}]
|
|
}
|
|
```
|
|
|
|
Metric types:
|
|
|
|
- `counter`: the increase since the last push.
|
|
- `gauge`: the current value.
|
|
- `summary`: pre-aggregated values from the client.
|
|
|
|
Limits:
|
|
|
|
- Names must match `^[a-z_][a-z0-9_]{0,63}$`.
|
|
- At most 8 labels per metric.
|
|
- At most 1000 metrics and 200 events per push.
|
|
- At most 200 series per metric and 5000 external series in total. Anything
|
|
over these limits is dropped and counted.
|
|
|
|
A stdlib-only Go client is in `telemetry/`, as its own module:
|
|
|
|
```go
|
|
import "gitea.tbdevent.eu/TBD/reforger_crawler_main/telemetry"
|
|
|
|
c := telemetry.New(telemetry.Config{BaseURL: apiURL, SecretKey: secret, Service: "indexer", Version: version})
|
|
c.Start(ctx)
|
|
defer c.Close(context.Background())
|
|
|
|
c.Counter("addons_indexed_total", "result", "ok").Inc()
|
|
c.Summary("download_seconds").Observe(took.Seconds())
|
|
c.Gauge("data_window_bytes").Set(float64(used))
|
|
c.Event(telemetry.Warn, "data limit reached", nil)
|
|
```
|
|
|
|
Reported metrics show up in the dashboard under **Services** and in the **Metrics explorer**.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
# API on SQLite (overrides config.yaml)
|
|
DB_DRIVER=sqlite SQLITE_PATH=dev.db SECRET_KEY=dev ADMIN_SECRET=devadminsecret1234 go run .
|
|
|
|
# UI with hot reload, proxied to :8083
|
|
npm --prefix web install
|
|
npm --prefix web run dev # http://localhost:5173/admin/ui/
|
|
|
|
# production build (needed before `go build` to embed the UI)
|
|
npm --prefix web run build
|
|
```
|
|
|
|
If the UI was not built, the binary still compiles and serves a placeholder page.
|