v1.4.0: модули с веб-UI, правое меню, расширенные тесты

Реестр MODULE_MOUNTS: API, ui_router, фрагменты главной, EventBus.
Главная и страницы модулей с правой навигацией из реестра; wrap_module_html_page.
Ingress: публикация alert.received после сохранения в БД.
Документация MODULES.md; pytest покрывает API, UI и навигацию.

Made-with: Cursor
This commit is contained in:
Alexandr
2026-04-03 08:45:19 +03:00
parent 85eb61b576
commit 349cea85a3
17 changed files with 571 additions and 24 deletions

View File

@ -1,6 +1,6 @@
# Архитектура onGuard24 (для разработки и доработок)
Цель продукта: **модульный монолит** в духе IRM — ядро + подключаемые области (дежурства, контакты, «светофор» по сервисам). Текущая версия — **каркас v1.1**: HTTP, БД, ingress Grafana, проверки интеграций, Alembic, задел домена в `onguard24/domain/`.
Цель продукта: **модульный монолит** в духе IRM — ядро + подключаемые области (дежурства, контакты, «светофор» по сервисам). Текущая версия — **каркас v1.2**: HTTP, БД, ingress Grafana, проверки интеграций, Alembic, задел домена в `onguard24/domain/`.
## Дерево пакетов
@ -20,7 +20,7 @@ onGuard24/
│ ├── integrations/
│ │ ├── grafana_api.py # Grafana HTTP API (Bearer SA)
│ │ └── forgejo_api.py # Forgejo/Gitea API (token + probe/fallback)
│ └── modules/ # Заглушки: schedules, contacts, statusboard
│ └── modules/ # API + ui_router + registry + ui_support (фрагменты главной)
├── web/ # Vite + React (опционально)
├── pyproject.toml
├── pytest.ini
@ -31,7 +31,7 @@ onGuard24/
## Поток данных (сейчас)
1. **Grafana** (отдельно настроенный contact point) шлёт **POST** на `/api/v1/ingress/grafana` → тело JSON пишется в **`ingress_events`**.
1. **Grafana** (отдельно настроенный contact point) шлёт **POST** на `/api/v1/ingress/grafana` → тело JSON пишется в **`ingress_events`**, затем **`event_bus`** публикует **`alert.received`** (см. [MODULES.md](MODULES.md)).
2. **Параллельно** Grafana может слать в Mattermost — это вне этого репозитория (конфиг Grafana).
3. **Статус страницы** не ходит в Grafana за алертами — только **проверка доступности API** (токен SA).
@ -39,7 +39,7 @@ onGuard24/
| Задача | Место |
|--------|--------|
| Новый HTTP-роут модуля | `onguard24/modules/<name>.py` + `include_router` в `main.py` |
| Новый HTTP-роут модуля | `onguard24/modules/<name>.py` + запись в `modules/registry.py` (см. [MODULES.md](MODULES.md)) |
| Общая логика инцидентов / событий | задел: `onguard24/domain/` + [DOMAIN.md](DOMAIN.md); позже сервисный слой и БД |
| Новая таблица БД | Alembic: `alembic revision`, правка `alembic/versions/`, `alembic upgrade head` |
| Новая внешняя интеграция | `onguard24/integrations/<name>.py`, вызов из `status_snapshot` при необходимости |
@ -51,6 +51,7 @@ onGuard24/
## Зависимости между компонентами
- `status_snapshot.build(request)` читает `request.app.state.pool` и `request.app.state.settings` (устанавливаются в `lifespan`).
- `request.app.state.event_bus` — доменная шина; модули подписываются в `register_events` из `modules/registry.py`.
- Модули **не** зависят друг от друга; контракт заделан через **доменные события** (`domain/events.py`, `EventBus`) и описан в [DOMAIN.md](DOMAIN.md); проводка в HTTP пока не подключена.
## Известные ограничения

View File

