feat: global notification policy severity to channels (notif-22)

Singleton notification_policy table, NOTIFY_MIN_SEVERITY/CHANNELS env,
central dispatch gate, Settings policy UI, migration 007.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-05-29 16:19:26 +10:00
parent 9b177c145b
commit ebb450be92
19 changed files with 577 additions and 126 deletions
+9 -13
View File
@@ -10,8 +10,8 @@ from email.mime.text import MIMEText
from sqlalchemy.orm import Session
from app.models import Event, Problem
from app.services.notification_severity import severity_meets_minimum
from app.services.notification_settings import EmailConfig, get_effective_email_config
from app.services.telegram_notify import SEVERITY_ORDER
logger = logging.getLogger(__name__)
@@ -24,14 +24,6 @@ class EmailSendError(Exception):
pass
def _severity_value(value: str) -> int:
return SEVERITY_ORDER.get(value, 0)
def _should_notify_severity(severity: str, *, config: EmailConfig) -> bool:
return _severity_value(severity) >= _severity_value(config.min_severity)
def parse_mail_recipients(mail_to: str) -> list[str]:
return [part.strip() for part in re.split(r"[,;]", mail_to) if part.strip()]
@@ -94,9 +86,11 @@ def send_email_test_message(*, config: EmailConfig | None = None) -> None:
)
def notify_event(event: Event, *, db: Session | None = None) -> None:
def notify_event(event: Event, *, db: Session | None = None, apply_policy_gate: bool = True) -> None:
cfg = get_effective_email_config(db)
if not cfg.active or not _should_notify_severity(event.severity, config=cfg):
if not cfg.active:
return
if apply_policy_gate and not severity_meets_minimum(event.severity, cfg.min_severity):
return
host = event.host.hostname if event.host else "unknown"
body = (
@@ -113,9 +107,11 @@ def notify_event(event: Event, *, db: Session | None = None) -> None:
logger.exception("notify_event email failed event_id=%s", event.event_id)
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None) -> None:
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None, apply_policy_gate: bool = True) -> None:
cfg = get_effective_email_config(db)
if not cfg.active or not _should_notify_severity(problem.severity, config=cfg):
if not cfg.active:
return
if apply_policy_gate and not severity_meets_minimum(problem.severity, cfg.min_severity):
return
host = problem.host.hostname if problem.host else "unknown"
related = ""