Registry and Analytics: run, subnet, direction and protocol filters, successes-by-target chart

Registry (/registry):
- filters by run (slice by the address's cycle in that run), subnet
  (drop-down of configured subnets), direction (egress/ingress) and
  protocol (icmp, tcp, ssh, https, tls); status in scope is computed over
  the narrowed checks
- chart "successful checks per target (egress) / site (ingress)" when both
  direction and protocol are chosen; a row opens the list of addresses
  (dialog, CSV)
- API: direction/protocol parameters and run in GET /admin/registry,
  GET /admin/registry/breakdown and /breakdown/list
- subnet filter passes ids as one JSON parameter (SQLite variable limit)

Analytics (/analytics):
- subnet filter recomputes the whole page over the addresses of the run
  inside the subnet; only their checks are read; cache per run and subnet
- direction and protocol focus the page; with both set the registry chart
  is shown
- subnet parameter in GET /admin/analytics/runs/{id} and lists (JSON, CSV)

Docs: plans and summaries in docs/changes, README, API, USAGE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-06 14:23:48 +03:00
1 parent 068c10ea1c
commit ded196ec8d
40 files changed
+2545 -188

No files matched your search

+83 -6
View File
@@ -671,8 +671,8 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
### `GET /api/v1/admin/registry`
Список адресов реестра с краткой сводкой по каждому. Без параметров — все адреса одним массивом; **с `limit`** (`1`…`1000`) —
постраничный конверт `{"items": [...], "total": N, "limit": L, "offset": O}`, параметры `offset`, `q` (подстрока адреса) и
`last_result` (`pass`/`partial`/`fail`/`cancelled`). Страница и фильтры применяются в SQL до расчёта сводки, поэтому
постраничный конверт `{"items": [...], "total": N, "limit": L, "offset": O, "run": R}`, параметры `offset`, `q` (подстрока адреса) и
`last_result` (`pass`/`partial`/`fail`/`cancelled`); остальные фильтры — ниже. Страница и фильтры применяются в SQL до расчёта сводки, поэтому
реестр из тысяч адресов отдаётся за доли секунды.
```json
@@ -714,9 +714,77 @@ 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`.
Фильтры постраничного режима (только вместе с `limit`), комбинируются через И:
| Параметр | Значения | Смысл |
|----------|----------|-------|
| `run` | id запуска | Только адреса запуска (см. [«Аналитика запусков»](#аналитика-запусков)); открытый (идущий) запуск тоже допустим, данные в нём частичные. Неизвестный запуск — пустой список. |
| `subnet` | CIDR, например `203.0.113.0/24` | Только адреса внутри подсети. |
| `direction` | `egress`, `ingress` | Только проверки этого направления. |
| `protocol` | `icmp`, `tcp`, `ssh`, `https`, `tls` | Только проверки этого семейства (`tcp` = `tcp-22`, `tcp-443` и т. д.). `tls` бывает только на входе, поэтому `direction=egress&protocol=tls` всегда даёт пустой список. |
Неверные `run`, `subnet`, `direction`, `protocol` и `last_result` — `400`.
**Запуск как срез данных.** Без `run` поля ответа считаются по последнему
циклу адреса. С `run` — по циклу адреса **в этом запуске**: `last_cycle_id` —
цикл запуска, `last_result` — вердикт запуска (как на странице «Аналитика»),
`egress`/`ingress` — проверки этого цикла. Если проверки запуска уже удалены
(очистка истории, `history_retention_cycles`), адрес остаётся в списке, а
уровни пусты (`total: 0`). Значение `run` возвращается в конверте (`0` — срез
не задан).
**Направление и протокол** сужают область проверок: в `egress`/`ingress` и
`by_type` попадают только подходящие проверки, а адрес без таких проверок из
списка исключается. `last_result`-фильтр при этом считается по проверкам
области, а не по вердикту: все успешны — `pass`, ни одной — `fail`, иначе
`partial` (вердикт учитывает и недостающие результаты, поэтому может
отличаться); `cancelled` вместе с `direction`/`protocol` всегда даёт пустой
список. Поле `last_result` в самих строках остаётся вердиктом (запуска или
последнего цикла).
Пример: адреса подсети, у которых в запуске 3 все входящие проверки `https`
неуспешны:
```
GET /api/v1/admin/registry?limit=50&run=3&subnet=203.0.113.0/24&direction=ingress&protocol=https&last_result=fail
```
### `GET /api/v1/admin/registry/breakdown`
Успешные проверки по целям (egress) или площадкам (ingress) — данные чарта над таблицей реестра. Параметры те же, что
у `GET /api/v1/admin/registry` (`q`, `last_result`, `run`, `subnet`, `direction`, `protocol`; `limit` и `offset` не нужны), но
`direction` и `protocol` **обязательны** — без любого из них `400`. Срез и набор адресов тоже те же, что у списка: все адреса под фильтром (не
страница), цикл адреса в запуске `run` или его последний цикл, проверки выбранных направления и протокола.
```json
{
"group": "site", "direction": "ingress", "protocol": "tcp", "run": 3, "addresses": 6440,
"rows": [
{"key": "inbound-site-2", "label": "rxspb", "total": 12880, "ok": 12790},
{"key": "inbound-site-1", "label": "rxmsk", "total": 12880, "ok": 12611}
]
}
```
`group` — `target` (egress, группировка по `checks.target`) или `site` (ingress, по `checks.source`); `addresses` совпадает с `total`
списка при тех же фильтрах. `key` — исходное значение (его принимает `…/list?key=`), `label` — подпись: у цели адрес без схемы и
завершающего `/` (`repo.almalinux.org/almalinux`), у площадки её имя из настройки (`site-N`, если площадка уже удалена). Считаются
**проверки**, а не адреса: `total` — записанные проверки, `ok` — успешные; у `tcp` и `tls` на ingress у адреса бывает по проверке на
каждую площадку и порт. Строки идут от большего `ok` к меньшему, при равенстве — по `label`. Нет проверок (например, `egress` + `tls`) —
`rows: []`.
### `GET /api/v1/admin/registry/breakdown/list`
Проверки одной строки чарта: те же параметры плюс `key` (обязателен, значение `key` из строки чарта). Ответ — таблица
`{"columns": [...], "rows": [[...]]}`; сначала провалы, затем в порядке реестра. Столбцы: `Адрес`, `Результат` (`успешно`/`провал`),
`Тип проверки` (`https`, `tcp-22`…), `Цель` (egress) или `Площадка` (ingress), `Валидатор` (только egress), `Задержка, мс`, `Детали`
(ошибка), `Проверено (UTC)`. С `format=csv` — тот же список файлом (`registry_<направление>_<протокол>_<ключ>.csv`, UTF-8 с BOM).
`404` — у ключа нет проверок в этом срезе; `400` — нет `key`, `direction` или `protocol`.
```
GET /api/v1/admin/registry/breakdown?run=3&direction=egress&protocol=https
GET /api/v1/admin/registry/breakdown/list?run=3&direction=egress&protocol=https&key=https://repo.almalinux.org/almalinux/&format=csv
```
### `GET /api/v1/admin/registry/{ip}`
@@ -1047,10 +1115,18 @@ curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
Все показатели страницы по одному **завершённому** запуску; открытый запуск — `409`, неизвестный — `404`.
Считаются проверки последнего цикла каждого адреса в запуске, в том числе пришедшие позже вердикта (как факты).
Результат кэшируется, пока данные запуска и список подсетей не менялись.
Результат кэшируется, пока данные запуска и список подсетей не менялись; кэш ведётся по паре «запуск + подсеть» и хранит не больше 16 записей (вытесняется давно не запрашивавшаяся).
Необязательный параметр `subnet` (CIDR, например `203.0.113.0/24`) пересчитывает все блоки по адресам запуска внутри подсети
(вложенные подсети тоже; биты хоста маскируются). Неверный CIDR — `400`. В ответе тогда есть блок `scope`.
```
GET /api/v1/admin/analytics/runs/7?subnet=203.0.113.0/24
```
| Блок | Содержимое |
|---|---|
| `scope` | только с `subnet`: `subnet` (CIDR в каноничной записи) и `run_addresses` (все адреса запуска без `cancelled`; `summary.addresses` — адреса подсети). `run.rechecked` остаётся по всему запуску |
| `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»; нулевые не выдаются |
@@ -1067,6 +1143,7 @@ curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
### `GET /api/v1/admin/analytics/runs/{id}/lists/{kind}`
Таблица адресов за показателем или классом ошибки: `{"kind", "class", "columns": [...], "rows": [[...]]}`.
С `?subnet=<CIDR>` — только адреса этой подсети, так что строки совпадают с числами отчёта той же подсети; неверный CIDR — `400` (то же для CSV).
`kind`: `verdict_pass`, `verdict_partial`, `verdict_fail`, `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`). Для `verdict_*` — адреса запуска с этим вердиктом (без `cancelled`, по числовому порядку; число строк равно `summary.pass`/`partial`/`fail`): адрес, подсеть, валидатор (по https-проверкам, «—», если их нет), `Egress` и `Ingress` («успешно из всех», «—» без проверок), «Проверок в цикле» (записано из ожидаемых) и у `partial` ещё «Причина» (как в блоке `reasons`). Для `error` строка — одна проваленная проверка: адрес, подсеть, площадка,