import logging from typing import Any import yaml from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_user from app.core.config import settings from app.db.database import AsyncSessionLocal, get_db from app.db.models import Node, PendingDevice, ScanRun from app.schemas.nodes import NodeCreate from app.schemas.scan import PendingDeviceResponse, ScanRunResponse from app.services.scanner import run_scan class ScanConfig(BaseModel): ranges: list[str] interval_seconds: int logger = logging.getLogger(__name__) router = APIRouter() def _load_ranges() -> list[str]: try: with open(settings.config_path) as f: cfg: dict[str, Any] = yaml.safe_load(f) or {} return list(cfg.get("scanner", {}).get("ranges", [])) except Exception: return [] async def _background_scan(run_id: str, ranges: list[str]) -> None: async with AsyncSessionLocal() as db: await run_scan(ranges, db, run_id) @router.post("/trigger", response_model=ScanRunResponse) async def trigger_scan( background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user), ) -> ScanRun: ranges = _load_ranges() run = ScanRun(status="running", ranges=ranges) db.add(run) await db.commit() await db.refresh(run) background_tasks.add_task(_background_scan, run.id, ranges) return run @router.get("/pending", response_model=list[PendingDeviceResponse]) async def list_pending(db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)) -> list[PendingDevice]: result = await db.execute(select(PendingDevice).where(PendingDevice.status == "pending")) return list(result.scalars().all()) @router.get("/hidden", response_model=list[PendingDeviceResponse]) async def list_hidden(db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)) -> list[PendingDevice]: result = await db.execute(select(PendingDevice).where(PendingDevice.status == "hidden")) return list(result.scalars().all()) @router.post("/pending/{device_id}/approve", response_model=dict) async def approve_device( device_id: str, node_data: NodeCreate, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user), ) -> dict[str, Any]: device = await db.get(PendingDevice, device_id) if device: device.status = "approved" node = Node(**node_data.model_dump()) db.add(node) await db.commit() return {"approved": True, "node_id": node.id} return {"approved": False} @router.post("/pending/{device_id}/hide") async def hide_device( device_id: str, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user) ) -> dict[str, bool]: device = await db.get(PendingDevice, device_id) if device: device.status = "hidden" await db.commit() return {"hidden": True} @router.post("/pending/{device_id}/ignore") async def ignore_device( device_id: str, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user) ) -> dict[str, bool]: device = await db.get(PendingDevice, device_id) if device: await db.delete(device) await db.commit() return {"ignored": True} @router.get("/runs", response_model=list[ScanRunResponse]) async def list_runs(db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)) -> list[ScanRun]: result = await db.execute(select(ScanRun).order_by(ScanRun.started_at.desc()).limit(20)) return list(result.scalars().all()) @router.get("/config", response_model=ScanConfig) async def get_scan_config(_: str = Depends(get_current_user)) -> ScanConfig: try: with open(settings.config_path) as f: cfg = yaml.safe_load(f) ranges = cfg.get("scanner", {}).get("ranges", []) interval = int(cfg.get("status_checker", {}).get("interval_seconds", 60)) return ScanConfig(ranges=ranges, interval_seconds=interval) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc @router.post("/config", response_model=ScanConfig) async def update_scan_config(payload: ScanConfig, _: str = Depends(get_current_user)) -> ScanConfig: try: with open(settings.config_path) as f: cfg = yaml.safe_load(f) or {} cfg.setdefault("scanner", {})["ranges"] = payload.ranges cfg.setdefault("status_checker", {})["interval_seconds"] = payload.interval_seconds with open(settings.config_path, "w") as f: yaml.dump(cfg, f, default_flow_style=False, allow_unicode=True) return payload except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc