Files
ThreatHunt/backend/app/api/routes/reports.py
mblanke 9b98ab9614 feat: interactive network map, IOC highlighting, AUP hunt selector, type filters
- NetworkMap: hunt-scoped force-directed graph with click-to-inspect popover
- NetworkMap: zoom/pan (wheel, drag, buttons), viewport transform
- NetworkMap: clickable IP/Host/Domain/URL legend chips to filter node types
- NetworkMap: brighter colors, 20% smaller nodes
- DatasetViewer: IOC columns highlighted with colored headers + cell tinting
- AUPScanner: hunt dropdown replacing dataset checkboxes, auto-select all
- Rename 'Social Media (Personal)' theme to 'Social Media' with DB migration
- Fix /api/hunts timeout: Dataset.rows lazy='noload' (was selectin cascade)
- Add OS column mapping to normalizer
- Full backend services, DB models, alembic migrations, new routes
- New components: Dashboard, HuntManager, FileUpload, NetworkMap, etc.
- Docker Compose deployment with nginx reverse proxy
2026-02-19 15:41:15 -05:00

68 lines
2.2 KiB
Python

"""API routes for report generation and export."""
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import HTMLResponse, PlainTextResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import get_db
from app.services.reports import report_generator
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/reports", tags=["reports"])
@router.get(
"/hunt/{hunt_id}",
summary="Generate hunt investigation report",
description="Generate a comprehensive report for a hunt. Supports JSON, HTML, and CSV formats.",
)
async def generate_hunt_report(
hunt_id: str,
format: str = Query("json", description="Report format: json, html, csv"),
include_rows: bool = Query(False, description="Include raw data rows"),
max_rows: int = Query(500, ge=0, le=5000, description="Max rows to include"),
db: AsyncSession = Depends(get_db),
):
result = await report_generator.generate_hunt_report(
hunt_id, db, format=format,
include_rows=include_rows, max_rows=max_rows,
)
if isinstance(result, dict) and result.get("error"):
raise HTTPException(status_code=404, detail=result["error"])
if format == "html":
return HTMLResponse(content=result, headers={
"Content-Disposition": f"inline; filename=threathunt_report_{hunt_id}.html",
})
elif format == "csv":
return PlainTextResponse(content=result, media_type="text/csv", headers={
"Content-Disposition": f"attachment; filename=threathunt_report_{hunt_id}.csv",
})
else:
return result
@router.get(
"/hunt/{hunt_id}/summary",
summary="Quick hunt summary",
description="Get a lightweight summary of the hunt for dashboard display.",
)
async def hunt_summary(
hunt_id: str,
db: AsyncSession = Depends(get_db),
):
result = await report_generator.generate_hunt_report(
hunt_id, db, format="json", include_rows=False,
)
if isinstance(result, dict) and result.get("error"):
raise HTTPException(status_code=404, detail=result["error"])
return {
"hunt": result.get("hunt"),
"summary": result.get("summary"),
}