utp_service/backend/app/routers/tasks.py

170 lines
5.7 KiB
Python

from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select, desc
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_user, get_db
from app.models.device import Device
from app.models.object import Object
from app.models.rack import Rack
from app.models.task import Task, TaskResult
from app.models.zone import Zone
from app.models.user import User
from app.plugins.registry import action_registry
from app.schemas.task import ActionInfo, TaskCreate, TaskDetail, TaskRead
from app.services import task as task_service
router = APIRouter(prefix="/api/v1/tasks", tags=["tasks"])
async def _resolve_labels(db: AsyncSession, tasks: list[Task]) -> dict[str, str]:
"""Batch-resolve target_scope to human-readable labels."""
obj_ids: set[int] = set()
dev_ids: set[int] = set()
rack_ids: set[int] = set()
zone_ids: set[int] = set()
for t in tasks:
scope = t.target_scope
kind = scope.get("type")
if kind == "object":
obj_ids.add(scope["id"])
elif kind == "device":
dev_ids.add(scope["id"])
elif kind == "rack":
rack_ids.add(scope["id"])
elif kind == "zone":
zone_ids.add(scope["id"])
elif kind in ("category", "role") and (oid := scope.get("object_id")):
obj_ids.add(oid)
objs: dict[int, str] = {}
devs: dict[int, str] = {}
rack_names: dict[int, str] = {}
zone_names: dict[int, str] = {}
if obj_ids:
rows = await db.execute(select(Object.id, Object.name).where(Object.id.in_(obj_ids)))
objs = {row[0]: row[1] for row in rows.all()}
if dev_ids:
rows = await db.execute(
select(Device.id, Device.hostname, Device.ip).where(Device.id.in_(dev_ids))
)
devs = {row[0]: (row[1] or row[2]) for row in rows.all()}
if rack_ids:
rows = await db.execute(select(Rack.id, Rack.name).where(Rack.id.in_(rack_ids)))
rack_names = {row[0]: row[1] for row in rows.all()}
if zone_ids:
rows = await db.execute(select(Zone.id, Zone.name).where(Zone.id.in_(zone_ids)))
zone_names = {row[0]: row[1] for row in rows.all()}
labels: dict[str, str] = {}
for t in tasks:
scope = t.target_scope
kind = scope.get("type", "")
if kind == "object":
labels[t.id] = objs.get(scope.get("id"), f"Объект #{scope.get('id')}")
elif kind == "device":
labels[t.id] = devs.get(scope.get("id"), f"Устройство #{scope.get('id')}")
elif kind == "rack":
_rid = scope.get('id')
labels[t.id] = f"Стойка: {rack_names.get(_rid, f'#{_rid}')}"
elif kind == "zone":
_zid = scope.get('id')
labels[t.id] = f"Зона: {zone_names.get(_zid, f'#{_zid}')}"
elif kind in ("category", "role"):
val = scope.get("value", "?")
if oid := scope.get("object_id"):
labels[t.id] = f"{val} @ {objs.get(oid, f'#{oid}')}"
else:
labels[t.id] = f"{val} (все объекты)"
else:
labels[t.id] = ""
return labels
@router.get("/actions", response_model=list[ActionInfo])
async def list_actions(_: User = Depends(get_current_user)):
return [
ActionInfo(
name=a.name,
display_name=a.display_name,
compatible_categories=a.compatible_categories,
params_schema=a.params_schema,
)
for a in action_registry.all()
]
@router.post("", response_model=TaskRead, status_code=status.HTTP_202_ACCEPTED)
async def create_task(
body: TaskCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
try:
task = await task_service.create_task(
action_type=body.type,
target_scope=body.target_scope,
params=body.params,
user_id=current_user.id,
)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
labels = await _resolve_labels(db, [task])
result = TaskRead.model_validate(task)
result.target_label = labels.get(task.id)
return result
@router.get("", response_model=list[TaskRead])
async def list_tasks(
limit: int = 50,
skip: int = 0,
device_id: Optional[int] = Query(None),
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
query = select(Task).order_by(desc(Task.created_at))
if device_id is not None:
query = (
query
.join(TaskResult, Task.id == TaskResult.task_id)
.where(TaskResult.device_id == device_id)
.distinct()
)
rows = await db.execute(query.offset(skip).limit(limit))
tasks = list(rows.scalars().all())
labels = await _resolve_labels(db, tasks)
result = []
for t in tasks:
r = TaskRead.model_validate(t)
r.target_label = labels.get(t.id)
result.append(r)
return result
@router.get("/{task_id}", response_model=TaskDetail)
async def get_task(
task_id: str,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
rows = await db.execute(
select(Task)
.options(selectinload(Task.events), selectinload(Task.device_results))
.where(Task.id == task_id)
)
task = rows.scalar_one_or_none()
if task is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
labels = await _resolve_labels(db, [task])
result = TaskDetail.model_validate(task)
result.target_label = labels.get(task.id)
return result