feat: SAC 0.7.0 display_name columns and event severity overrides

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-06-01 10:21:43 +10:00
parent 8de5d14cfb
commit de29270a25
25 changed files with 850 additions and 222 deletions
@@ -0,0 +1,84 @@
"""Per event-type severity overrides (admin Settings UI)."""
from __future__ import annotations
from dataclasses import dataclass
from sqlalchemy import delete, select
from sqlalchemy.orm import Session
from app.constants.event_types import DEFAULT_EVENT_SEVERITIES
from app.models.event_severity_override import EventSeverityOverride
from app.services.notification_settings import VALID_SEVERITIES
SOURCE_DB = "db"
@dataclass(frozen=True)
class EventSeverityOverrideItem:
event_type: str
default_severity: str
override_severity: str | None
def get_override_map(db: Session) -> dict[str, str]:
rows = db.scalars(select(EventSeverityOverride)).all()
return {row.event_type: row.severity for row in rows}
def apply_severity_override(payload: dict, db: Session) -> dict:
"""Return payload copy with severity replaced when override exists."""
event_type = str(payload.get("type") or "")
if not event_type:
return payload
override_map = get_override_map(db)
override = override_map.get(event_type)
if not override:
return payload
agent_severity = str(payload.get("severity") or "info")
if agent_severity == override:
return payload
out = dict(payload)
details = dict(out.get("details") or {})
details["severity_agent"] = agent_severity
out["details"] = details
out["severity"] = override
return out
def list_severity_override_items(db: Session) -> list[EventSeverityOverrideItem]:
override_map = get_override_map(db)
types = set(DEFAULT_EVENT_SEVERITIES) | set(override_map)
items: list[EventSeverityOverrideItem] = []
for event_type in sorted(types):
default = DEFAULT_EVENT_SEVERITIES.get(event_type, "info")
items.append(
EventSeverityOverrideItem(
event_type=event_type,
default_severity=default,
override_severity=override_map.get(event_type),
)
)
return items
def replace_severity_overrides(db: Session, overrides: dict[str, str | None]) -> list[EventSeverityOverrideItem]:
for event_type, severity in overrides.items():
normalized_type = event_type.strip()
if not normalized_type:
raise ValueError("event_type is required")
if severity is None or severity == "":
db.execute(
delete(EventSeverityOverride).where(EventSeverityOverride.event_type == normalized_type)
)
continue
if severity not in VALID_SEVERITIES:
raise ValueError(f"severity must be one of: {sorted(VALID_SEVERITIES)}")
row = db.get(EventSeverityOverride, normalized_type)
if row is None:
row = EventSeverityOverride(event_type=normalized_type, severity=severity)
db.add(row)
else:
row.severity = severity
db.flush()
return list_severity_override_items(db)
+20
View File
@@ -0,0 +1,20 @@
from app.models.event import Event
from app.schemas.list_models import EventSummary
def event_to_summary(event: Event) -> EventSummary:
host = event.host
return EventSummary(
id=event.id,
event_id=event.event_id,
host_id=event.host_id,
hostname=host.hostname,
display_name=host.display_name,
occurred_at=event.occurred_at,
received_at=event.received_at,
category=event.category,
type=event.type,
severity=event.severity,
title=event.title,
summary=event.summary,
)
+2
View File
@@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
from app.models import Event, Host
from app.services.daily_report_format import normalize_daily_report_details
from app.services.event_severity_overrides import apply_severity_override
DAILY_REPORT_TYPES = frozenset({"report.daily.ssh", "report.daily.rdp"})
@@ -75,6 +76,7 @@ def ingest_event(db: Session, payload: dict) -> tuple[Event, bool]:
return existing, False
host = upsert_host(db, payload)
payload = apply_severity_override(payload, db)
details = payload.get("details")
if payload.get("type") in DAILY_REPORT_TYPES:
details = normalize_daily_report_details(details, host, payload["type"])