Files
cloud-ip-validator/docs/changes/2026-10-03_16-39_analytics-section-plan.md
T
ayurishchevandClaude Sonnet 5.5 b7669c9e41 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>
2026-10-03 18:36:03 +03:00

150 lines
23 KiB
Markdown
Raw 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.
# План: раздел «Аналитика»
Статус: план, код не менялся. Основа — прогон 6440 адресов (2026-10-02 13:47 – 22:29 UTC) и три отчёта в `analysis/`.
## 1. Зачем
Сейчас выводы по прогону получены ручными запросами к БД: срезы по подсетям, провалы по целям, ingress по площадкам, расхождение вердикта и проверок. Нужна страница, которая показывает то же самое постоянно, для любого прогона, без SQL.
Главное требование: переключать **отметку времени** — завершённый запуск проверки, — чтобы данные разных запусков не смешивались.
## 2. Ключевая находка: «запуска» в данных нет
`cycle_id` считается **на адрес** (`ip_registry.next_cycle`), а не на весь прогон. В БД сейчас: цикл 1 у 6431 адреса, циклы 2–4 у 9 перепроверенных. По `cycle_id` нельзя выбрать «прогон от 2 октября»: он ничего не группирует.
Группировка по времени тоже ненадёжна: прогон 6440 адресов шёл 8 ч 42 мин, а перепроверки внутри него шли в то же время.
Поэтому нужна новая сущность — **запуск** (`run`). Это единственное изменение схемы, без него переключатель невозможен.
## 3. Модель данных (миграция `0010`)
Новые таблицы:
- `check_runs(id, kind, started_at, finalized_at, state)`. `kind`: `auto` (автоцикл), `manual` (добавление, скан, перепроверка). `state`: `open` или `finalized`.
- `run_results(run_id, registry_id, ip_address, cycle_id, verdict, aggregated_at, expected_checks, recorded_at_aggregation)`. Одна строка на адрес в запуске. Нужна, потому что вердикт сейчас хранится только в `ip_queue.overall_result` и затирается при перепроверке.
- `subnets(cidr PRIMARY KEY, label)`. Список подсетей заказчика (45 шт.) не лежит ни в БД, ни в репозитории, его задаёт администратор.
Новые колонки: `run_id` в `ip_queue` и `checks`. Индекс `idx_checks_run(run_id, registry_id)`.
### Правила запуска
1. Новый запуск открывается, когда в очередь попадает адрес, а открытого запуска нет. Автоцикл открывает запуск явно в начале фазы.
2. Пока запуск открыт, все добавленные и перепроверяемые адреса входят в него.
3. Запуск **завершается**, когда все его строки очереди в конечном состоянии (`done`, `failed`, `occupied`, отмена). Тогда `finalized_at` = последняя агрегация.
4. Перепроверка после завершения открывает **новый** запуск. Поэтому старый запуск не меняется.
5. Если адрес перепроверен внутри открытого запуска, в аналитике берётся его последний цикл этого запуска.
6. Поздние результаты (пришли после агрегации, как у 50 адресов в анализе 6.5) остаются в своём запуске: `run_id` берётся из строки очереди при записи проверки. Их число показывается отдельно.
### Заполнение уже накопленных данных
Запуски выделяются по паузе: если между окончаниями соседних циклов больше 60 минут, начинается новый запуск. Максимальная пауза в текущей БД 0,8 минуты, поэтому получится **один запуск на 6440 адресов**, 9 перепроверок войдут в него. Вердикт берётся из `ip_queue.overall_result`, для прежних циклов без него считается по проверкам и помечается как расчётный.
## 4. API (control-api, только чтение)
- `GET /api/v1/admin/analytics/runs` — список запусков: id, тип, начало, конец, адресов, доли `pass/partial/fail`, состояние. Открытый запуск виден, но помечен как идущий.
- `GET /api/v1/admin/analytics/runs/{id}` — все блоки страницы одним ответом (десятки КБ):
- **summary**: адресов; вердикты; Egress OK и Ingress OK по фактическим проверкам; доли по типам проверок; индикаторы прикладного уровня: адреса, провалившие все egress-проверки https (на запуске 02.10: 307, из них по всем 5 целям 290), и адреса, провалившие все ingress-проверки ssh (7; хотя бы с одной площадки 222); длительность и скорость; число поздних результатов; число неполных наборов.
- **partial_reasons**: причины `partial` (только egress; ingress и egress; только ingress; неполный набор; только неполный набор).
- **subnets**: по каждой подсети — адресов, `pass`, Egress OK, Ingress OK.
- **targets**: egress по цели и типу проверки (процент провалов); матрица «подсеть × цель» для выбранного типа.
- **sites**: ingress по площадке и типу проверки.
- **validators**: доля провалов egress по валидаторам.
- **errors**: классы ошибок по уровню и типу (таймаут, нет маршрута, баннер SSH и др.), классификатор по `checks.detail`.
- **data_quality**: поздние результаты, неполные наборы, адреса, где вердикт расходится с проверками.
- `GET/PUT /api/v1/admin/config/subnets` — список подсетей (замена целиком, текстом по одной в строке).
Группировка по типу проверки везде одна и та же, правило `CheckFamily` из `internal/db/models.go` (tcp-22 и tcp-443 → `tcp`). Новый тип появляется в таблицах сам.
Адрес → подсеть определяется в Go по самому длинному совпавшему префиксу (SQLite не умеет работать с CIDR). Для 6440 адресов это миллисекунды.
Расчёт идёт одним проходом по `checks` запуска (~190 тыс. строк). Результат завершённого запуска кэшируется в памяти по `run_id` и `MAX(checks.id)` запуска, чтобы поздний результат сбросил кэш.
## 5. Макет страницы `/analytics?run=<id>`
```
┌─ Аналитика ─────────────────────────────────────────────────────────────┐
│ Запуск: ◀ [ 02.10 13:47 → 22:29 · авто · 6440 адр · 30% pass ▾ ] ▶ │
│ (идущий запуск виден в списке, но недоступен: «идёт, 120 из 800») │
├──────────────────────────────────────────────────────────────────────────┤
│ [Адресов 6440] [pass 1962 · 30%] [partial 4478 · 70%] [fail 0] │
│ [Egress OK 31%] [Ingress OK 97%] [Длительность 8 ч 42 м] [Поздних 246] │
├──────────────────────────────────┬───────────────────────────────────────┤
│ Почему partial │ Данные: качество │
│ ▇▇▇▇▇▇▇▇▇▇▇▇ только egress 4146 │ Поздние результаты 246 (50 адр.)│
│ ▇ ingress+egress 157 │ Неполный набор 167 адресов │
│ ▇ egress+неполный набор 125 │ Вердикт ≠ проверки 50 адресов │
│ ▏ только ingress 8 │ │
├──────────────────────────────────┴───────────────────────────────────────┤
│ Подсети [сорт: хуже всего ▾] [показать все 43] │
│ подсеть адр. pass% Egress OK% Ingress OK% │
│ 161.104.108.0/23 45 ░░░░░ 0% ░░░░░ 0% ▇▇▇▇▇ 100% │
│ 83.166.248.0/21 659 ▇░░░░ 11% ▇░░░░ 12% ▇▇▇▇░ 91% →реестр│
├──────────────────────────────────────────────────────────────────────────┤
│ Egress по целям тип: [https] [icmp] [все] │
│ цель провал | подсеть × цель (тепловая карта) │
│ packages.ubuntu.com 60% | 83.166.248.0/21 ■■□■■ │
│ dl-cdn.alpinelinux.org 34% | 212.233.72.0/21 ■■□■■ │
├──────────────────────────────────────────────────────────────────────────┤
│ Ingress по площадкам площадка × тип (tcp/ssh/icmp) | классы ошибок │
├──────────────────────────────────────────────────────────────────────────┤
│ Валидаторы: полоса «доля провалов egress» по 20 валидаторам (≈33–38%) │
└──────────────────────────────────────────────────────────────────────────┘
```
- Два индикатора прикладного уровня (Egress https и Ingress ssh: все провалены) кликабельны: клик открывает окно со списком адресов и кнопкой «Скачать CSV». В рабочей странице список берётся из `GET /admin/analytics/runs/{id}/addresses?list=egress_https_all_failed|ingress_ssh_all_failed` (CSV собирается на сервере, `Content-Disposition: attachment`, UTF-8 с BOM), чтобы не зависеть от размера страницы.
- Каждая строка блока «Классы ошибок ingress» кликабельна: окно показывает распределение проваленных проверок класса по валидаторам, вердикт адреса, статус проверки («в вердикте» или «после вердикта») и список с кнопкой «Скачать CSV». Эндпоинт: `GET /admin/analytics/runs/{id}/errors/{class}` (JSON и `?format=csv`).
- Выбор запуска хранится в адресной строке (`?run=`), ссылку можно переслать. По умолчанию — последний завершённый запуск.
- Каждый блок рассчитан только на выбранный запуск. Данных других запусков на странице нет.
- Строка подсети, ячейка матрицы и причина `partial` — ссылки в «Реестр» с фильтром, чтобы увидеть конкретные адреса. Для этого в `GET /admin/registry` и `/registry` добавляются параметры `run`, `subnet`, `target`, `reason`; страница «Реестр» тоже учитывает выбранный запуск.
- Рисование: HTML и встроенный SVG на сервере, без библиотек графиков (в дашборде их нет). Цвета и тепловая карта — по руководству навыка `dataviz`, с тёмной темой, как в остальном дашборде.
- Пункт «Аналитика» в боковом меню (`internal/dashboard/templates/layout.html`).
## 6. Файлы
- БД: `internal/db/migrations/0010_check_runs.sql`, `models.go`, `queries_runs.go`, правки `queries_ipqueue.go` (`SubmitIPs`, `FinishIP`, `CancelIP`, `ClearAllIPs`) и `queries_checks.go` (`UpsertCheck` ставит `run_id`).
- Оркестратор: `internal/orchestrator/orchestrator.go` — запись `run_results` при агрегации, завершение запуска; автоцикл открывает запуск.
- Аналитика: новый пакет `internal/analytics` (расчёт блоков, классификатор ошибок, подсети).
- API: `internal/httpapi/handlers_analytics.go`, `routes.go`, `docs/API.md`.
- Дашборд: `internal/dashboard/handlers_analytics.go`, `templates/analytics.html`, `static/dashboard.css`, пункт меню, форма списка подсетей на `/settings`.
## 7. Поставка тремя шагами
1. **Запуски**: миграция, правила, заполнение накопленных данных. Результат: в БД один запуск на 6440 адресов. Видимых изменений в UI нет.
2. **API аналитики**: расчёт и эндпоинты.
3. **Страница**: макет выше, фильтры в реестре.
Каждый шаг отдельно собирается, тестируется и выкатывается на стенд.
## 8. Проверка
- Контрольные числа: расчёт по запуску на копии боевой БД (`rxprod-compose/capi-db`) должен дать цифры отчётов `analysis/`: 6440 адресов, 1962 `pass` / 4478 `partial`; провал egress у 4437; провал HTTPS по `packages.ubuntu.com` у 3841; 290 адресов провалили все цели; ingress провален у 224; 50 `pass` с поздними провалами; 167 неполных наборов. Если цифры не сходятся, расчёт неверен.
- Миграция на копии боевой БД: ровно один запуск, `run_id` заполнен у всех `checks` и `ip_queue`, повторный запуск миграции безопасен.
- Тесты: изоляция запусков (два запуска с разными результатами у одного адреса не пересекаются); перепроверка после завершения открывает новый запуск, внутри открытого — нет; поздний результат попадает в свой запуск; идущий запуск не выбирается; новый тип проверки (`dns`) появляется в таблицах; подсети (самый длинный префикс, адрес вне списка → «прочие»).
- Нагрузка: страница на запуске 6440 адресов, расчёт до ~1 с, повторный запрос из кэша быстрее; `EXPLAIN QUERY PLAN` по `idx_checks_run`.
- Вручную на стенде: выбрать запуск, сверить ключевые числа с отчётами, пройти по ссылке из подсети в реестр.
## 9. Допущения и ограничения
- **Валидатор у ingress-проверок.** В `checks.validator_id` для проверок пробера пусто. Валидатор адреса в цикле берётся из события `fip_associated` (поле `validator_id` в payload) по `registry_id` и `cycle_id`. На запуске 02.10 все 950 проваленных ingress-проверок нашли валидатора. Надёжнее писать `validator_id` в ingress-проверку при приёме результата; это отдельное маленькое изменение шага 1.
- **Классификатор ошибок** работает по `checks.detail` и типу проверки; на запуске 02.10 он даёт 7 классов с суммой 950 (SSH: таймаут 327, ICMP: нет ответа 276, TCP-22: таймаут 260, SSH: баннер «Not allowed» 47, SSH: нет маршрута 18, ICMP: time exceeded 15, TCP-22: нет маршрута 7). Новые тексты ошибок попадают в класс «прочее» и видны в списке.
- **Новое наблюдение для блока «Качество данных».** 844 из 950 проваленных ingress-проверок (89%) записаны после агрегации вердикта (среди всех ingress-проверок после вердикта пришло 1430 из 76644, около 1,9%). То есть таймауты приходят позже вердикта систематически, а не случайно. В отчёте анализа 6.5 это видно только для 246 провалов у адресов `pass`.
- Список подсетей даёт администратор (форма на `/settings`). Пока он пуст, подсети группируются автоматически по `/24`.
- Порог 60 минут для заполнения накопленных данных — только для старых записей, настройкой не делается.
- Глубина истории (`history_retention_cycles` > 0) удаляет старые проверки, и аналитика старых запусков теряет детали. По умолчанию 0 (хранить всё). Вердикты в `run_results` остаются.
- Перед выкладкой миграции обязательна копия `rxprod-compose/capi-db/control-api.db`.
- Вне объёма (следующий этап): сравнение двух запусков, выгрузка в CSV, оповещения.
- Вердикт системы и фактические проверки считаются отдельно и показаны рядом (по анализу 6.5 они расходятся у 50 адресов), чтобы поздние результаты не скрывались.
## 10. Порядок по правилам проекта
План (этот файл) → реализация по шагам из п.7 → `…-summary.md` на каждый шаг → обновление `README.md` и `docs/` → обновление графа.
## 11. Уточнения при реализации (макет утверждён)
- Реализуется страница, точно повторяющая макет `docs/mockups/analytics-mockup.html`; отличия только там, где макет был заглушкой (данные, ссылки) или где этого требует рабочее приложение: время в подписях запусков — локальное время дашборда (как везде в нём), матрица «подсеть × цель» строится для любого типа проверки (в макете для `icmp` её не было), надпись про перепроверки показывается, только если они были.
- Решения по открытым вопросам плана приняты по умолчанию: перепроверка после завершения запуска открывает новый запуск; список подсетей хранится в БД (`subnets`) и задаётся на `/settings`, пока пуст — группы по /24.
- Миграция `0011` (запуски, `run_results`, `subnets`, `run_id` у `ip_queue` и `checks`, заполнение накопленных данных, валидатор у ingress-проверок). Раньше плана «Аналитика» выполнена миграция `0010` (вердикт без опоздавших результатов).
- Ссылки из подсетей ведут в «Реестр» с фильтрами `run` и `subnet` (добавлены в `GET /admin/registry` и `/registry`).
- **Сайдбар** (новое требование): убраны три точки возле логотипа; индикатор связи с control-api, переключатель темы и кнопка выхода подняты наверх в блок сессии под логотипом; индикатор краснеет («нет связи»), когда control-api недоступен; разделы разбиты на группы «Мониторинг» (Обзор, Очередь IP, Реестр, Аналитика) и «Настройка» (Валидаторы, Площадки, Цели, Типы проверок, Настройки); нижний блок сайдбара убран.