diff --git a/README.md b/README.md
index eba4dee..ff24229 100644
--- a/README.md
+++ b/README.md
@@ -208,6 +208,7 @@ scripts/run-local-e2e.sh # сквозной прог
| Дата | Веха | Документ |
|---|---|---|
+| 2026-10-04 | Аналитика: сравнение двух запусков (`/analytics/compare`): новые, выбывшие и изменившиеся адреса, динамика по семи индикаторам, матрица переходов вердикта; API `analytics/compare` | [план](docs/changes/2026-10-04_10-08_analytics-run-compare-plan.md) · [итог](docs/changes/2026-10-04_10-08_analytics-run-compare-summary.md) |
| 2026-10-04 | Аналитика: карточки `pass`, `partial`, `fail` открывают список адресов с этим вердиктом и выгрузку в CSV (`lists/verdict_*`) | [план](docs/changes/2026-10-04_09-55_analytics-verdict-indicators-plan.md) · [итог](docs/changes/2026-10-04_09-55_analytics-verdict-indicators-summary.md) |
| 2026-10-04 | Повтор после сбоя self-check — на другом валидаторе: валидатор, проваливший self-check, этому адресу больше не выдаётся; потолок провалов `self_check_max_attempts` (по умолчанию 5, миграция `0012`); поле `self_check_failed_on` | [план](docs/changes/2026-10-04_08-01_self-check-exclude-validator-plan.md) · [итог](docs/changes/2026-10-04_08-01_self-check-exclude-validator-summary.md) |
| 2026-10-03 | Раздел «Аналитика»: запуски проверки (миграция `0011`), показатели и списки адресов по запуску, подсети, CSV; сайдбар: связь с control-api и выход наверху, группы разделов; фильтры реестра по запуску и подсети | [план](docs/changes/2026-10-03_16-39_analytics-section-plan.md) · [итог](docs/changes/2026-10-03_18-41_analytics-section-summary.md) · [макет](docs/mockups/analytics-mockup.html) · [USAGE](docs/USAGE.md#аналитика-запусков) · [API](docs/API.md#аналитика-запусков) |
diff --git a/bin/SHA256SUMS b/bin/SHA256SUMS
index e5d4836..00db052 100644
--- a/bin/SHA256SUMS
+++ b/bin/SHA256SUMS
@@ -1,4 +1,4 @@
-43160e29c10730592ba1b25c20b42665d043a7d25b94433b2b89d5fd111a5b36 control-api
+2f4fa432060fb70c744ab5f593d3f12bbf39f5f6587b4fb6a445c60218d25831 control-api
9fb6608b84143f7c4f318f3cc92dcd9f95c7831d627b23d67cce5a5908ced704 validator-agent
3e9e14dbb361ee76aaad7c1da6864b3ea111e0ed151403f904b12485631bbf75 prober
-bb3ca7ba12a93c5a018a5c7532dfeb722a866ddfa27a9521cb24d0464fa3595d admin-dashboard
+76db6c2dedaa89b7ba8253287a8289c7d72034f5371bcc714a55f3ab257f66ff admin-dashboard
diff --git a/bin/admin-dashboard b/bin/admin-dashboard
index b0eb867..65d34e1 100755
Binary files a/bin/admin-dashboard and b/bin/admin-dashboard differ
diff --git a/bin/control-api b/bin/control-api
index 5d67a86..ca3004e 100755
Binary files a/bin/control-api and b/bin/control-api differ
diff --git a/docs/API.md b/docs/API.md
index 1c1e588..2d8186b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -1072,6 +1072,33 @@ curl -s "$BASE/api/v1/admin/ips/203.0.113.10" | python3 -m json.tool
имя вида `ingress_ssh_all_run1.csv`). Для `verdict_*` — адреса запуска с этим вердиктом (без `cancelled`, по числовому порядку; число строк равно `summary.pass`/`partial`/`fail`): адрес, подсеть, валидатор (по https-проверкам, «—», если их нет), `Egress` и `Ingress` («успешно из всех», «—» без проверок), «Проверок в цикле» (записано из ожидаемых) и у `partial` ещё «Причина» (как в блоке `reasons`). Для `error` строка — одна проваленная проверка: адрес, подсеть, площадка,
валидатор, вердикт адреса, статус («провал, в вердикте» или «провал, после вердикта»).
+### `GET /api/v1/admin/analytics/compare?base=A&target=B`
+
+Сравнение двух завершённых запусков: `base` — старый (A), `target` — новый (B). Адрес — это IP; адрес с итогом
+`cancelled` в запуск не входит (их число — в `cancelled`). Ответ:
+
+| Поле | Содержимое |
+|---|---|
+| `runs` | `base` и `target`: сведения о запуске и `addresses` |
+| `groups` | `new` (есть в B, нет в A), `left` (были в A, нет в B), `common` (в обоих), `changed`, `same`; `common = changed + same` |
+| `indicators` | по семи индикаторам (`verdict_pass`, `verdict_partial`, `verdict_fail`, `egress_https_any`, `egress_https_all`, `ingress_ssh_any`, `ingress_ssh_all`): `base`, `target`, `delta`, `new`, `left`, `entered`, `exited`; `delta = new − left + entered − exited` |
+| `transitions` | `verdicts`, `matrix[из][в]` вердиктов общих адресов, `new` и `left` — новые/выбывшие по вердикту |
+| `cancelled` | отменённые адреса в `base` и `target` |
+
+Общий адрес **изменился**, если его принадлежность хотя бы к одному из семи индикаторов в A и B разная (другой набор
+проваленных целей или площадок при тех же индикаторах — не изменение). `400` — нет или неверные `base`/`target` либо
+они совпадают, `404` — запуска нет, `409` — запуск ещё идёт.
+
+### `GET /api/v1/admin/analytics/compare/lists/{group}?base=A&target=B`
+
+Таблица адресов группы `new`, `left`, `common`, `changed`, `same`, `entered` или `exited`. Фильтры: `indicator` (ключ
+индикатора; для `entered` и `exited` обязателен; для `new`/`left` — адрес входит в индикатор в своём запуске, для
+`common`/`changed`/`same` — хотя бы в одном из запусков), `from` и `to` вместе (вердикт в A и в B; только для общих групп).
+Неизвестные группа, индикатор или фильтр — `404`. Столбцы `new` и `left`: адрес, подсеть, вердикт, Egress, Ingress,
+индикаторы. Остальные группы: адрес, подсеть, вердикт, Egress и Ingress в виде `A → B` и «Что изменилось» (вердикт,
+вход в индикаторы и выход из них, добавленные и убранные цели https и площадки ssh, смена валидатора; у группы без
+изменений — «без изменений»). С `?format=csv` — файл CSV (UTF-8 с BOM), имя `compare_
@@ -46,6 +47,7 @@ {{end}} {{if .NextURL}}▶{{else}}▶{{end}} +{{if .HasRun}}Сравнить с другим запуском{{end}} {{if .HasRun}}
{{else}}
Завершённых запусков пока нет: данные появятся, когда все адреса запуска получат итог.
{{end}} @@ -114,23 +116,7 @@
Ровная полоса значит: проблема зависит от подсети адреса, а не от валидатора.
- -
+{{template "analytics_dialog"}} {{end}} {{end}} diff --git a/internal/dashboard/templates/analytics_compare.html b/internal/dashboard/templates/analytics_compare.html new file mode 100644 index 0000000..2ab0b4d --- /dev/null +++ b/internal/dashboard/templates/analytics_compare.html @@ -0,0 +1,75 @@ +{{define "analytics_compare_page"}} + + +
{{template "html_head" .}} + + +
+