"""Единая точка регистрации модулей: API, веб-UI и подписки на события. Новый модуль: файл в `onguard24/modules/`, запись в `MODULE_MOUNTS` — см. docs/MODULES.md. """ from __future__ import annotations import asyncpg from collections.abc import Awaitable, Callable from dataclasses import dataclass from fastapi import APIRouter from starlette.requests import Request from onguard24.domain.events import EventBus from onguard24.modules import ( alerts, contacts, escalations, grafana_catalog, incidents, schedules, statusboard, tasks, ) # async (Request) -> str — фрагмент HTML для главной страницы (опционально) HomeFragment = Callable[[Request], Awaitable[str]] RegisterEvents = Callable[[EventBus, asyncpg.Pool | None], None] @dataclass(frozen=True) class ModuleMount: """Один модуль: API под url_prefix, UI под /ui/modules/{slug}.""" router: APIRouter url_prefix: str register_events: RegisterEvents slug: str title: str ui_router: APIRouter | None = None render_home_fragment: HomeFragment | None = None def _mounts() -> list[ModuleMount]: return [ ModuleMount( router=grafana_catalog.router, url_prefix="/api/v1/modules/grafana-catalog", register_events=grafana_catalog.register_events, slug="grafana-catalog", title="Каталог Grafana", ui_router=grafana_catalog.ui_router, render_home_fragment=grafana_catalog.render_home_fragment, ), ModuleMount( router=alerts.router, url_prefix="/api/v1/modules/alerts", register_events=alerts.register_events, slug="alerts", title="Алерты", ui_router=alerts.ui_router, render_home_fragment=alerts.render_home_fragment, ), ModuleMount( router=incidents.router, url_prefix="/api/v1/modules/incidents", register_events=incidents.register_events, slug="incidents", title="Инциденты", ui_router=incidents.ui_router, render_home_fragment=incidents.render_home_fragment, ), ModuleMount( router=tasks.router, url_prefix="/api/v1/modules/tasks", register_events=tasks.register_events, slug="tasks", title="Задачи", ui_router=tasks.ui_router, render_home_fragment=tasks.render_home_fragment, ), ModuleMount( router=escalations.router, url_prefix="/api/v1/modules/escalations", register_events=escalations.register_events, slug="escalations", title="Эскалации", ui_router=escalations.ui_router, render_home_fragment=escalations.render_home_fragment, ), ModuleMount( router=schedules.router, url_prefix="/api/v1/modules/schedules", register_events=schedules.register_events, slug="schedules", title="Календарь дежурств", ui_router=schedules.ui_router, render_home_fragment=schedules.render_home_fragment, ), ModuleMount( router=contacts.router, url_prefix="/api/v1/modules/contacts", register_events=contacts.register_events, slug="contacts", title="Контакты", ui_router=contacts.ui_router, render_home_fragment=contacts.render_home_fragment, ), ModuleMount( router=statusboard.router, url_prefix="/api/v1/modules/statusboard", register_events=statusboard.register_events, slug="statusboard", title="Светофор", ui_router=statusboard.ui_router, render_home_fragment=statusboard.render_home_fragment, ), ] MODULE_MOUNTS: list[ModuleMount] = _mounts() def register_module_events(bus: EventBus, pool: asyncpg.Pool | None = None) -> None: for m in MODULE_MOUNTS: m.register_events(bus, pool)