satdetect-dev / app /dda /admin_routes.py
coderuday21's picture
Implement unlimited-depth tree library architecture for dev DDA.
214c544
Raw
History Blame Contribute Delete
3.38 kB
"""DDA admin diagnostics and maintenance (Phase 7)."""
from __future__ import annotations
import logging
import shutil
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.orm import Session
from ..database import DATA_DIR, get_db
from ..models import DetectionRun, User
from .dda_auth import current_dda_user, get_user_role, require_min_role
from .job_runner import is_job_runner_busy, reconcile_stale_jobs
from .tree.image_service import list_all_images
from .models import DetectionJob, RegionReview
logger = logging.getLogger(__name__)
router = APIRouter()
def _require_dda():
from .config import IS_DDA_MODE
if not IS_DDA_MODE:
raise HTTPException(status_code=404, detail="DDA mode is not enabled")
@router.get("/admin/status")
def admin_status(
request: Request,
db: Session = Depends(get_db),
user: User = Depends(current_dda_user),
):
"""System status for UAT / ops (analyst+)."""
_require_dda()
require_min_role(user, db, "analyst")
runs = db.query(DetectionRun).count()
jobs_queued = db.query(DetectionJob).filter(DetectionJob.status == "queued").count()
jobs_running = db.query(DetectionJob).filter(DetectionJob.status == "running").count()
fp_count = db.query(RegionReview).filter(RegionReview.status == "false_positive").count()
disk = shutil.disk_usage(DATA_DIR)
return {
"role": get_user_role(db, user),
"libraryImages": len(list_all_images(db)),
"detectionRuns": runs,
"jobsQueued": jobs_queued,
"jobsRunning": jobs_running,
"runnerBusy": is_job_runner_busy(),
"falsePositiveArchive": fp_count,
"dataDir": str(DATA_DIR),
"diskFreeGb": round(disk.free / (1024 ** 3), 2),
"diskTotalGb": round(disk.total / (1024 ** 3), 2),
}
@router.post("/admin/reconcile-jobs")
def admin_reconcile_jobs(
request: Request,
db: Session = Depends(get_db),
user: User = Depends(current_dda_user),
):
_require_dda()
require_min_role(user, db, "admin")
fixed = reconcile_stale_jobs(db)
return {"ok": True, "staleRunningFixed": fixed}
@router.delete("/admin/purge-runs")
def admin_purge_old_runs(
request: Request,
days: int = Query(90, ge=7, le=3650),
db: Session = Depends(get_db),
user: User = Depends(current_dda_user),
):
"""Delete detection runs older than N days (admin only)."""
_require_dda()
require_min_role(user, db, "admin")
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
old = db.query(DetectionRun).filter(DetectionRun.created_at < cutoff).all()
count = 0
overlays_root = DATA_DIR
for run in old:
db.query(RegionReview).filter(RegionReview.run_id == run.id).delete()
for job in db.query(DetectionJob).filter(DetectionJob.run_id == run.id).all():
job.run_id = None
for path_attr in ("overlay_path", "before_full_path", "before_thumb_path", "after_thumb_path", "after_full_path"):
path_val = getattr(run, path_attr, None)
if path_val:
f = overlays_root / path_val
if f.exists():
f.unlink(missing_ok=True)
db.delete(run)
count += 1
db.commit()
return {"ok": True, "purgedRuns": count, "olderThanDays": days}