Files
homelable/backend/app/api/routes/scan.py
T
Pouzor 974e782057 fix: resolve all 64 mypy errors across backend
- Add dict[str, Any] / list[Any] type params throughout (fingerprint, models, schemas, scanner, status_checker)
- Add return type annotations to all route functions (nodes, edges, canvas, scan, auth, status, main)
- Fix no-any-return in security.py: cast pwd/jwt results to bool/str explicitly
- Fix canvas.py: use model_validate() for NodeResponse/EdgeResponse, rename db_node/db_edge upsert vars
- Fix scheduler.py: rename 'result' → 'check_result' to avoid type collision
- Fix get_db() return type: AsyncGenerator[AsyncSession, None]
- Add types-PyYAML for yaml import stubs
- Fix scanner.py: remove unnecessary type: ignore comment (nmap has stubs)
- Fix scan.py: wrap scalars().all() with list() for Sequence→list compatibility
2026-03-07 15:48:41 +01:00

137 lines
4.8 KiB
Python

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