Analytics: compare two finished runs
New page /analytics/compare and API GET /admin/analytics/compare (+ /lists/{group}):
the administrator picks an old (A) and a new (B) run; the report shows the new
addresses (only in B), the ones that left (only in A) and the common ones whose
membership in the seven indicators (pass, partial, fail, egress https any/all,
ingress ssh any/all) differs, with a "what changed" summary per address; the
dynamics of each indicator (delta = new - left + entered - exited) and a verdict
transition matrix. Every number opens a list with CSV. Cancelled addresses are not
part of a run. The list dialog moved to a shared analytics-dialog.js and template;
/analytics got a "compare with another run" button.
Docs, plan and summary in docs/changes/.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f038f8362
commit
068c10ea1c
28 files changed
+2057
-138
No files matched your search
+27
@@ -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_<group>[_<indicator>][_<from>-<to>]_run<A>-<B>.csv`.
|
||||
|
||||
### `GET /api/v1/admin/config/subnets`, `PUT /api/v1/admin/config/subnets`
|
||||
|
||||
Список подсетей, по которым группируются адреса на странице «Аналитика». `PUT` заменяет список целиком:
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml
|
||||
| `/ips/{ip}` | Детали одного адреса, пока он в очереди: все проверки текущей попытки и вся история событий, плюс ссылка на полную историю в реестре (см. ниже). |
|
||||
| `/registry` | **Реестр** — все адреса, когда-либо поставленные на проверку, независимо от того, стоят ли они сейчас в очереди. Переживает удаление адреса из `/ips` и повторное добавление того же адреса позже (см. «Реестр адресов» ниже). Поиск по IP и фильтр по статусу — то же самое, что на `/overview`, плюс отражается в адресной строке (`?q=&status=`), так что отфильтрованную ссылку можно сохранить/переслать. В колонке «Последний результат» под вердиктом — уровни **Egress** и **Ingress** в виде «N из M» с разбивкой по типам проверок (`https`, `icmp`, `ssh`, `tcp`, `tls`…), см. [USAGE.md](USAGE.md#реестр-адресов-и-глубина-истории). |
|
||||
| `/registry/{ip}` | Полная сохранённая история проверок одного адреса по всем циклам (не только текущему) — в отличие от `/ips/{ip}`, которая показывает только текущую попытку. |
|
||||
| `/analytics` | **Аналитика** одного завершённого запуска (`?run=ID`, по умолчанию последний): выбор запуска (идущий виден, но недоступен), показатели, причины `partial`, качество данных, подсети, egress по целям (по типу проверки, тепловая карта «подсеть × цель»), ingress по площадкам, классы ошибок, валидаторы. Карточки `pass`/`partial`/`fail`, карточки провалов `https`/`ssh` и классы ошибок открывают список адресов с выгрузкой в CSV. Подробности — [USAGE.md](USAGE.md#аналитика-запусков). |
|
||||
| `/analytics` | **Аналитика** одного завершённого запуска (`?run=ID`, по умолчанию последний): выбор запуска (идущий виден, но недоступен), показатели, причины `partial`, качество данных, подсети, egress по целям (по типу проверки, тепловая карта «подсеть × цель»), ingress по площадкам, классы ошибок, валидаторы. Карточки `pass`/`partial`/`fail`, карточки провалов `https`/`ssh` и классы ошибок открывают список адресов с выгрузкой в CSV. Кнопка «Сравнить с другим запуском» ведёт на `/analytics/compare?base=A&target=B`: сравнение двух запусков (новые, выбывшие и изменившиеся адреса, динамика по семи индикаторам, матрица переходов вердикта; числа открывают списки с CSV). Подробности — [USAGE.md](USAGE.md#аналитика-запусков). |
|
||||
| `/validators` | Список валидаторов + создание/изменение `os_port_id`/удаление. |
|
||||
| `/sites` | Площадки — число слотов не ограничено, форма сверху добавляет новый слот, назначить/сменить/освободить `site_id` в каждой строке; колонка «Статус» показывает бейдж подключения пробера (`unregistered`/`idle`/`unreachable`, по аналогии с `/validators`), см. [USAGE.md](USAGE.md#состояния-площадки). |
|
||||
| `/targets` | Группы целей для egress-проверок — создание/редактирование/удаление. |
|
||||
|
||||
@@ -446,6 +446,17 @@ curl -s http://<control-api>:8080/api/v1/admin/registry/203.0.113.10 | python3 -
|
||||
UTF-8 с BOM, открывается в Excel) и «Копировать». Строка подсети и строка матрицы «подсеть × цель» ведут в «Реестр»
|
||||
с фильтром по запуску и подсети (`/registry?run=…&subnet=…`).
|
||||
|
||||
**Сравнение запусков.** Страница `/analytics/compare` (кнопка «Сравнить с другим запуском» на `/analytics`) показывает
|
||||
динамику между двумя завершёнными запусками: A (старый) и B (новый) выбираются из списков, «Поменять местами» меняет
|
||||
их роли; по умолчанию B — последний запуск, A — предыдущий; выбор хранится в адресе страницы. Страница выделяет три
|
||||
группы адресов: **новые** (есть в B, не было в A: пришли в проект), **выбывшие** (были в A, в B нет) и **изменившиеся**
|
||||
(есть в обоих, но принадлежность к индикаторам разная; в списке столбец «Что изменилось»). Таблица «Динамика по
|
||||
индикаторам» даёт по каждому из семи индикаторов (`pass`, `partial`, `fail`, «Egress https» и «Ingress ssh» — «есть
|
||||
провалы» и «все провалены») значения в A и B, разность и её разложение: новые, выбывшие, вошли, вышли. «Переходы
|
||||
вердикта» — матрица 3×3 по общим адресам. Все числа открывают список адресов с выгрузкой в CSV. Адреса с итогом
|
||||
`cancelled` в сравнение не входят. Если запуски разного состава (например, перепроверка части адресов), новых и
|
||||
выбывших будет много: это состав запусков, а не динамика. API — [API.md](API.md#get-apiv1adminanalyticscomparebasea-targetb).
|
||||
|
||||
**Подсети.** Список задаётся на `/settings` (блок «Подсети»): по одной в строке, CIDR и, через пробел, подпись. Адрес
|
||||
относится к самой узкой подходящей подсети, остальные идут в строку «прочие». Пока список пуст, адреса
|
||||
группируются по /24. То же через API: `PUT /api/v1/admin/config/subnets`.
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# План: сравнение двух запусков в разделе «Аналитика»
|
||||
|
||||
Статус: реализовано, см. [итог](2026-10-04_10-08_analytics-run-compare-summary.md).
|
||||
|
||||
## 1. Что нужно
|
||||
|
||||
Администратор выбирает два завершённых запуска и видит динамику между ними по семи индикаторам страницы «Аналитика»: `pass`, `partial`, `fail`, «Egress https: есть провалы», «Egress https: все провалены», «Ingress ssh: есть провалы», «Ingress ssh: все провалены». Отдельно выделяются три группы адресов:
|
||||
|
||||
1. **Новые**: есть в новом запуске, в старом не было. Нужны, чтобы понять, какие адреса прибыли в проект и подключились к анализу.
|
||||
2. **Изменившиеся**: есть в обоих запусках, но состояние по проверкам разное. Для них показывается подробная сводка, что именно изменилось.
|
||||
3. **Выбывшие**: были в старом запуске, в новом их нет. Нужны, чтобы понять, какие адреса вышли из состава проекта.
|
||||
|
||||
Адреса, которые есть в обоих запусках и не изменились, только считаются (список доступен, но не выделяется).
|
||||
|
||||
## 2. Основные решения
|
||||
|
||||
- **Отдельная страница `/analytics/compare`**, а не режим текущей: страница одного запуска остаётся как есть («данные других запусков на странице не участвуют»). На `/analytics` добавляется кнопка «Сравнить с другим запуском», она ведёт на сравнение с текущим запуском в роли нового.
|
||||
- **Выбор запусков.** Два списка: «Запуск A (старый)» и «Запуск B (новый)» и кнопка «Поменять местами». По умолчанию B — последний завершённый запуск, A — предыдущий. Порядок не навязывается: «новые» всегда означает «есть в B, нет в A». Доступны только завершённые запуски с адресами (как в текущем выборе). Одинаковые A и B — ошибка `400`.
|
||||
- **Адрес — это IP.** В запуске он присутствует, если у него есть итог, отличный от `cancelled`. Отменённые адреса из сравнения исключаются (в обоих запусках), их число показывается примечанием, чтобы они не выглядели как «новые» или «выбывшие».
|
||||
- **Состояние адреса** — принадлежность к семи индикаторам по последнему циклу адреса в запуске (те же правила, что в отчёте запуска; один адрес может входить в несколько, вердикт — ровно в один из трёх). Адрес **изменился**, если принадлежность хотя бы к одному индикатору в A и B разная. Адрес с теми же индикаторами, но другим набором проваленных целей или площадок считается неизменившимся (см. риски).
|
||||
- **Расчёт на control-api** по двум уже существующим `Analysis` (кэш завершённых запусков переиспользуется). Новых таблиц, миграций и новых данных нет.
|
||||
|
||||
## 3. Решение
|
||||
|
||||
### 3.1. control-api (`internal/analytics/compare.go`, новый)
|
||||
|
||||
- Таблица семи индикаторов (ключ, название, предикат по адресу): ключи совпадают с видами списков `verdict_pass`, `verdict_partial`, `verdict_fail`, `egress_https_any`, `egress_https_all`, `ingress_ssh_any`, `ingress_ssh_all`. Предикаты повторяют условия `Compute`/`List`; тест проверяет, что число адресов по каждому индикатору в каждом запуске равно соответствующему полю `summary`.
|
||||
- `Compare(a, b *Analysis) *Comparison`:
|
||||
- группы: `new`, `left`, `common`, `changed`, `same` (числа; `common = changed + same`);
|
||||
- по каждому индикатору: `base`, `target`, `delta`, `new` (новые адреса в индикаторе), `left` (выбывшие, были в индикаторе), `entered` (общие адреса, вошедшие в индикатор), `exited` (общие адреса, вышедшие из индикатора). Инвариант: `delta = new − left + entered − exited` (проверяется тестом);
|
||||
- матрица переходов вердикта для общих адресов (3×3), плюс строка «нет в A» (новые) и столбец «нет в B» (выбывшие);
|
||||
- число отменённых адресов в каждом запуске.
|
||||
- `(*Comparison) List(group, filter)`: таблица адресов. Группы: `new`, `left`, `changed`, `same`, `entered`, `exited`. Фильтры: `indicator` (для `new`/`left` — адрес входит в индикатор в своём запуске; для `changed`/`same` — принадлежность этому индикатору; для `entered`/`exited` обязателен), `from`+`to` (вердикт в A и в B, для ячейки матрицы). Порядок — по числовому адресу. Неверная группа или индикатор — ошибка (`404`).
|
||||
- Столбцы списков:
|
||||
- `new`: Адрес, Подсеть, Вердикт, Egress, Ingress, Индикаторы (в B);
|
||||
- `left`: те же столбцы по запуску A;
|
||||
- `changed`, `entered`, `exited`, `same`: Адрес, Подсеть, Вердикт (A → B), Egress (A → B), Ingress (A → B), **Что изменилось**. «Что изменилось» — текст по шагам: «вердикт partial → pass», «вошёл в: Egress https: все провалены», «вышел из: Ingress ssh: есть провалы», «https: провалены цели +a.test −b.test», «ssh: площадки −rxmsk», «валидатор v3 → v12». У `same` — «без изменений».
|
||||
- Для текста нужны из `addr` уже имеющиеся поля (`https.failedTargets`, `ssh.sites`, `https.validator`, статистики egress/ingress); новых вычислений в `Compute` нет.
|
||||
|
||||
### 3.2. API (`internal/httpapi/handlers_analytics.go`)
|
||||
|
||||
- `GET /api/v1/admin/analytics/compare?base=A&target=B` → отчёт сравнения (`runs`: сведения об обоих запусках, `groups`, `indicators`, `transitions`, `cancelled`).
|
||||
- `GET /api/v1/admin/analytics/compare/lists/{group}?base=A&target=B[&indicator=…][&from=…&to=…][&format=csv]` → таблица или CSV (UTF-8 с BOM; имя `compare_<group>[_<indicator>]_run<A>-<B>.csv`).
|
||||
- Ошибки: `400` (нет или неверные id, `base = target`), `404` (запуска нет, неизвестная группа/индикатор), `409` (запуск ещё идёт).
|
||||
- Загрузка `Analysis` по id выносится из `analysisFor` в общую функцию, чтобы сравнение и страница одного запуска пользовались одним кэшем.
|
||||
|
||||
### 3.3. Дашборд
|
||||
|
||||
- Страница `/analytics/compare?base=A&target=B` (`handlers_analytics.go`, `templates/analytics_compare.html`, `static/analytics-compare.js`), прокси списков и CSV: `/analytics/compare/lists/{group}` и `/analytics/compare/csv/{group}`. Состояние страницы целиком в адресе (можно отправить ссылку).
|
||||
- Содержимое страницы сверху вниз:
|
||||
1. Выбор A и B, «Поменять местами», «Сравнить»; под ним примечание: даты, тип и размер обоих запусков, число отменённых.
|
||||
2. Карточки: «Новые», «Выбывшие», «Общие», «Изменились», «Без изменений». Кликабельны, открывают диалог со списком.
|
||||
3. «Динамика по индикаторам»: строка на индикатор, столбцы A, B, Δ (рост `pass` и падение остальных — зелёным, обратное — красным), Новые, Выбывшие, Вошли, Вышли. Ненулевые числа кликабельны (список с соответствующими `group` и `indicator`).
|
||||
4. «Переходы вердикта»: матрица 3×3 по общим адресам + «нет в A» / «нет в B»; ячейки кликабельны.
|
||||
5. Диалог со списком, «Скачать CSV», «Копировать» — тот же, что на странице запуска.
|
||||
- Код диалога (открытие, заполнение, CSV, копирование, подсказки) выносится из `analytics.js` в общий `analytics-dialog.js`, разметка диалога — в общий шаблон; страница одного запуска переходит на него без изменения поведения.
|
||||
- На `/analytics` добавляется кнопка «Сравнить с другим запуском».
|
||||
|
||||
## 4. Файлы
|
||||
|
||||
- `internal/analytics/compare.go` (новый), `internal/analytics/compare_test.go` (новый).
|
||||
- `internal/httpapi/handlers_analytics.go` (общий загрузчик, два новых обработчика), `routes`.
|
||||
- `internal/dashboard`: `handlers_analytics.go` (страница и прокси), `client.go`, `routes.go`, `templates/analytics_compare.html`, `templates/analytics.html` (кнопка, общий диалог), `static/analytics.js`, `static/analytics-dialog.js` (новый), `static/analytics-compare.js` (новый), `static/analytics.css`.
|
||||
- Документы: `docs/API.md`, `docs/USAGE.md` («Аналитика запусков»), `docs/DASHBOARD.md`, `README.md`, итог `docs/changes/…-summary.md`.
|
||||
|
||||
## 5. Тесты
|
||||
|
||||
- `analytics`: два запуска с новыми, выбывшими, изменившимися и неизменившимися адресами, с отменёнными; числа групп; инвариант `delta = new − left + entered − exited` по каждому индикатору; число адресов по индикатору равно `summary` каждого запуска; матрица переходов; списки каждой группы и фильтры (`indicator`, `from`/`to`); текст «Что изменилось» (вердикт, вход/выход из индикатора, цели, площадки, валидатор); порядок по адресу; пустые группы.
|
||||
- `httpapi`: отчёт и списки (JSON, CSV с именем файла), `400` на одинаковые и неверные id, `404` на неизвестные запуск/группу/индикатор, `409` на незавершённый запуск.
|
||||
- `dashboard`: страница с запуском по умолчанию, с явным `base`/`target`, предупреждение на неверные id, прокси списка и CSV; страница одного запуска после выноса диалога по-прежнему отдаёт данные и скрипты.
|
||||
- `gofmt -l`, `go build ./... && go vet ./... && go test ./...`, проверка синтаксиса JS.
|
||||
|
||||
## 6. Выкладка и проверка
|
||||
|
||||
Меняются `control-api` и `admin-dashboard` (статика встроена); агенты, prober, миграции без изменений. Порядок: проверка пустой очереди, тег отката образов, пересборка, перезапуск. Проверка на стенде: сравнение запусков 1 и 2 через API (сумма групп равна числу адресов, инвариант по индикаторам), затем страница в браузере: выбор запусков, обмен местами, открытие диалогов из карточек, таблицы и матрицы, CSV, а также что страница одного запуска работает как раньше.
|
||||
|
||||
## 7. Риски
|
||||
|
||||
- **Что считается изменением.** Только принадлежность к семи индикаторам. Адрес, который остался в «Egress https: есть провалы», но у которого поменялись проваленные цели, попадает в «без изменений»; в списке `same` его сводка «без изменений» это не показывает. Если такие случаи важны, следующим шагом можно считать изменением и смену наборов целей и площадок.
|
||||
- **Размер списков.** Группа новых или `same` может содержать тысячи строк (диалог показывает все, как и другие списки); CSV полный.
|
||||
- **Разный состав запусков.** Если запуск 1 был частичным (например, перепроверка), то в сравнении будет много «новых» и «выбывших»: это отражает состав запусков, а не динамику. Размеры обоих запусков показаны в примечании над таблицами.
|
||||
- Вердикт — оценка системы при агрегации, а не итог всех проверок (см. «Качество данных»); сравнение вердикта это не меняет, столбцы Egress и Ingress показывают фактические проверки.
|
||||
- Вынос кода диалога затрагивает работающую страницу; закрывается тестами и ручной проверкой п. 6.
|
||||
|
||||
## 8. Не входит в доработку
|
||||
|
||||
Сравнение более чем двух запусков, графики динамики по времени, разбивка новых и выбывших по подсетям, сравнение по другим показателям (классы ошибок, площадки, валидаторы), автоматический выбор «интересных» пар запусков.
|
||||
|
||||
Открытых вопросов нет. Жду команды начать реализацию.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Итог: сравнение двух запусков в разделе «Аналитика»
|
||||
|
||||
План: [2026-10-04_10-08_analytics-run-compare-plan.md](2026-10-04_10-08_analytics-run-compare-plan.md).
|
||||
Статус: код написан и проверен (gofmt, build, vet, test, синтаксис JS); стенд **не пересобирался**.
|
||||
|
||||
## Что изменено
|
||||
|
||||
- **control-api** (`internal/analytics/compare.go`): таблица семи индикаторов (ключи совпадают с видами списков), `Compare(base, target)` — группы `new`, `left`, `common`, `changed`, `same`, разложение каждого индикатора (`delta = new − left + entered − exited`), матрица переходов вердикта, число отменённых. `List(group, filter)` с фильтрами `indicator`, `from`/`to` и столбцом «Что изменилось».
|
||||
- **API**: `GET /admin/analytics/compare` и `GET /admin/analytics/compare/lists/{group}` (JSON и CSV); `400` — неверные или одинаковые id, `404` — нет запуска, группы, индикатора или фильтра, `409` — запуск идёт. Загрузка отчёта запуска вынесена в общую функцию, кэш общий.
|
||||
- **Дашборд**: страница `/analytics/compare` (выбор A и B, «Поменять местами», карточки групп, «Динамика по индикаторам», «Переходы вердикта», диалог со списком и CSV); прокси списков и CSV; кнопка «Сравнить с другим запуском» на `/analytics`.
|
||||
- **Общий диалог**: код списков вынесен в `static/analytics-dialog.js`, разметка — в шаблон `analytics_dialog.html`; страница одного запуска перешла на него.
|
||||
- **Документы**: `API.md`, `USAGE.md`, `DASHBOARD.md`, `README.md`.
|
||||
|
||||
## Отклонения от плана
|
||||
|
||||
1. Добавлена группа `common` (карточка «Общие» и ячейки матрицы открывают список через неё).
|
||||
2. Фильтр `indicator` для `changed`/`same`/`common` — «хотя бы в одном из запусков».
|
||||
3. Неверные `from`/`to` дают `404`, как другие неверные виды списков; к имени CSV добавляется суффикс `_<from>-<to>`.
|
||||
4. Разметка диалога вынесена в отдельный шаблон.
|
||||
|
||||
## Особенности
|
||||
|
||||
- Адрес, оставшийся в тех же индикаторах, но с другим набором проваленных целей или площадок, считается неизменившимся (`same`).
|
||||
- Дубли IP внутри запуска не обрабатываются (в реестре IP уникален).
|
||||
|
||||
## Проверки
|
||||
|
||||
- `gofmt -l` пусто; `go build ./...`, `go vet ./...`, `go test ./...` — все пакеты `ok`; `node --check` для трёх JS-файлов.
|
||||
- Исправлено при ревью: в тесте дашборда число с неразрывным пробелом (`6 440`); в `TestRouteTableIsClassified` число admin-маршрутов 39 → 41.
|
||||
- Новые тесты: инварианты сравнения (числа индикаторов равны `summary`, разложение `delta`, сумма групп и матрицы), все группы и фильтры, тексты «Что изменилось», API (отчёт, списки, CSV, коды ошибок), дашборд (пара по умолчанию, предупреждения, прокси, страница одного запуска после выноса диалога).
|
||||
- Не проверено: страницы в браузере (диалог на `/analytics`, «Поменять местами», клики по матрице) и сравнение на данных стенда.
|
||||
|
||||
## Выкладка
|
||||
|
||||
Не выполнена. Нужны пересборка и перезапуск `control-api` и `admin-dashboard`; миграций нет.
|
||||
Reference in new issue
Block a user