Registry: the "last result" column now also shows, per level (egress,
ingress), how many of the recorded checks of the latest cycle succeeded, split
by check family (tcp-22 and tcp-443 are both "tcp"). One grouped query per
chunk of addresses; new fields last_cycle_id, egress, ingress in
GET /admin/registry; the dashboard renders them under the verdict.
Verdict integrity (migration 0010):
- the prober is handed an address once per site and attempt, not on every
poll, so results are no longer overwritten by later probe rounds;
- UpsertCheckIfOpen refuses writes once the address is aggregating or has its
verdict, or for an older attempt; senders get {"ok":true,"ignored":N} and a
result_dropped event is recorded;
- the checking window counts from checking_started_at, not from assigned_at;
- checks.recorded_at (server clock) and checks.after_verdict (flag for rows
written after the verdict in existing data);
- the verdict rule is a pure function (computeVerdict) and the aggregated
event carries the egress/ingress check counts.
Rebuilt bin/control-api and bin/admin-dashboard to match. Plans and summaries
are in docs/changes; README, API, USAGE, DASHBOARD and DIAGRAMS are updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8.9 KiB
8.9 KiB
План: уровни 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. Один пакетный запрос на страницу вместо запроса на каждый адрес:Новая функция заполняет сводки списком адресов (порциями по 500). Её вызывают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_typeListRegistryPage,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 с сохраняется.
Порядок работы (по правилам проекта)
- Скопировать этот план в
docs/changes/2026-10-03_<ЧЧ-ММ>_registry-egress-ingress-levels-plan.md. - Реализовать п.1 → п.2 → п.3 → п.4.
- Написать
docs/changes/…-summary.md(что изменено). - Обновить
README.mdиdocs/API.md/docs/USAGE.md, где описан реестр. - Обновить граф:
/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). Это показано подсказкой. - Пока идёт цикл, счётчики отражают частично пришедшие проверки текущего цикла. Состояние цикла видно в колонке состояния.
- Фильтра и сортировки по уровням нет: сейчас вне объёма. Если понадобятся для аналитики, это отдельное изменение.