# Antigravity: управление Links и журнал событий в веб-консоли

## Репозиторий

- https://github.com/ochenstarik-ui/server-monitor-manager
- База: `main` @ `7c43977`
- Ветка: `antigravity/console-links`
- `main` защищён: PR обязателен, обязательны статусы `build-and-test` и `build`, force-push запрещён
- один PR — одна тема; описание PR заполняется **после** завершения CI
- отчёт — раздел в описании PR; отдельным файлом отчёт не является

## Задача

Консоль сейчас показывает узлы и Links и выдаёт коды регистрации. Создать Link или отключить его из неё нельзя.

Links — это главная функция продукта: направленный доступ от одного узла к другому по протоколу и порту, с ограниченным сроком жизни и возможностью мгновенно отозвать. Пока их нельзя ни выдать, ни отозвать из интерфейса, продуктом в его основном назначении пользоваться невозможно — только руками через API с клиентским сертификатом.

**Новых эндпоинтов не требуется, все нужные уже есть.** Работа целиком в консоли.

## Что использовать

| Действие | Эндпоинт |
|---|---|
| Список | `GET /api/v1/control/links` |
| Создать | `POST /api/v1/control/links` |
| Отключить | `POST /api/v1/control/links/{id}/disable` |
| Журнал | `GET /api/v1/control/events` |

Тело создания — `LinkPolicyCreateRequest`: `SourceNodeId`, `TargetNodeId`, `Protocol`, `Port`, `TtlMinutes`, `Reason`, `IdempotencyKey`. Отключение — `LinkPolicyDisableRequest` с `IdempotencyKey`.

## Требования

**1. Показывать желаемое и фактическое раздельно.** `LinkPolicy` содержит `DesiredState`, `ActualState` и `LastError`. Расхождение между желаемым и фактическим — не деталь реализации, а суть системы: правило может быть заказано, но ещё не применено на узле, или примениться с ошибкой. Схлопывать оба поля в один значок «активно» нельзя. Расхождение должно быть заметно само, без вчитывания, и `LastError` показывается, когда он есть.

**2. Создание — с явным подтверждением направления.** Link открывает сетевой доступ. Перед отправкой показать человеку одной строкой, что именно произойдёт: «с узла A к узлу B, протокол, порт, на N минут». Направленность — свойство модели, и перепутанные местами источник и цель дают работающее правило, открывающее доступ не туда.

**3. `Reason` обязателен и осмысленный.** Он идёт в журнал и остаётся единственным следом того, зачем доступ открывали. Пустое поле или пробел не принимать.

**4. Отключение — быстрое и заметное.** Это аварийный рычаг: когда он нужен, его ищут в спешке. Действие доступно из строки списка, без перехода на другую страницу, и подтверждается одним шагом.

**5. `IdempotencyKey` генерируется на клиенте** и не повторяется между разными операциями. При повторной отправке той же формы, например по второму нажатию кнопки или после потери связи, ключ остаётся прежним, чтобы не создать два правила вместо одного.

**6. Срок жизни виден.** Для каждого Link показать, когда он истекает, а не только `TtlMinutes`, заданный при создании.

**7. Журнал событий.** Отдельный раздел со списком из `GET /events`: что произошло, с каким узлом, когда. Достаточно чтения, фильтры не требуются.

**8. Ошибки сервера показывать текстом.** Если создание отклонено проверкой, оператор должен увидеть причину, а не молчаливое ничего.

## Чего не делать

- не добавлять эндпоинты в Control: всё нужное существует;
- не трогать выдачу кодов регистрации и вход по паролю — они уже работают;
- не добавлять операций, которых нет в списке выше: удаление узлов, перезапуск служб, правка сертификатов в консоль пока не входят.

## Границы

Codex ведёт `deploy/**`, релизный пайплайн и проверку релиза. **Не трогать:** `deploy/**`, `.github/workflows/**`, `tests/bootstrap/**`, `tests/release-verification/**`.

Ваша область: `src/ServerMonitorManager.Control/wwwroot/**`, `tests/ServerMonitorManager.Control.Tests/**`. Правки в `src/ServerMonitorManager.Control/Program.cs` допустимы, только если без них не обойтись, — опишите зачем.

## Тесты

Существующие проверки консоли должны продолжать проходить: анонимный доступ отклонён, роли `Agent` и `Automation` запрещены, ассеты отдаются из ресурсов сборки при отсутствии `wwwroot` на диске.

Добавить проверку, что разметка консоли содержит элементы управления Links и раздел журнала, — по образцу существующего `WebConsoleHtmlContainsRequiredUiElementsAndWarning`.

## Критерий приёмки

- из консоли можно создать Link и отключить его;
- желаемое и фактическое состояние показаны раздельно, `LastError` виден при наличии;
- перед созданием показано направление, порт и срок одной понятной строкой;
- `Reason` обязателен, пустой не принимается;
- `IdempotencyKey` генерируется клиентом и устойчив к повторной отправке;
- виден момент истечения каждого Link;
- есть раздел журнала событий;
- ошибки сервера показываются текстом;
- новых эндпоинтов в Control не появилось;
- PR, CI зелёный, PR не смержен до зелёного.
