"""IRM: задачи по инцидентам (или вне привязки).""" from __future__ import annotations import html from uuid import UUID import asyncpg from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse from pydantic import BaseModel, Field from onguard24.deps import get_pool from onguard24.domain.events import EventBus from onguard24.modules.ui_support import wrap_module_html_page router = APIRouter(tags=["module-tasks"]) ui_router = APIRouter(tags=["web-tasks"], include_in_schema=False) class TaskCreate(BaseModel): title: str = Field(..., min_length=1, max_length=500) incident_id: UUID | None = None def register_events(_bus: EventBus, _pool: asyncpg.Pool | None = None) -> None: pass async def render_home_fragment(request: Request) -> str: pool = get_pool(request) if pool is None: return '

Нужна БД для задач.

' try: async with pool.acquire() as conn: n = await conn.fetchval("SELECT count(*)::int FROM tasks") except Exception: return '

Таблица задач недоступна (миграции?).

' return f'

Задач: {int(n)}

' @router.get("/") async def list_tasks_api( pool: asyncpg.Pool | None = Depends(get_pool), incident_id: UUID | None = None, limit: int = 100, ): if pool is None: return {"items": [], "database": "disabled"} limit = min(max(limit, 1), 200) async with pool.acquire() as conn: if incident_id: rows = await conn.fetch( """ SELECT id, incident_id, title, status, created_at FROM tasks WHERE incident_id = $1::uuid ORDER BY created_at DESC LIMIT $2 """, incident_id, limit, ) else: rows = await conn.fetch( """ SELECT id, incident_id, title, status, created_at FROM tasks ORDER BY created_at DESC LIMIT $1 """, limit, ) items = [] for r in rows: items.append( { "id": str(r["id"]), "incident_id": str(r["incident_id"]) if r["incident_id"] else None, "title": r["title"], "status": r["status"], "created_at": r["created_at"].isoformat() if r["created_at"] else None, } ) return {"items": items} @router.post("/", status_code=201) async def create_task_api(body: TaskCreate, pool: asyncpg.Pool | None = Depends(get_pool)): if pool is None: raise HTTPException(status_code=503, detail="database disabled") if body.incident_id: async with pool.acquire() as conn: ok = await conn.fetchval( "SELECT 1 FROM incidents WHERE id = $1::uuid", body.incident_id, ) if not ok: raise HTTPException(status_code=400, detail="incident not found") async with pool.acquire() as conn: row = await conn.fetchrow( """ INSERT INTO tasks (title, incident_id, status) VALUES ($1, $2::uuid, 'open') RETURNING id, incident_id, title, status, created_at """, body.title.strip(), body.incident_id, ) return { "id": str(row["id"]), "incident_id": str(row["incident_id"]) if row["incident_id"] else None, "title": row["title"], "status": row["status"], "created_at": row["created_at"].isoformat() if row["created_at"] else None, } @ui_router.get("/", response_class=HTMLResponse) async def tasks_ui_home(request: Request): pool = get_pool(request) rows_html = "" err = "" if pool is None: err = "

База данных не настроена.

" else: try: async with pool.acquire() as conn: rows = await conn.fetch( """ SELECT t.id, t.title, t.status, t.incident_id, t.created_at FROM tasks t ORDER BY t.created_at DESC LIMIT 100 """ ) for r in rows: iid = str(r["incident_id"])[:8] + "…" if r["incident_id"] else "—" rows_html += ( "" f"{html.escape(str(r['id']))[:8]}…" f"{html.escape(r['title'])}" f"{html.escape(r['status'])}" f"{html.escape(iid)}" "" ) except Exception as e: err = f"

{html.escape(str(e))}

" inner = f"""

Задачи

{err} {rows_html or ''}
IDЗаголовокСтатусИнцидент
Пока нет задач
""" return HTMLResponse( wrap_module_html_page( document_title="Задачи — onGuard24", current_slug="tasks", main_inner_html=inner, ) )