@ -1,6 +1,6 @@
# Доменная модель onGuard24
Версия **1.1.0** вводит явные сущности и задел под **события** между модулями. Таблицы БД для инцидентов пока не добавлены — см. [Alembic](../alembic/versions/).
Версия **1.1+** вводит явные сущности и задел под **события** между модулями. Таблицы БД для инцидентов пока не добавлены — см. [Alembic](../alembic/versions/).
## Сущности (код: `onguard24/domain/entities.py`)
@ -24,9 +24,9 @@
1. Модуль реализует **`Module`**: свойство `name`, метод `on_event(event)`.
2. При старте приложения модуль регистрируется: `bus.subscribe("alert.received", handler)`.
3. После успешного INSERT в `ingress_events` (или нормализации) ядро вызывает `await bus.publish(AlertReceived(...))`.
3. После успешного INSERT в `ingress_events` ядро вызывает `await bus.publish_alert_received(Alert, raw_payload_ref=id_строки)`.
Сейчас **ingress** ещё не публикует в шину — подключение в следующих версиях.
Подключение к шине и регистрация модулей: **`app.state.event_bus`**, список модулей — **`modules/registry.py`** (см. [MODULES.md](MODULES.md)).
## Связь с БД

57
docs/MODULES.md Normal file
View File

@ -0,0 +1,57 @@
# Разработка функционала через модули
Цель: **новые возможности добавляются в `onguard24/modules/`**, без правок «размазанных» по `main.py`, с явной подпиской на события и **своим веб-UI** (HTML и API в одном файле пакета).
## Что уже есть в ядре
| Механизм | Назначение |
|----------|------------|
| **`modules/registry.py`** | Список `MODULE_MOUNTS`: API, метаданные UI, `register_events`. |
| **`modules/ui_support.py`** | `safe_fragment` — безопасный вызов фрагмента главной: ошибка **одного** модуля не роняет `/`. |
| **`app.state.event_bus`** | `InMemoryEventBus` — публикация после сохранения ingress (`alert.received`). |
| **`domain/events.py`** | Имена событий, `AlertReceived`. |
| **Ingress** | `INSERT … RETURNING id``publish_alert_received`. |
## Веб-UI: главная и полные страницы
- **Главная `/`** автоматически подтягивает карточки из `MODULE_MOUNTS`: заголовок, превью (`render_home_fragment`), ссылка на полный UI.
- **Правое меню («Разделы»)** строится из того же реестра: пункт **«Главная»** и по одному пункту на каждый модуль с **`ui_router`** (текст пункта = поле **`title`** в `ModuleMount`). Новый модуль с UI появляется в меню без правок шаблона — только запись в реестре.
- **Полный интерфейс модуля** — **`/ui/modules/<slug>/`**, страница собирается через **`wrap_module_html_page`** (`ui_support.py`): тот же каркас и правое меню, активный пункт подсвечивается (`current_slug`).
- Всё, что относится к модулю (JSON API, HTML, события), живёт **в одном файле модуля** + строка в реестре.
### Изоляция сбоев
- Ошибка в **`render_home_fragment`** перехватывается в **`safe_fragment`**: на главной показывается блок с классом `module-err`, остальные модули и таблица статусов отображаются.
- Ошибка в обработчике **полной страницы** `/ui/modules/...` даёт 500 **только для этого запроса**; процесс и остальные маршруты продолжают работать.
- Рекомендуется не полагаться на глобальное состояние между модулями; общение — через БД и `event_bus`.
Фронт в **`web/`** (Vite) остаётся опциональным; серверный HTML — основной путь для встроенного UI.
## Добавить новый модуль (чеклист)
1. **Файл** `onguard24/modules/<имя>.py`:
- `router` — JSON API под `/api/v1/modules/<имя>/`.
- Опционально **`ui_router`** — `APIRouter(include_in_schema=False)`, маршруты полных HTML-страниц (корень `/``/ui/modules/<slug>/`).
- Опционально **`async def render_home_fragment(request) -> str`** — HTML-фрагмент (без `<html>`) для карточки на главной.
- **`register_events(_bus)`** — подписки на шину.
2. **Регистрация** в **`onguard24/modules/registry.py`** — объект **`ModuleMount`**:
- `router`, `url_prefix`, `register_events`, **`slug`**, **`title`**, опционально **`ui_router`**, **`render_home_fragment`**.
3. **Миграции** — если нужны таблицы: `alembic revision`, `alembic upgrade head`.
4. **Тесты** — API, при необходимости GET `/` и `/ui/modules/<slug>/`.
`main.py` **не** меняется — только реестр.
## События
- Из ingress публикуется **`alert.received`** (`AlertReceived`).
- Обработчик: `async def h(event: DomainEvent) -> None`; удобно `isinstance(event, AlertReceived)`.
## Ограничения
- Шина **in-process**; несколько воркеров — позже общая очередь.
- Auth на модули пока нет — сеть / reverse proxy.
См. [DOMAIN.md](DOMAIN.md), [ARCHITECTURE.md](ARCHITECTURE.md).