diff --git a/bin/SHA256SUMS b/bin/SHA256SUMS
index ce77107..2330100 100644
--- a/bin/SHA256SUMS
+++ b/bin/SHA256SUMS
@@ -1,4 +1,4 @@
-dcb137ae6a3f6a59465f2772215d48f98fd303dd1c266645b20ec70194ab7915 control-api
-b7a6068db1d095ae7b73cbd9b7273d6a1629ae017e9c3c9601e103432894a247 validator-agent
-be8e9576c6fbe5e5798b5d4b758a5617b63bea6abe80e326998d10583edbbf5c prober
-61d1df7aa84528e8fd9b5e700ac6a9b77e45763b5c5d938913dbce25d0ad9940 admin-dashboard
+62fb7e4e6a05f60e0e2f1d113c11b4ef46840eadb87202c3539a508ea0e996ae control-api
+7c11ffe00f09cbfc33bab57d8a889cdfedacc69f0049a5881705dfabc154fa76 validator-agent
+543f7df07939d576b79dc8fdc655ee6ff5577c834d5606a1e99687b99d2a8927 prober
+2e9798e86ebaf92e70622cbd76ca0d3de8f90958eadbe7c71fb2118940b4a275 admin-dashboard
diff --git a/bin/admin-dashboard b/bin/admin-dashboard
index 84f43f2..e125397 100755
Binary files a/bin/admin-dashboard and b/bin/admin-dashboard differ
diff --git a/bin/control-api b/bin/control-api
index ed06e42..ca32640 100755
Binary files a/bin/control-api and b/bin/control-api differ
diff --git a/bin/prober b/bin/prober
index 5293591..8548125 100755
Binary files a/bin/prober and b/bin/prober differ
diff --git a/bin/validator-agent b/bin/validator-agent
index e73751d..8067bff 100755
Binary files a/bin/validator-agent and b/bin/validator-agent differ
diff --git a/cmd/control-api/main.go b/cmd/control-api/main.go
index ac6a3f2..283dab7 100644
--- a/cmd/control-api/main.go
+++ b/cmd/control-api/main.go
@@ -92,6 +92,17 @@ func runOrchestratorLoop(ctx context.Context, orch *orchestrator.Orchestrator, c
heartbeatTicker := time.NewTicker(interval * 2)
defer heartbeatTicker.Stop()
+ // Periodic floating-IP scanning is optional: a zero interval leaves
+ // scanTickerC nil, and a select on a nil channel simply never fires, so
+ // the loop falls back to manual-only scanning (POST
+ // /api/v1/admin/ips/scan) without a special-cased branch below.
+ var scanTickerC <-chan time.Time
+ if cfg.Orchestrator.FIPScanIntervalSeconds > 0 {
+ scanTicker := time.NewTicker(time.Duration(cfg.Orchestrator.FIPScanIntervalSeconds) * time.Second)
+ defer scanTicker.Stop()
+ scanTickerC = scanTicker.C
+ }
+
for {
select {
case <-ctx.Done():
@@ -105,6 +116,10 @@ func runOrchestratorLoop(ctx context.Context, orch *orchestrator.Orchestrator, c
if err := orch.SweepStaleSiteHeartbeats(ctx); err != nil {
log.Error("sweep stale site heartbeats", "err", err)
}
+ case <-scanTickerC:
+ if _, _, err := orch.ScanFloatingIPs(ctx); err != nil {
+ log.Error("scan floating ips", "err", err)
+ }
}
}
}
diff --git a/configs/control-api.example.yaml b/configs/control-api.example.yaml
index d6c6669..f9f2a1f 100644
--- a/configs/control-api.example.yaml
+++ b/configs/control-api.example.yaml
@@ -54,6 +54,12 @@ orchestrator:
# /settings page) and this field is ignored. Must satisfy
# fip_settle_seconds + self_check_timeout_seconds < lease_ttl_seconds.
fip_settle_seconds: 0
+ # How often (seconds) to automatically scan the OpenStack project for free
+ # (unassociated) floating IPs and submit them to the check queue. 0 (the
+ # default) disables periodic scanning — an operator can still trigger a
+ # scan on demand via POST /api/v1/admin/ips/scan or the dashboard's
+ # "Scan Floating IPs" button.
+ fip_scan_interval_seconds: 0
aggregation:
missing_counts_as_fail: true
diff --git a/deploy/docker/control-api/control-api.docker.example.yaml b/deploy/docker/control-api/control-api.docker.example.yaml
index 9f7bf4f..663ee81 100644
--- a/deploy/docker/control-api/control-api.docker.example.yaml
+++ b/deploy/docker/control-api/control-api.docker.example.yaml
@@ -26,6 +26,7 @@ orchestrator:
lease_ttl_seconds: 180
heartbeat_timeout_seconds: 30
fip_settle_seconds: 0
+ fip_scan_interval_seconds: 0
aggregation:
missing_counts_as_fail: true
diff --git a/docs/API.md b/docs/API.md
index 9adc8f3..84fc6f8 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -28,6 +28,7 @@ JSON, базовый префикс прикладных методов — `/ap
- [Методы для prober](#методы-для-prober)
- [Служебные и административные методы](#служебные-и-административные-методы)
- [Управление очередью и конфигурацией](#управление-очередью-и-конфигурацией)
+- [Реестр адресов и история проверок](#реестр-адресов-и-история-проверок)
- [Модель состояний и связь методов с ней](#модель-состояний-и-связь-методов-с-ней)
- [Сквозной пример работы (curl)](#сквозной-пример-работы-curl)
@@ -416,12 +417,18 @@ YAML для этой секции больше не перечитывается
### `DELETE /api/v1/admin/ips/{ip}`, `POST /api/v1/admin/ips/delete`, `POST /api/v1/admin/ips/clear`
-**Безвозвратное удаление**, в отличие от `cancel` выше: строка `ip_queue`
-и вся её история (`checks`, `events`) стираются физически, без возможности
-восстановления. Работает из любого состояния, включая активно
-проверяемое — если Floating IP привязан, он отвязывается тем же
-best-effort способом, что и при `cancel`/обычном завершении, владеющий
-валидатор освобождается.
+Удаляет строку `ip_queue` — адрес пропадает из очереди/`GET
+/api/v1/admin/ips*` — безвозвратно, без возможности восстановить именно
+эту строку. Работает из любого состояния, включая активно проверяемое —
+если Floating IP привязан, он отвязывается тем же best-effort способом,
+что и при `cancel`/обычном завершении, владеющий валидатор освобождается.
+
+**Накопленная история адреса при этом не теряется**: `checks`/`events`
+остаются в реестре (`ip_registry`, см. раздел [«Реестр
+адресов»](#реестр-адресов-и-история-проверок) ниже) и доступны через `GET
+/api/v1/admin/registry/{ip}` даже после удаления строки из очереди — в
+отличие от `ip_queue`, реестровая запись никогда не удаляется этими
+методами.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
@@ -433,7 +440,95 @@ best-effort способом, что и при `cancel`/обычном заве
идут в `not_found`, не ошибка — тот же терпимый стиль, что у `POST
/api/v1/admin/ips`). `POST .../clear` удаляет **вообще всё**, что сейчас в
очереди, включая адреса в процессе проверки — самая опасная операция
-этого API, используйте с осторожностью.
+этого API, используйте с осторожностью (и, опять же, ничья история при
+этом физически не стирается — см. выше).
+
+### `POST /api/v1/admin/ips/scan`
+
+Сканирует текущий проект OpenStack на предмет свободных (не привязанных ни
+к одному порту) Floating IP и сразу передаёт найденный список в `POST
+/api/v1/admin/ips` — тот же add/requeue/reorder-вызов, как если бы
+оператор ввёл эти адреса вручную. Не принимает тело запроса.
+
+Ответ (`200`):
+```json
+{
+ "scanned_free": 3,
+ "added": ["203.0.113.20"],
+ "requeued": [],
+ "reordered": ["203.0.113.10", "203.0.113.11"],
+ "skipped_in_progress": []
+}
+```
+
+`scanned_free` — сколько свободных Floating IP нашлось в проекте всего
+(включая уже стоящие в очереди — они попадут в `reordered`, а не
+`added`). Если свободных адресов нет вообще, это не ошибка: ответ будет
+`{"scanned_free": 0, "added": [], ...}`.
+
+Помимо ручного вызова, сканирование можно включить по расписанию —
+`orchestrator.fip_scan_interval_seconds` в `control-api.yaml` (0, по
+умолчанию, — только по запросу через эту ручку или кнопку «Сканировать
+Floating IP» в дашборде).
+
+## Реестр адресов и история проверок
+
+В отличие от `ip_queue` (текущая рабочая очередь, см. выше), реестр —
+`ip_registry` — это накопительная запись **обо всех адресах, когда-либо
+поставленных на проверку**, вне зависимости от того, стоят ли они сейчас в
+очереди. Запись в реестре переживает удаление адреса из `ip_queue` (`DELETE
+/api/v1/admin/ips/{ip}` и т.п.) и повторное добавление того же адреса
+позже — обе истории (до и после) остаются доступны и не перекрывают друг
+друга (каждой постановке на проверку соответствует свой `cycle_id`,
+уникальный в пределах адреса на всё время).
+
+### `GET /api/v1/admin/registry`
+
+Список всех адресов реестра с краткой сводкой по каждому.
+
+```json
+[
+ {
+ "ip_address": "203.0.113.10",
+ "first_seen_at": "2026-01-10T12:00:00Z",
+ "last_seen_at": "2026-02-01T09:00:00Z",
+ "total_cycles": 4,
+ "last_result": "pass",
+ "last_checked_at": "2026-02-01T09:05:00Z",
+ "in_queue": true,
+ "current_state": "done"
+ }
+]
+```
+
+`in_queue`/`current_state` отражают, есть ли у адреса сейчас живая строка в
+`ip_queue`, а не только в реестре.
+
+### `GET /api/v1/admin/registry/{ip}`
+
+Реестровая запись по одному адресу плюс вся сохранённая история проверок
+по нему, по всем циклам (не только текущему — в отличие от `GET
+/api/v1/admin/ips/{ip}`, который отдаёт проверки только текущей попытки).
+Порядок — от новых циклов к старым.
+
+```json
+{
+ "registry": { "ip_address": "203.0.113.10", "total_cycles": 4, "...": "..." },
+ "checks": [
+ {"CycleID": 4, "Source": "egress", "CheckType": "https", "Success": true, "...": "..."},
+ {"CycleID": 3, "Source": "egress", "CheckType": "https", "Success": false, "...": "..."}
+ ]
+}
+```
+
+`404`, если адрес никогда не ставился на проверку.
+
+**Глубина хранения.** Сколько последних циклов на адрес хранится в
+`checks` (и синхронно — в `events`), управляется полем
+`history_retention_cycles` в `GET`/`PUT /api/v1/admin/config/orchestrator`
+(0, по умолчанию, — без ограничения). Сама реестровая запись (`ip_address`,
+`first_seen_at`, счётчик циклов) не удаляется никогда, независимо от этой
+настройки — она лишь ограничивает глубину детальной истории проверок.
```bash
curl -s -X DELETE "$BASE/api/v1/admin/ips/203.0.113.10"
@@ -497,25 +592,33 @@ curl -s -X POST "$BASE/api/v1/admin/ips/clear"
### Настройки оркестратора: `/api/v1/admin/config/orchestrator`
-Единственный на сегодня параметр — `fip_settle_seconds`: пауза между
-привязкой Floating IP к валидатору и моментом, когда self-check по этому
-адресу становится доступен агенту (`GET
-/api/v1/agents/{id}/assignment` до истечения паузы отдаёт `204`, как если
-бы валидатору просто нечего было делать — никаких изменений в протоколе
-агента). Нужна, чтобы дать data plane OpenStack время реально начать
-пропускать трафик через только что привязанный адрес, прежде чем
-запускать по нему проверки. `0` — без паузы (поведение по умолчанию, как
-до появления этого параметра).
+Два параметра:
+
+- `fip_settle_seconds` — пауза между привязкой Floating IP к валидатору и
+ моментом, когда self-check по этому адресу становится доступен агенту
+ (`GET /api/v1/agents/{id}/assignment` до истечения паузы отдаёт `204`,
+ как если бы валидатору просто нечего было делать — никаких изменений в
+ протоколе агента). Нужна, чтобы дать data plane OpenStack время реально
+ начать пропускать трафик через только что привязанный адрес, прежде чем
+ запускать по нему проверки. `0` — без паузы (поведение по умолчанию, как
+ до появления этого параметра).
+- `history_retention_cycles` — сколько последних циклов проверки хранить
+ на адрес в реестре (`GET /api/v1/admin/registry/{ip}`, см.
+ [«Реестр адресов»](#реестр-адресов-и-история-проверок)). `0` — без
+ ограничения (поведение по умолчанию).
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
-| GET | `/api/v1/admin/config/orchestrator` | — | `{"fip_settle_seconds":N}` | |
-| PUT | `/api/v1/admin/config/orchestrator` | `{"fip_settle_seconds":N}` | `200` | `400`, если `N < 0`, или если `fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения `lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
+| GET | `/api/v1/admin/config/orchestrator` | — | `{"fip_settle_seconds":N,"history_retention_cycles":M}` | |
+| PUT | `/api/v1/admin/config/orchestrator` | `{"fip_settle_seconds":N,"history_retention_cycles":M}` | `200` | `400`, если `N < 0` или `M < 0`, или если `fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения `lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
Как и остальные разделы этой группы, YAML-поле `orchestrator.
fip_settle_seconds` в `control-api.yaml` — только одноразовый bootstrap
для пустой БД; дальше источник истины — сама база, менять значение нужно
через `PUT` выше (или страницу `/settings` в дашборде).
+`history_retention_cycles` не имеет YAML-эквивалента вообще — управляется
+только через `PUT` выше/дашборд, значение по умолчанию `0` всегда
+применяется на пустой БД.
### Типы проверок пробера: `/api/v1/admin/config/inbound-checks`
@@ -633,12 +736,15 @@ queued ──(control-api сам, без вызова API)──▶ assigning_fi
"cancelled"`)** — `POST /api/v1/admin/ips/{ip}/cancel`;
- **`done`/`failed`/`occupied` → `queued` (новая попытка)** — `POST
/api/v1/admin/ips` с уже завершённым (или занятым) адресом в списке;
-- **любое состояние → адрес физически исчезает из очереди**, вместе со
- всей историей — `DELETE /api/v1/admin/ips/{ip}`, `POST
- /api/v1/admin/ips/delete`, `POST /api/v1/admin/ips/clear` (см.
+- **любое состояние → адрес физически исчезает из очереди** — `DELETE
+ /api/v1/admin/ips/{ip}`, `POST /api/v1/admin/ips/delete`, `POST
+ /api/v1/admin/ips/clear` (см.
[выше](#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)).
Не путать с cancel — cancel сохраняет запись как историю (`failed`/
- `cancelled`), delete стирает её целиком без возможности восстановления.
+ `cancelled`) прямо в `ip_queue`; delete убирает саму строку `ip_queue`
+ безвозвратно, но накопленная история проверок остаётся в реестре (`GET
+ /api/v1/admin/registry/{ip}`) — см.
+ [«Реестр адресов»](#реестр-адресов-и-история-проверок).
## Сквозной пример работы (curl)
diff --git a/docs/DASHBOARD.md b/docs/DASHBOARD.md
index 8dc5e71..f41f203 100644
--- a/docs/DASHBOARD.md
+++ b/docs/DASHBOARD.md
@@ -49,13 +49,15 @@ admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml
| Страница | Назначение |
|---|---|
| `/overview` | Сводная статистика: счётчики по состояниям, «текущая проверка» (live-снимок всех IP не в терминальном состоянии) и «последние N завершённых» (по умолчанию 20, `overview.last_completed_count`) с разбивкой pass/partial/fail/cancelled. Обновляется каждые `overview.poll_interval_seconds` секунд без перезагрузки страницы. |
-| `/ips` | Полная очередь. Форма сверху принимает список адресов (по одному на строке или через запятую) и отправляет их в `POST /api/v1/admin/ips` — **один и тот же вызов** добавляет новые адреса и принудительно перезапускает уже завершённые (см. ниже). У каждого адреса — кнопка «Перепроверить» (для `done`/`failed`) или «Отменить» (для активных состояний), и всегда — «Удалить» (безвозвратно, в отличие от «Отменить», см. ниже). Чекбоксы у строк + кнопка «Удалить выбранные» удаляют список одним вызовом; «Очистить всё» удаляет вообще всё, включая активные проверки — обе операции требуют явного подтверждения. Пока не истекла настроенная на `/settings` пауза (`fip_settle_seconds`), только что привязавший Floating IP адрес показывает отдельный бейдж «прогрев FIP» вместо обычного статуса. Если на момент попытки привязки Floating IP оказался уже занят другим портом (дрейф состояния облака или ошибочно переданный адрес), цикл проверки для него не запускается — адрес показывает отдельный бейдж «занят» (отличный от «fail») и строку `fip_occupied` в списке событий на его странице; кнопка «Перепроверить» ставит его в очередь заново. |
-| `/ips/{ip}` | Детали одного адреса: все проверки текущей попытки и вся история событий. |
+| `/ips` | Полная очередь. Форма сверху принимает список адресов (по одному на строке или через запятую) и отправляет их в `POST /api/v1/admin/ips` — **один и тот же вызов** добавляет новые адреса и принудительно перезапускает уже завершённые (см. ниже). Кнопка «Сканировать Floating IP» делает то же самое автоматически: находит в проекте OpenStack все свободные (не привязанные к порту) Floating IP и сразу ставит их в очередь (`POST /api/v1/admin/ips/scan`, см. [API.md](API.md#post-apiv1adminipsscan)) — то же сканирование можно включить по расписанию через `orchestrator.fip_scan_interval_seconds`. У каждого адреса — кнопка «Перепроверить» (для `done`/`failed`) или «Отменить» (для активных состояний), и всегда — «Удалить» (безвозвратно убирает адрес из очереди, но не из реестра — см. ниже). Чекбоксы у строк + кнопка «Удалить выбранные» удаляют список одним вызовом; «Очистить всё» удаляет вообще всё, включая активные проверки — обе операции требуют явного подтверждения. Пока не истекла настроенная на `/settings` пауза (`fip_settle_seconds`), только что привязавший Floating IP адрес показывает отдельный бейдж «прогрев FIP» вместо обычного статуса. Если на момент попытки привязки Floating IP оказался уже занят другим портом (дрейф состояния облака или ошибочно переданный адрес), цикл проверки для него не запускается — адрес показывает отдельный бейдж «занят» (отличный от «fail») и строку `fip_occupied` в списке событий на его странице; кнопка «Перепроверить» ставит его в очередь заново. |
+| `/ips/{ip}` | Детали одного адреса, пока он в очереди: все проверки текущей попытки и вся история событий, плюс ссылка на полную историю в реестре (см. ниже). |
+| `/registry` | **Реестр** — все адреса, когда-либо поставленные на проверку, независимо от того, стоят ли они сейчас в очереди. Переживает удаление адреса из `/ips` и повторное добавление того же адреса позже (см. «Реестр адресов» ниже). |
+| `/registry/{ip}` | Полная сохранённая история проверок одного адреса по всем циклам (не только текущему) — в отличие от `/ips/{ip}`, которая показывает только текущую попытку. |
| `/validators` | Список валидаторов + создание/изменение `os_port_id`/удаление. |
| `/sites` | Площадки — число слотов не ограничено, форма сверху добавляет новый слот, назначить/сменить/освободить `site_id` в каждой строке; колонка «Статус» показывает бейдж подключения пробера (`unregistered`/`idle`/`unreachable`, по аналогии с `/validators`), см. [USAGE.md](USAGE.md#состояния-площадки). |
| `/targets` | Группы целей для egress-проверок — создание/редактирование/удаление. |
| `/check-types` | Типы проверок (`https`/`icmp`/`ssh`/...), включение/выключение, привязка к группам целей. |
-| `/settings` | Две формы: `fip_settle_seconds` — пауза (в секундах) между привязкой Floating IP и началом self-check («прогрев» дата-плейна OpenStack, см. [USAGE.md](USAGE.md#пауза-перед-self-check-fip_settle_seconds)); и типы проверок пробера — TCP-порты (через запятую) + чекбокс ICMP, общие для всех площадок (см. [USAGE.md](USAGE.md#управление-типами-проверок-пробера)). |
+| `/settings` | Три формы: `fip_settle_seconds` — пауза (в секундах) между привязкой Floating IP и началом self-check («прогрев» дата-плейна OpenStack, см. [USAGE.md](USAGE.md#пауза-перед-self-check-fip_settle_seconds)); `history_retention_cycles` — сколько последних циклов проверки хранить на адрес в реестре (0 — без ограничения); и типы проверок пробера — TCP-порты (через запятую) + чекбокс ICMP, общие для всех площадок (см. [USAGE.md](USAGE.md#управление-типами-проверок-пробера)). |
### «Текущая» и «последняя завершённая» проверка
@@ -83,20 +85,38 @@ admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml
(дашборд честно показывает это в таблице, а не делает вид, что запрос
ничего не значил).
-### Удаление адресов — безвозвратно, в отличие от «Отменить»
+### Удаление адресов — безвозвратно из очереди, но не из реестра
«Отменить» (`POST .../cancel`) останавливает проверку, но сохраняет
адрес и его историю как `failed`/`cancelled` — он остаётся виден в
очереди. «Удалить» (кнопка в строке, «Удалить выбранные» по чекбоксам,
-«Очистить всё») стирает строку и всю её историю проверок/событий
-физически, без возможности восстановления — работает из любого
-состояния, включая активно проверяемое (Floating IP отвязывается,
+«Очистить всё») убирает строку из `/ips` безвозвратно — работает из
+любого состояния, включая активно проверяемое (Floating IP отвязывается,
валидатор освобождается). Все три операции удаления в UI защищены
`hx-confirm` с формулировкой, отражающей необратимость — «Очистить всё»
-предупреждает отдельно, так как затрагивает и активные проверки. Подробнее
-— [API.md](API.md#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)
+предупреждает отдельно, так как затрагивает и активные проверки.
+
+**Накопленная история при этом не теряется** — она остаётся в
+[реестре](#реестр-адресов) (`/registry/{ip}`) даже после того, как адрес
+пропал из `/ips`, и продолжает пополняться, если адрес позже добавят
+заново. Подробнее —
+[API.md](API.md#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)
и [USAGE.md](USAGE.md#удаление-адресов-из-очереди).
+### Реестр адресов
+
+`/registry` решает задачу, которую `/ips` принципиально не может: история
+проверок конкретного адреса не должна теряться только из-за того, что его
+временно вывели из очереди (например, адрес переиспользуют для другой
+цели) и позже добавили обратно — возможно, под другим циклом проверки.
+Каждая запись реестра живёт всё время, что адрес когда-либо существовал в
+системе, и не удаляется вместе со строкой `ip_queue`. Единственное, что
+можно ограничить — глубину детальной истории проверок на один адрес
+(`history_retention_cycles` на `/settings`, по циклам, а не по времени);
+сама запись в реестре (когда адрес впервые встречен, сколько всего было
+циклов) остаётся всегда. Подробнее —
+[API.md](API.md#реестр-адресов-и-история-проверок).
+
## Конфигурация
См. `configs/admin-dashboard.example.yaml`. Ключевые поля:
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 82e0ebc..65865d7 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -11,10 +11,12 @@
- [Как устроена работа с системой](#как-устроена-работа-с-системой)
- [Добавление новых IP в очередь](#добавление-новых-ip-в-очередь)
+- [Сканирование Floating IP из OpenStack](#сканирование-floating-ip-из-openstack)
- [Наблюдение за очередью](#наблюдение-за-очередью)
- [Значения полей IP](#значения-полей-ip)
- [Как читать итоговый результат (pass/partial/fail)](#как-читать-итоговый-результат-passpartialfail)
- [Просмотр деталей и истории по конкретному адресу](#просмотр-деталей-и-истории-по-конкретному-адресу)
+- [Реестр адресов и глубина истории](#реестр-адресов-и-глубина-истории)
- [Управление валидаторами](#управление-валидаторами)
- [Управление площадками (проберами)](#управление-площадками-проберами)
- [Управление типами проверок пробера](#управление-типами-проверок-пробера)
@@ -75,6 +77,31 @@ curl -s -X POST http:// Полная история проверок этого адреса за всё время →
попытка {{.Detail.IP.AttemptNumber}} (повторов: {{.Detail.IP.RetryCount}})
· валидатор {{deref .Detail.IP.OwnerValidatorID}}
diff --git a/internal/dashboard/templates/ips.html b/internal/dashboard/templates/ips.html
index 2a20843..63affa4 100644
--- a/internal/dashboard/templates/ips.html
+++ b/internal/dashboard/templates/ips.html
@@ -34,7 +34,10 @@
Новый адрес встаёт в очередь; уже завершённый (done/failed) запускается заново
-(тот же вызов); тот, что сейчас проверяется, не трогается.