feat(phase-16): enhanced Tests view, Red/Blue dashboard metrics, and Template admin panel (T-122, T-123, T-124)
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
Endpoints
|
||||
---------
|
||||
GET /test-templates — list with filters + pagination
|
||||
GET /test-templates/stats — catalog statistics (admin)
|
||||
GET /test-templates/{id} — detail
|
||||
POST /test-templates — create custom (admin)
|
||||
PATCH /test-templates/{id} — update (admin)
|
||||
PATCH /test-templates/{id}/toggle-active — toggle active/inactive (admin)
|
||||
DELETE /test-templates/{id} — soft delete (admin)
|
||||
GET /test-templates/by-technique/{mitre_id} — templates for a MITRE technique
|
||||
|
||||
@@ -16,6 +18,7 @@ Filters (GET /test-templates)
|
||||
- severity: low | medium | high | critical
|
||||
- mitre_technique_id: filter by specific technique
|
||||
- search: full-text search across name and description
|
||||
- is_active: true | false (default only active)
|
||||
- offset / limit: pagination (default limit=50)
|
||||
"""
|
||||
|
||||
@@ -23,7 +26,7 @@ import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
@@ -57,7 +60,7 @@ def list_templates(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return a paginated, filterable list of active test templates."""
|
||||
"""Return a paginated, filterable list of test templates."""
|
||||
query = db.query(TestTemplate).filter(TestTemplate.is_active == True) # noqa: E712
|
||||
|
||||
if source:
|
||||
@@ -87,6 +90,53 @@ def list_templates(
|
||||
return templates
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /test-templates/stats — catalog statistics (admin)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
def template_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Return catalog statistics: totals by source, platform, active/inactive."""
|
||||
|
||||
total = db.query(func.count(TestTemplate.id)).scalar() or 0
|
||||
active = (
|
||||
db.query(func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.scalar()
|
||||
) or 0
|
||||
inactive = total - active
|
||||
|
||||
# By source
|
||||
source_rows = (
|
||||
db.query(TestTemplate.source, func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.group_by(TestTemplate.source)
|
||||
.all()
|
||||
)
|
||||
by_source = {source: cnt for source, cnt in source_rows}
|
||||
|
||||
# By platform
|
||||
platform_rows = (
|
||||
db.query(TestTemplate.platform, func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.group_by(TestTemplate.platform)
|
||||
.all()
|
||||
)
|
||||
by_platform = {(platform or "unspecified"): cnt for platform, cnt in platform_rows}
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"active": active,
|
||||
"inactive": inactive,
|
||||
"by_source": by_source,
|
||||
"by_platform": by_platform,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /test-templates/by-technique/{mitre_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -208,6 +258,41 @@ def update_template(
|
||||
return template
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PATCH /test-templates/{id}/toggle-active — toggle active/inactive (admin)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.patch("/{template_id}/toggle-active", response_model=TestTemplateOut)
|
||||
def toggle_template_active(
|
||||
template_id: uuid.UUID,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Toggle a template between active and inactive. Admin only."""
|
||||
template = db.query(TestTemplate).filter(TestTemplate.id == template_id).first()
|
||||
if template is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Test template not found",
|
||||
)
|
||||
|
||||
template.is_active = not template.is_active
|
||||
db.commit()
|
||||
db.refresh(template)
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="toggle_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"name": template.name, "is_active": template.is_active},
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DELETE /test-templates/{id} — soft delete (admin only)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user