Spaces:
Sleeping
Sleeping
File size: 3,382 Bytes
ac7ea7c 214c544 ac7ea7c 214c544 ac7ea7c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """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}
|