Add the Analytics section: check runs, analytics API and page

Runs (migration 0011): a run groups the cycles of one launch. It opens when an
address enters an idle queue, takes everything submitted or re-checked while it
is open and is finalized when all its addresses are done; a re-check after that
opens a new run, so results of different runs never mix. check_runs,
run_results (one result per address and run, with the verdict and the expected
and stored check counts), subnets, run_id on ip_queue and checks. Existing data
is split into runs at pauses of more than an hour; ingress checks get the
validator that held the address (also at write time from now on).

Analytics (internal/analytics): figures computed from the stored checks of the
latest cycle of each address in the run, as facts next to the verdict: summary,
reasons of partial, data quality, subnets, targets and the subnet x target
matrix by check type, ingress by site, error classes, validators, and the
address lists behind the indicators and error classes. API: analytics runs,
report, lists (JSON or CSV), subnet list; run and subnet filters for the
registry.

Dashboard: /analytics matching the approved mockup (run selector, indicators
with address lists and CSV, error-class dialogs, drill-down to the registry),
subnet list on /settings. Sidebar: the control-api link state, theme toggle and
logout moved to the top, the three dots next to the logo removed, sections
grouped.

Rebuilt bin/control-api and bin/admin-dashboard to match. Plan, summary and the
updated README, API, USAGE, DASHBOARD and ADMIN_CLEANUP docs are in docs/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-03 18:36:03 +03:00
1 parent 864208238f
commit b7669c9e41
44 files changed
+4123 -62

No files matched your search

+68
View File
@@ -705,6 +705,10 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
результаты, поэтому при неполном наборе вердикт может быть хуже, чем «`ok` из
`total`».
Фильтры постраничного режима (только вместе с `limit`): `run` — только адреса,
у которых есть результат в этом запуске (см. [«Аналитика запусков»](#аналитика-запусков)); `subnet` — только
адреса внутри подсети (CIDR, например `203.0.113.0/24`). Неверный `run` или `subnet` — `400`.
### `GET /api/v1/admin/registry/{ip}`
Реестровая запись по одному адресу плюс вся сохранённая история проверок
@@ -993,3 +997,67 @@ curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
Для полностью автоматизированного локального прогона (без ручных curl)
см. `scripts/run-local-e2e.sh` и [docs/LOCAL_E2E.md](LOCAL_E2E.md).
## Аналитика запусков
Запуск — одна «партия» проверок. Он открывается, когда адрес попадает в пустую (или полностью обработанную)
очередь; пока он открыт, в него входят все добавленные и перепроверяемые адреса. Запуск завершается, когда все его
адреса получили итог (`done`, `failed`, `occupied`) либо удалены из очереди. Перепроверка после завершения запуска
открывает **новый** запуск, прежний не меняется. Тип запуска: `auto` (скан автоцикла) или `manual`. Для одного адреса
в запуске хранится результат его последнего цикла. Для данных, накопленных до появления запусков, запуски выделены по
паузам: циклы, которые заканчиваются с промежутком меньше часа, образуют один запуск.
### `GET /api/v1/admin/analytics/runs`
Список запусков, новые первыми, для выбора на странице «Аналитика».
```json
[
{"id": 2, "kind": "manual", "state": "open", "started_at": "2026-10-03T15:30:00Z", "finalized_at": null,
"addresses": 120, "pass": 40, "partial": 80, "fail": 0, "cancelled": 0, "total": 200, "pending": 80},
{"id": 1, "kind": "manual", "state": "finalized", "started_at": "2026-10-02T13:46:45Z", "finalized_at": "2026-10-02T22:28:54Z",
"addresses": 6440, "pass": 1962, "partial": 4478, "fail": 0, "cancelled": 0, "total": 6440, "pending": 0}
]
```
`addresses` — адреса с итогом, `total` — все адреса запуска в очереди, `pending` — ещё в работе.
### `GET /api/v1/admin/analytics/runs/{id}`
Все показатели страницы по одному **завершённому** запуску; открытый запуск — `409`, неизвестный — `404`.
Считаются проверки последнего цикла каждого адреса в запуске, в том числе пришедшие позже вердикта (как факты).
Результат кэшируется, пока данные запуска и список подсетей не менялись.
| Блок | Содержимое |
|---|---|
| `run` | `id`, `kind`, `state`, `started_at`, `finalized_at`, `duration_seconds`, `rechecked` (адресов с несколькими циклами в запуске) |
| `summary` | `addresses`, `pass`, `partial`, `fail`, `cancelled`; `egress_ok`, `ingress_ok` (адреса, у которых все записанные проверки уровня успешны); `egress_https_any_failed` и `egress_https_all_failed` (хотя бы одна / все https-проверки провалены), `egress_https_all_targets_failed` (все цели полного набора); `ingress_ssh_any_failed`, `ingress_ssh_all_failed`; `addresses_per_minute` |
| `reasons` | причины `partial`, каждый адрес один раз: «Только egress», «Ingress и egress», «Egress и неполный набор», «Ingress, egress и неполный набор», «Только неполный набор», «Только ingress»; нулевые не выдаются |
| `quality` | `late_failed_checks_at_pass`, `late_failed_addresses_at_pass`, `ingress_failed_checks`, `ingress_failed_late`, `incomplete_addresses`, `pass_with_failed_addresses`, `pass_by_facts` |
| `subnets` | по подсети: `cidr`, `label`, `addresses`, `pass`, `egress_ok`, `ingress_ok` (без списка подсетей — группы по /24; адрес вне списка — «прочие») |
| `targets` | `types` (семейства egress-проверок), `targets` (хосты, по убыванию провалов https), `failed` (по типу: число адресов с провалом на каждую цель) |
| `matrix` | по типу: строки «подсеть × цель» для подсетей с `partial` (`partial`, `percent` по целям) |
| `sites` | `types` и строки площадок: `total` и `ok` проверок по типу |
| `errors` | классы ошибок проваленных ingress-проверок («SSH: таймаут», «ICMP: нет ответа», …) со счётчиками |
| `validators` | по валидатору: `total`, `ok` https-проверок egress |
Тип проверки — это `check_type` до первого дефиса: `tcp-22` и `tcp-443` дают `tcp`.
### `GET /api/v1/admin/analytics/runs/{id}/lists/{kind}`
Таблица адресов за показателем или классом ошибки: `{"kind", "class", "columns": [...], "rows": [[...]]}`.
`kind`: `egress_https_any`, `egress_https_all`, `ingress_ssh_any`, `ingress_ssh_all` или `error` (с `?class=SSH: таймаут`;
без класса и неизвестный `kind` — `404`). С `?format=csv` — файл CSV (UTF-8 с BOM, `Content-Disposition: attachment`,
имя вида `ingress_ssh_all_run1.csv`). Для `error` строка — одна проваленная проверка: адрес, подсеть, площадка,
валидатор, вердикт адреса, статус («провал, в вердикте» или «провал, после вердикта»).
### `GET /api/v1/admin/config/subnets`, `PUT /api/v1/admin/config/subnets`
Список подсетей, по которым группируются адреса на странице «Аналитика». `PUT` заменяет список целиком:
```json
{"subnets": [{"cidr": "83.166.248.0/21", "label": "москва"}, {"cidr": "10.0.0.0/8"}]}
```
CIDR приводится к канонической записи (`10.1.2.3/24` → `10.1.2.0/24`), повторы схлопываются; неверный CIDR — `400`,
список остаётся прежним. Адрес относится к самой узкой подходящей подсети.