Files
cloud-ip-validator/docs/PLAN_ADMIN_DASHBOARD.md

155 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: `cmd/admin-dashboard` — веб-панель администратора
> Статус: **реализовано**. Актуальное описание — [docs/DASHBOARD.md](DASHBOARD.md).
## Context
Единственный способ управлять `control-api` (очередь IP, валидаторы,
площадки, цели проверки) — HTTP API через `curl` (см. `docs/API.md`). API
уже покрывает весь необходимый функционал (`POST /api/v1/admin/ips` для
постановки/принудительного повтора, `POST /api/v1/admin/ips/{ip}/cancel`
для остановки, `/api/v1/admin/config/{validators,sites,targets,check-types}`
для CRUD), но curl неудобен для повседневного оперирования и не даёт
наглядной картины состояния очереди. Нужен браузерный admin dashboard —
графический доступ ко всей этой функциональности: сводная статистика
(текущая проверка / итог последней завершённой), управление конфигурацией
и принудительные операции над очередью — без YAML и без перезапуска
`control-api`.
**Согласованные решения:**
- **Без сборки фронтенда.** Server-rendered `html/template` + htmx
(частичные AJAX-обновления) + Alpine.js (точечная клиентская
интерактивность) + Pico.css classless (минимализм на голой семантической
разметке). Все три библиотеки вендорятся как статические файлы и
встраиваются через `//go:embed` — без CDN во время выполнения (принцип
проекта — полностью автономный бинарник, работает офлайн).
- **Никакого нового backend-состояния.** «Текущая проверка» — live-снимок
IP не в терминальном состоянии. «Последняя завершённая» — последние N
(по умолчанию 20, настраивается) по `AggregatedAt` desc среди
`done`/`failed`, с разбивкой по `OverallResult`. Оба вычисляются на
каждый запрос из `GET /admin/status` + `GET /admin/ips` — никакого
понятия «запуска»/«батча» в `control-api` не добавляется.
- **Без аутентификации** — как и сам API; доступ ограничивается сетью/firewall.
- **Отдельный 4-й бинарник** (`cmd/admin-dashboard`), не новые маршруты
внутри `control-api`. Ходит в `control-api` только через существующий
HTTP admin API (`internal/apiclient.Client`). Рендеринг на сервере —
браузер не видит JSON `control-api` напрямую, CORS/reverse-proxy не нужны.
- Малые допущения: без пагинации очереди (текущий масштаб — десятки
адресов); баннер ошибок различает 4xx (жёлтый) и 5xx/транспортные
(красный); порт дашборда по умолчанию `:8090`.
## 1. Структура файлов
```
cmd/admin-dashboard/main.go
internal/dashboard/
server.go, routes.go, client.go, dto.go, render.go, embed.go
handlers_overview.go, handlers_ips.go, handlers_validators.go,
handlers_sites.go, handlers_targets.go, handlers_checktypes.go
templates/ (layout, overview[+fragment], ips[+table], ip_detail,
validators[+table+row], sites[+table], targets[+table+row],
checktypes[+table+row], error_banner)
static/vendor/{htmx.min.js,alpine.min.js,pico.classless.min.css,VENDOR.md}
static/dashboard.css
configs/admin-dashboard.example.yaml
deploy/systemd/admin-dashboard.service
docs/DASHBOARD.md
```
Таблица `ips` перерисовывается целиком при любой мутации (`POST
/admin/ips` может завести новые строки и поменять `sequence`).
`validators`/`sites`/`targets`/`check-types` — точечный swap одной `<tr>`.
## 2. Маршруты дашборда
| Метод | Путь | Вызов к control-api |
|---|---|---|
| GET | `/` | редирект на `/overview` |
| GET | `/overview`, `/overview/fragment` | `GET /admin/status`, `GET /admin/ips` |
| GET | `/ips`, `/ips/{ip}` | `GET /admin/ips`, `GET /admin/ips/{ip}` |
| POST | `/ips` | `POST /admin/ips` |
| POST | `/ips/{ip}/recheck` | `POST /admin/ips` `{"addresses":[ip]}` |
| POST | `/ips/{ip}/cancel` | `POST /admin/ips/{ip}/cancel` |
| GET/POST `/validators`, PUT/DELETE `/validators/{id}` | `.../config/validators[/{id}]` |
| GET `/sites`, PUT/DELETE `/sites/{index}` | `.../config/sites[/{index}]` |
| GET/POST `/targets`, PUT/DELETE `/targets/{group}` | `.../config/targets[/{group}]` |
| GET/POST `/check-types`, PUT/DELETE `/check-types/{name}` | `.../config/check-types[/{name}]` |
| GET | `/static/*` | embed.FS |
`overview/fragment` — `hx-trigger="every Ns"` (из конфига), без фонового
тикера на сервере. `POST /targets`/`/check-types` — перевод «форма с
именем» → `PUT .../{name}` (control-api там upsert).
## 3. Ошибки control-api
`client.go`: не-2xx → `*apiErr{Status, Message}`. Хендлеры не отдают 500 —
рендерят страницу/фрагмент + out-of-band `error_banner.html`
(`hx-swap-oob="true"`, `id="error-banner"` в `layout.html`); статус ответа
дашборда = статус control-api (502 при транспортной ошибке). Баннер жёлтый
для 4xx, красный для 5xx/транспортных.
## 4. Конфигурация
`internal/config/config.go`, секция `AdminDashboard{Server, ControlAPI{
BaseURL, TimeoutSeconds}, Overview{LastCompletedCount, PollIntervalSeconds}}`
+ `LoadAdminDashboard` (defaults: `:8090`, timeout 10s, N=20, poll=5s;
`BaseURL` обязателен). `configs/admin-dashboard.example.yaml`,
`deploy/systemd/admin-dashboard.service` (без CAP_NET_RAW/EnvironmentFile).
## 5. DTO и клиент
`internal/dashboard/dto.go` — свои wire-структуры (не импортируют
приватные DTO `internal/httpapi`, тот же паттерн, что `internal/probercore`):
snake_case-структуры дословно повторяют `internal/httpapi/dto_admin.go`;
для `GET /admin/ips[/{ip}]`/`GET /admin/validators` — зеркала untagged
PascalCase `db.IPQueueItem`/`db.Validator`/`db.Check`/`db.Event`.
`client.go` — обёртка над `apiclient.Client`: `Status`, `ListIPs`, `GetIP`,
`SubmitIPs`, `CancelIP`, CRUD-методы для validators/sites/target-groups/
check-types.
`handlers_overview.go` — чистые функции: `currentlyChecking`,
`lastCompleted(items, n)`, `resultBreakdown`.
## 6. Вендоринг статики
Скачать один раз, закоммитить, задокументировать в `VENDOR.md`:
`htmx.min.js` (unpkg htmx.org, ядро без расширений), `alpine.min.js`
(unpkg alpinejs `dist/cdn.min.js`, IIFE-сборка), `pico.classless.min.css`
(unpkg @picocss/pico).
## 7. Тестирование
`httptest`-фейковый control-api + `httptest`-дашборд поверх него, проверка
рендера через `strings.Contains` (по образцу `internal/httpapi/handlers_config_test.go`).
Кейсы: overview live-агрегация и `resultBreakdown`; submit (happy +
пустой список); recheck (done→requeued, checking→skipped, явно показано);
cancel (happy + 409); CRUD-раунд-трип + конфликты (409/400) по всем 4
сущностям; control-api недоступен → баннер + 502.
**Обязательный ручной шаг:** браузерный смоук-тест против
`scripts/run-local-e2e.sh` (или отдельного mock control-api) — все
страницы/формы/действия, live-обновление во время реального прогона,
проверка через DevTools Network отсутствия внешних (CDN) запросов.
## 8. Документация
Новый `docs/DASHBOARD.md`; `README.md` («три компонента» →
«четыре»); `docs/SETUP.md` (компонент + раздел развёртывания + сетевые
доступы); `docs/API.md` (отсылка на дашборд в предупреждении об
открытости API).
## Критичные файлы
`internal/dashboard/{client.go,dto.go,routes.go,server.go,
handlers_overview.go,render.go,embed.go}`, `internal/config/config.go`
(`AdminDashboard`), `cmd/admin-dashboard/main.go`.
## Проверка
1. `go build ./... && go test ./...`
2. `scripts/run-local-e2e.sh` + `admin-dashboard` отдельно против того же control-api
3. Ручной браузерный смоук-тест (обязателен, не пропускается)