import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from starlette.responses import HTMLResponse, Response from onguard24 import __version__ as app_version from onguard24.config import get_settings from onguard24.db import create_pool from onguard24.domain.events import InMemoryEventBus from onguard24.ingress import grafana as grafana_ingress from onguard24.log_buffer import install_log_handler from onguard24.modules.registry import MODULE_MOUNTS, register_module_events from onguard24.root_html import render_root_page from onguard24.status_snapshot import build as build_status from onguard24.ui_logs import router as logs_router logging.basicConfig(level=logging.DEBUG) # Приглушаем шумные низкоуровневые библиотеки for _noisy in ( "httpx", "httpcore", "asyncio", "uvicorn.access", "uvicorn.error", ): logging.getLogger(_noisy).setLevel(logging.WARNING) log = logging.getLogger("onguard24") def parse_addr(http_addr: str) -> tuple[str, int]: s = http_addr.strip() if s.startswith(":"): return "0.0.0.0", int(s[1:]) if ":" in s: h, p = s.rsplit(":", 1) return (h or "0.0.0.0"), int(p) return "0.0.0.0", int(s) @asynccontextmanager async def lifespan(app: FastAPI): settings = get_settings() install_log_handler(asyncio.get_event_loop(), log_file=settings.log_file) pool = await create_pool(settings) bus = InMemoryEventBus() register_module_events(bus, pool) app.state.pool = pool app.state.settings = settings app.state.event_bus = bus log.info("onGuard24 started v%s, db=%s", app_version, "ok" if pool else "disabled") yield if pool: await pool.close() log.info("database pool closed") def create_app() -> FastAPI: app = FastAPI(title="onGuard24", version=app_version, lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"], allow_credentials=True, allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["*"], ) @app.get("/") async def root(request: Request): if request.query_params.get("format") == "json": data = await build_status(request) data["links"] = { "docs": "/docs", "openapi": "/openapi.json", "health": "/health", "status_api": "/api/v1/status", } return data return HTMLResponse(await render_root_page(request)) @app.get("/favicon.ico", include_in_schema=False) async def favicon(): return Response(status_code=204) @app.get("/health") @app.get("/api/v1/health") async def health(): return {"status": "ok", "service": "onGuard24"} @app.get("/api/v1/status") async def status(request: Request): return await build_status(request) app.include_router(grafana_ingress.router, prefix="/api/v1") app.include_router(logs_router) for mount in MODULE_MOUNTS: app.include_router(mount.router, prefix=mount.url_prefix) if mount.ui_router is not None: app.include_router(mount.ui_router, prefix=f"/ui/modules/{mount.slug}") return app app = create_app() def run() -> None: import uvicorn settings = get_settings() host, port = parse_addr(settings.http_addr) lvl = settings.log_level.upper() uvicorn.run( "onguard24.main:app", host=host, port=port, log_level=lvl.lower() if lvl in {"DEBUG", "INFO", "WARNING", "ERROR"} else "info", ) if __name__ == "__main__": run()