Files
cloud-ip-validator/docs/changes/2026-10-03_16-24_registry-egress-ingress-levels-plan.md
T

82 lines
8.9 KiB
Markdown
Raw Normal View History

# План: уровни Egress / Ingress в таблице «Реестр»
## Контекст
Колонка «Последний результат» в «Реестре» показывает одну пилюлю общего вердикта (pass / partial / fail / cancelled). Для аналитики этого мало: не видно, где именно сбой — на выходе (egress) или на входе (ingress) — и какой тип проверки упал.
Нужно разделить результат на два уровня и показать «успешно из всего» (например, «5 из 5») по каждому уровню, с разбивкой по типу проверки (icmp, ssh, tcp, https, tls и новые типы, когда появятся).
Решения пользователя:
- Пилюля общего вердикта остаётся, уровни добавляются под ней. Фильтр по вердикту не меняется.
- «Всего» = число записанных проверок последнего цикла (не ожидаемое по настройкам).
## Что уже есть (по графу и коду)
- Проверки лежат в `checks` (`internal/db/migrations/0007_ip_registry.sql:39-65`). Уровень задаёт `source`: `egress` или `inbound-site-N` (`internal/db/models.go:39,68-72`). Тип — в `check_type`: `https`, `icmp`, `ssh`, `tcp-<порт>`, `tls-<порт>`.
- Индекс `idx_checks_registry_cycle(registry_id, cycle_id)` уже есть. Миграция не нужна.
- Цепочка данных: `fillRegistrySummary` (`internal/db/queries_registry.go:199`) → `registrySummaryToDTO` (`internal/httpapi/handlers_registry.go:75`) → `registryDTO` (`internal/httpapi/dto_admin.go:87`) → клиент дашборда → `registryItem` (`internal/dashboard/dto.go:258`) → шаблон `internal/dashboard/templates/registry.html` (`registry_table`, строки 69–101).
- Дашборд ходит в БД только через HTTP control-api. Менять нужно оба слоя.
## Правила расчёта
- Цикл: максимальный `cycle_id` в `checks` для адреса (так же, как сейчас считается `LastCheckedAt`).
- Уровень: `source = 'egress'` → Egress; `source LIKE 'inbound-site-%'` → Ingress. Прочее игнорируется.
- Тип: часть `check_type` до первого `-` (`tcp-22`, `tcp-443` → `tcp`; `tls-443` → `tls`; `ssh`, `icmp`, `https` без изменений). Новый тип попадает в разбивку сам, без правок кода.
- Ingress: считаются проверки всех площадок вместе (площадок может быть любое число).
- Правило разбора живёт в одном месте: две функции в `internal/db/models.go` рядом с `InboundSource`: `CheckLevel(source)` и `CheckFamily(checkType)`.
- Группировка делается в Go, не в SQL: SQL отдаёт `GROUP BY registry_id, source, check_type`, и это не больше ~30 строк на адрес.
## Изменения
### 1. БД (`internal/db`)
- `models.go`: типы `TypeStat{Type, Total, OK}` и `LevelResult{Total, OK, ByType []TypeStat}` (по типам — в алфавитном порядке). Функции `CheckLevel`, `CheckFamily`.
- `queries_registry.go`: в `RegistrySummary` добавить `LastCycleID`, `Egress`, `Ingress`. Один пакетный запрос на страницу вместо запроса на каждый адрес:
```sql
SELECT c.registry_id, c.source, c.check_type, COUNT(*), SUM(c.success)
FROM checks c
JOIN (SELECT registry_id, MAX(cycle_id) AS cid FROM checks
WHERE registry_id IN (…) GROUP BY registry_id) m
ON m.registry_id = c.registry_id AND m.cid = c.cycle_id
GROUP BY c.registry_id, c.source, c.check_type
```
Новая функция заполняет сводки списком адресов (порциями по 500). Её вызывают `ListRegistryPage`, `ListRegistry` и `GetRegistryByAddress`. Это заодно убирает N+1 для новых полей.
- `lastResultCond` и `fillRegistrySummary` (вердикт) не меняются.
### 2. HTTP API (`internal/httpapi`)
- `dto_admin.go`: в `registryDTO` добавить `last_cycle_id`, `egress`, `ingress` как `{total, ok, by_type: [{type, total, ok}]}`. Изменение только добавляет поля, старые клиенты не ломаются.
- `handlers_registry.go`: `registrySummaryToDTO` заполняет новые поля.
- `docs/API.md` (раздел «Реестр адресов», ~645–668): новые поля и пример JSON.
### 3. Дашборд (`internal/dashboard`)
- `dto.go`: `registryItem` получает те же поля.
- `templates/registry.html`: в ячейке «Последний результат» сверху пилюля вердикта, ниже две строки:
`Egress 5 из 5` и `Ingress 3 из 4`, под каждой чипы по типам: `https 3 из 3`, `icmp 2 из 2`. Цвет: все успешны — зелёный, ни одной — красный, иначе жёлтый. Нет проверок — «—». В `title` ячейки: «по записанным проверкам цикла N; вердикт учитывает недостающие результаты». Сохранить `data-label` для мобильной раскладки.
- `static/dashboard.css` (~417–423): стили строки уровня и чипа по образцу `pill-*`.
- Страница деталей `registry_detail.html` не меняется (вне объёма).
### 4. Тесты
- `internal/db/queries_registry_test.go`: новые тесты — разбивка egress/ingress по типам; `tcp-22` и `tcp-443` сливаются в `tcp`; `tls`, `ssh`; новый тип (например `dns`) появляется в разбивке; адрес без проверок даёт нули; берётся последний цикл; работает после удаления строки очереди; несколько адресов в одном пакетном запросе. Добавить хелпер с ingress-проверками рядом с `finishWithChecks` (`queries_scale_test.go:24`).
- `internal/httpapi/handlers_scale_test.go` (`TestAdminRegistryPaginationFiltersAndCompat`): проверить новые поля в ответе.
- `internal/dashboard/handlers_test.go` (`TestRegistryPageAndDetail`): фикстура с уровнями, проверка вывода «5 из 5» и имён типов.
- `TestScaleSmoke6440` (`queries_scale_test.go:356`): добавить ingress-проверки в засев, лимит 10 с сохраняется.
## Порядок работы (по правилам проекта)
1. Скопировать этот план в `docs/changes/2026-10-03_<ЧЧ-ММ>_registry-egress-ingress-levels-plan.md`.
2. Реализовать п.1 → п.2 → п.3 → п.4.
3. Написать `docs/changes/…-summary.md` (что изменено).
4. Обновить `README.md` и `docs/API.md`/`docs/USAGE.md`, где описан реестр.
5. Обновить граф: `/graphify . --update`.
## Проверка
- `go build ./... && go vet ./... && go test ./...`
- `EXPLAIN QUERY PLAN` пакетного запроса: должен идти по `idx_checks_registry_cycle`, без полного перебора `checks`.
- Локальный стенд (`scripts/run-local-e2e.sh`, `docs/LOCAL_E2E.md`): прогнать цикл, открыть `/registry`, сверить «N из M» по уровням и типам с таблицей на странице деталей `/registry/<ip>`.
- Страница на 6440 адресов отвечает за время порядка прежних 0,12 с (допустим рост в разы, но не секунды).
## Ограничения и риски
- «N из M» считается по записанным проверкам. Если результатов не хватает, вердикт может быть хуже, чем «N из M» (например, все записанные успешны, а вердикт `partial`). Это показано подсказкой.
- Пока идёт цикл, счётчики отражают частично пришедшие проверки текущего цикла. Состояние цикла видно в колонке состояния.
- Фильтра и сортировки по уровням нет: сейчас вне объёма. Если понадобятся для аналитики, это отдельное изменение.