feat(alerts): close Phase 13 gaps — hourly job + webhook + in-app notifications
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- Add dispatch_webhook_targeted() to webhook_service for rule-specific delivery - evaluate_all_rules() now dispatches in-app notifications (admins/leads) and webhooks after each alert fires (targeted + global alert.fired broadcast) - APScheduler: _run_alert_evaluation() job registered hourly alongside existing jobs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
"""Webhook dispatch service — outbound HTTP notifications.
|
||||
|
||||
Supported event types:
|
||||
- test.validated — fired when a test reaches "validated" state
|
||||
- test.rejected — fired when a test reaches "rejected" state
|
||||
- campaign.completed — fired when a campaign is completed
|
||||
- campaign.started — fired when a campaign is activated
|
||||
- mitre.synced — fired after MITRE ATT&CK sync completes
|
||||
- test.validated — fired when a test reaches "validated" state
|
||||
- test.rejected — fired when a test reaches "rejected" state
|
||||
- campaign.completed — fired when a campaign is completed
|
||||
- campaign.started — fired when a campaign is activated
|
||||
- mitre.synced — fired after MITRE ATT&CK sync completes
|
||||
- technique.status_changed — fired when a technique's status changes
|
||||
- webhook.test — manual test ping from the admin UI
|
||||
- webhook.test — manual test ping from the admin UI
|
||||
- alert.fired — fired when an operational alert is triggered
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
@@ -29,6 +30,28 @@ logger = logging.getLogger(__name__)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def dispatch_webhook_targeted(webhook_id: str, event_type: str, payload: dict) -> None:
|
||||
"""Send to a single specific WebhookConfig by ID (used by alert rules).
|
||||
|
||||
Opens its own DB session. All exceptions are caught.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
wh = db.query(WebhookConfig).filter(
|
||||
WebhookConfig.id == webhook_id,
|
||||
WebhookConfig.is_active == True, # noqa: E712
|
||||
).first()
|
||||
if wh:
|
||||
_send_webhook(db, wh, event_type, payload)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"dispatch_webhook_targeted: error for webhook_id=%s event=%s",
|
||||
webhook_id, event_type,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def dispatch_webhook(event_type: str, payload: dict) -> None:
|
||||
"""Send an outbound webhook to all active subscribers for *event_type*.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user