fix: show RDG flap badge on 302 and 303 events in SAC UI (0.20.5)

Seaca surfaced flap via Problems; the web UI only flagged 303 in DB and had
no visible label. Resolve flap pairs at read time and show badge plus qwinsta
on both sides of 302→303.
This commit is contained in:
PTah
2026-06-20 17:27:41 +10:00
parent a35e50ccd1
commit 01d72d0f68
15 changed files with 249 additions and 17 deletions
+15 -3
View File
@@ -1,11 +1,21 @@
from sqlalchemy.orm import Session
from app.models.event import Event
from app.schemas.list_models import EventSummary
from app.services.event_actor_user import extract_event_actor_user
from app.services.rdg_session_flap import event_has_rdg_flap
from app.services.rdg_session_flap import resolve_rdg_flap_summary
def event_to_summary(event: Event) -> EventSummary:
def event_to_summary(event: Event, db: Session | None = None) -> EventSummary:
host = event.host
rdg_flap = False
rdg_flap_pair_event_id: int | None = None
rdg_flap_qwinsta_event_id: int | None = None
if db is not None:
rdg_flap, rdg_flap_pair_event_id, rdg_flap_qwinsta_event_id = resolve_rdg_flap_summary(
db, event
)
return EventSummary(
id=event.id,
event_id=event.event_id,
@@ -21,5 +31,7 @@ def event_to_summary(event: Event) -> EventSummary:
title=event.title,
summary=event.summary,
actor_user=extract_event_actor_user(event.type, event.details),
rdg_flap=event_has_rdg_flap(event),
rdg_flap=rdg_flap,
rdg_flap_pair_event_id=rdg_flap_pair_event_id,
rdg_flap_qwinsta_event_id=rdg_flap_qwinsta_event_id,
)
+72
View File
@@ -122,3 +122,75 @@ def evaluate_rdg_session_flap(db: Session, event: Event) -> RuleMatch | None:
def event_has_rdg_flap(event: Event) -> bool:
details = event.details if isinstance(event.details, dict) else {}
return details.get("rdg_flap") is True
def _stored_flap_pair_id(event: Event) -> int | None:
details = event.details if isinstance(event.details, dict) else {}
raw = details.get("rdg_flap_pair_event_id")
if raw is None:
return None
try:
return int(raw)
except (TypeError, ValueError):
return None
def find_rdg_end_after_success(db: Session, success_event: Event) -> Event | None:
"""303 с rdg_flap, у которого пара — этот 302 (или вычисляется по окну)."""
if success_event.type != RDG_SUCCESS_TYPE:
return None
settings = get_settings()
min_sec = settings.sac_rdg_flap_window_min_sec
max_sec = settings.sac_rdg_flap_window_max_sec
start_at = _as_utc(success_event.occurred_at)
window_start = start_at + timedelta(seconds=min_sec)
window_end = start_at + timedelta(seconds=max_sec)
candidates = db.scalars(
select(Event)
.where(
Event.host_id == success_event.host_id,
Event.type.in_(RDG_END_TYPES),
Event.occurred_at >= window_start,
Event.occurred_at <= window_end,
Event.id != success_event.id,
)
.order_by(Event.occurred_at.asc())
).all()
for end in candidates:
if not _users_match(end, success_event):
continue
if not _internal_ips_compatible(end, success_event):
continue
pair_id = _stored_flap_pair_id(end)
if pair_id == success_event.id:
return end
prior = find_rdg_success_before_end(db, end)
if prior is not None and prior.id == success_event.id:
return end
return None
def resolve_rdg_flap_summary(
db: Session, event: Event
) -> tuple[bool, int | None, int | None]:
"""
(rdg_flap, pair_event_id, qwinsta_event_id).
qwinsta_event_id — всегда 303; для 302 указывает на связанный end-event.
"""
if event_has_rdg_flap(event):
pair_id = _stored_flap_pair_id(event)
return True, pair_id, event.id
if event.type in RDG_END_TYPES:
prior = find_rdg_success_before_end(db, event)
if prior is not None:
return True, prior.id, event.id
if event.type == RDG_SUCCESS_TYPE:
end = find_rdg_end_after_success(db, event)
if end is not None:
return True, end.id, end.id
return False, None, None