Spaces:
Sleeping
Sleeping
| """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") | |
| 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), | |
| } | |
| 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} | |
| 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} | |