Files
cloud-ip-validator/docs/DASHBOARD.md
T
ayurishchevandClaude Sonnet 5 93c79b63ea Skip the check cycle for a Floating IP already occupied by another port
The cloud is live: the address list submitted as "free" (bootstrap config
or POST /api/v1/admin/ips) can drift by the time the orchestrator claims
it, or an operator can queue an already-occupied address by mistake.
Neutron's floating-IP association is a blind "last write wins" PUT with
no conflict error to catch, so associateFIP now checks the FIP's PortID
(already fetched via GetFloatingIPByAddress) before associating, guarded
against the false-positive of the FIP already belonging to this same
validator's own port.

A match routes the address straight to a new terminal ip_queue.state
("occupied", distinct from failed/fail) via db.MarkFIPOccupied — no
retries, since Neutron won't free it on its own and requeuing would let
it be reclaimed again next tick, starving the rest of the queue — plus a
dedicated fip_occupied audit event. Resubmitting the address later (once
the conflict is resolved) resets it to queued via the existing
POST /api/v1/admin/ips resubmit path (CancelIP/ListExpiredLeases updated
to treat occupied as terminal too). admin-dashboard gets its own "занят"
badge, distinct from fail/partial/cancelled.

Rebuilt bin/{control-api,admin-dashboard,prober,validator-agent} and
bin/SHA256SUMS per docs/SETUP.md's documented build recipe, since
control-api and admin-dashboard source changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeVbMVEiE7XQAkBd7HQgj6
2026-09-13 23:54:35 +03:00

12 KiB
Raw Blame History

Admin Dashboard

admin-dashboard — 4-й компонент системы: браузерная веб-панель, дающая полное покрытие административного API control-api (docs/API.md) без единого curl. Отдельный, полностью самостоятельный процесс — не хранит состояния, не подключается к базе данных напрямую, общается с control-api только через его же HTTP admin API.

Устройство

  • Рендеринг полностью на сервере: html/template + htmx (частичные обновления без перезагрузки страницы) + Alpine.js (точечная клиентская интерактивность). Оформление — собственная небольшая дизайн-система (internal/dashboard/static/dashboard.css), не построена на готовом CSS-фреймворке: боковая навигация, карточки-панели со скруглением и мягкой тенью, статусы — пастельные «пилюли» (не Bootstrap-бейджи). Шрифты — Manrope (интерфейс/заголовки) и IBM Plex Mono (IP-адреса, таймстампы, числа — моноширинные для табличного выравнивания), с кириллицей. Адаптивна: боковая панель на узких экранах уходит в выдвижное меню, таблицы складываются в карточки. Никакой сборки фронтенда нет — шрифты и JS-библиотеки вендорены как статические файлы (internal/dashboard/static/vendor/, см. VENDOR.md там же) и встроены в бинарник через //go:embed. Дашборд работает полностью офлайн — при открытии страницы браузер не делает ни одного запроса за пределы самого дашборда (можно проверить через DevTools → Network).
  • Браузер никогда не видит JSON control-api напрямую: каждая страница и каждый htmx-фрагмент — это HTML, отрендеренный Go-хендлером дашборда после вызова control-api. CORS и reverse-proxy не нужны.
  • Как и control-api, дашборд не аутентифицирован — ограничивайте доступ на уровне сети/файрвола (см. SETUP.md).

Запуск

cp configs/admin-dashboard.example.yaml /etc/cloud-ip-validator/admin-dashboard.yaml
# отредактируйте control_api.base_url под ваш стенд
admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml

Развёртывание как systemd-юнита — по образцу остальных компонентов, см. SETUP.md. Порт по умолчанию — :8090 (у control-api — :8080).

Страницы и что на них можно делать

Страница Назначение
/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} Детали одного адреса: все проверки текущей попытки и вся история событий.
/validators Список валидаторов + создание/изменение os_port_id/удаление.
/sites Площадки — число слотов не ограничено, форма сверху добавляет новый слот, назначить/сменить/освободить site_id в каждой строке; колонка «Статус» показывает бейдж подключения пробера (unregistered/idle/unreachable, по аналогии с /validators), см. USAGE.md.
/targets Группы целей для egress-проверок — создание/редактирование/удаление.
/check-types Типы проверок (https/icmp/ssh/...), включение/выключение, привязка к группам целей.
/settings Две формы: fip_settle_seconds — пауза (в секундах) между привязкой Floating IP и началом self-check («прогрев» дата-плейна OpenStack, см. USAGE.md); и типы проверок пробера — TCP-порты (через запятую) + чекбокс ICMP, общие для всех площадок (см. USAGE.md).

«Текущая» и «последняя завершённая» проверка

В control-api нет понятия «запуска»/«цикла проверки» как отдельной сущности — есть только общая очередь IP-адресов (docs/PLAN_ADMIN_DASHBOARD.md). Дашборд ничего не меняет в этом устройстве и не заводит своего состояния:

  • Текущая проверка — все адреса, которые прямо сейчас не в состоянии done/failed (queued, assigning_fip, awaiting_self_check, checking, aggregating), вычисляется заново на каждый запрос из GET /api/v1/admin/status + GET /api/v1/admin/ips.
  • Последняя завершённая проверка — последние N адресов, перешедших в done/failed, отсортированные по AggregatedAt по убыванию (не «последний запуск», а именно скользящее окно последних по времени завершений).

Добавление адресов и принудительный повтор — один и тот же вызов

Форма на /ips всегда бьёт в POST /api/v1/admin/ips. Поведение зависит от текущего состояния каждого конкретного адреса (см. docs/API.md): новый — встаёт в очередь; уже done/failed — принудительно перезапускается; уже queued — просто переупорядочивается; уже активно проверяется — не трогается (дашборд честно показывает это в таблице, а не делает вид, что запрос ничего не значил).

Удаление адресов — безвозвратно, в отличие от «Отменить»

«Отменить» (POST .../cancel) останавливает проверку, но сохраняет адрес и его историю как failed/cancelled — он остаётся виден в очереди. «Удалить» (кнопка в строке, «Удалить выбранные» по чекбоксам, «Очистить всё») стирает строку и всю её историю проверок/событий физически, без возможности восстановления — работает из любого состояния, включая активно проверяемое (Floating IP отвязывается, валидатор освобождается). Все три операции удаления в UI защищены hx-confirm с формулировкой, отражающей необратимость — «Очистить всё» предупреждает отдельно, так как затрагивает и активные проверки. Подробнее — API.md и USAGE.md.

Конфигурация

См. configs/admin-dashboard.example.yaml. Ключевые поля:

  • server.listen_addr — где слушает сам дашборд (по умолчанию :8090).
  • control_api.base_url — адрес control-api, обязателен.
  • control_api.timeout_seconds — таймаут HTTP-запросов к control-api.
  • overview.last_completed_count — размер окна «последних завершённых» на странице обзора.
  • overview.poll_interval_seconds — как часто браузер опрашивает /overview/fragment для live-обновления.

Отображение ошибок

Любая ошибка control-api (4xx/5xx с телом {"error":"..."}) или сбой связи с ним (недоступен, таймаут) показывается баннером наверху страницы, а не приводит к падению дашборда — таблица/страница при этом всегда отражает актуальное состояние control-api (дашборд перезапрашивает данные после любой попытки мутации, независимо от её исхода). Жёлтый баннер — бизнес-ошибка (4xx, например «валидатор занят»), красный — инфраструктурная проблема (5xx или control-api недоступен).