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:
@@ -5,6 +5,11 @@ from sqlalchemy.orm import Session
|
||||
from app.auth.jwt_auth import get_current_user
|
||||
from app.database import get_db
|
||||
from app.services.email_notify import EmailNotConfiguredError, EmailSendError, send_email_test_message
|
||||
from app.services.notification_policy import (
|
||||
NotificationPolicyConfig,
|
||||
get_effective_notification_policy,
|
||||
upsert_notification_policy,
|
||||
)
|
||||
from app.services.notification_settings import (
|
||||
VALID_SEVERITIES,
|
||||
EmailConfig,
|
||||
@@ -75,22 +80,36 @@ class EmailSettingsResponse(BaseModel):
|
||||
source: str = Field(description="env или db")
|
||||
|
||||
|
||||
class NotificationPolicyResponse(BaseModel):
|
||||
min_severity: str
|
||||
use_telegram: bool
|
||||
use_webhook: bool
|
||||
use_email: bool
|
||||
source: str = Field(description="env или db")
|
||||
|
||||
|
||||
class NotificationSettingsResponse(BaseModel):
|
||||
policy: NotificationPolicyResponse
|
||||
telegram: TelegramSettingsResponse
|
||||
webhook: WebhookSettingsResponse
|
||||
email: EmailSettingsResponse
|
||||
|
||||
|
||||
class NotificationPolicyUpdate(BaseModel):
|
||||
min_severity: str
|
||||
use_telegram: bool
|
||||
use_webhook: bool
|
||||
use_email: bool
|
||||
|
||||
|
||||
class TelegramSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
bot_token: str | None = None
|
||||
chat_id: str | None = None
|
||||
|
||||
|
||||
class WebhookSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
url: str | None = None
|
||||
secret_header: str | None = None
|
||||
secret: str | None = None
|
||||
@@ -98,7 +117,6 @@ class WebhookSettingsUpdate(BaseModel):
|
||||
|
||||
class EmailSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
smtp_host: str | None = None
|
||||
smtp_port: int | None = None
|
||||
smtp_user: str | None = None
|
||||
@@ -114,30 +132,40 @@ class ChannelTestResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
def _telegram_to_response(cfg: TelegramConfig) -> TelegramSettingsResponse:
|
||||
def _policy_to_response(cfg: NotificationPolicyConfig) -> NotificationPolicyResponse:
|
||||
return NotificationPolicyResponse(
|
||||
min_severity=cfg.min_severity,
|
||||
use_telegram=cfg.use_telegram,
|
||||
use_webhook=cfg.use_webhook,
|
||||
use_email=cfg.use_email,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _telegram_to_response(cfg: TelegramConfig, policy: NotificationPolicyConfig) -> TelegramSettingsResponse:
|
||||
return TelegramSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
chat_id_hint=_mask_secret(cfg.chat_id),
|
||||
bot_token_hint=_mask_secret(cfg.bot_token),
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _webhook_to_response(cfg: WebhookConfig) -> WebhookSettingsResponse:
|
||||
def _webhook_to_response(cfg: WebhookConfig, policy: NotificationPolicyConfig) -> WebhookSettingsResponse:
|
||||
return WebhookSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
url_hint=_mask_url(cfg.url),
|
||||
secret_header=cfg.secret_header or None,
|
||||
secret_hint=_mask_secret(cfg.secret),
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _email_to_response(cfg: EmailConfig) -> EmailSettingsResponse:
|
||||
def _email_to_response(cfg: EmailConfig, policy: NotificationPolicyConfig) -> EmailSettingsResponse:
|
||||
return EmailSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
@@ -149,7 +177,7 @@ def _email_to_response(cfg: EmailConfig) -> EmailSettingsResponse:
|
||||
mail_to_hint=_mask_secret(cfg.mail_to.replace(";", ",")) if cfg.mail_to else None,
|
||||
smtp_starttls=cfg.smtp_starttls,
|
||||
smtp_ssl=cfg.smtp_ssl,
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
@@ -159,32 +187,55 @@ def get_notification_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> NotificationSettingsResponse:
|
||||
policy = get_effective_notification_policy(db)
|
||||
return NotificationSettingsResponse(
|
||||
telegram=_telegram_to_response(get_effective_telegram_config(db)),
|
||||
webhook=_webhook_to_response(get_effective_webhook_config(db)),
|
||||
email=_email_to_response(get_effective_email_config(db)),
|
||||
policy=_policy_to_response(policy),
|
||||
telegram=_telegram_to_response(get_effective_telegram_config(db), policy),
|
||||
webhook=_webhook_to_response(get_effective_webhook_config(db), policy),
|
||||
email=_email_to_response(get_effective_email_config(db), policy),
|
||||
)
|
||||
|
||||
|
||||
@router.put("/notifications/policy", response_model=NotificationPolicyResponse)
|
||||
def update_notification_policy(
|
||||
body: NotificationPolicyUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> NotificationPolicyResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
if not any((body.use_telegram, body.use_webhook, body.use_email)):
|
||||
raise HTTPException(status_code=422, detail="Выберите хотя бы один канал")
|
||||
try:
|
||||
policy = upsert_notification_policy(
|
||||
db,
|
||||
min_severity=body.min_severity,
|
||||
use_telegram=body.use_telegram,
|
||||
use_webhook=body.use_webhook,
|
||||
use_email=body.use_email,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _policy_to_response(policy)
|
||||
|
||||
|
||||
@router.put("/notifications/telegram", response_model=TelegramSettingsResponse)
|
||||
def update_telegram_settings(
|
||||
body: TelegramSettingsUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> TelegramSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_telegram_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
bot_token=body.bot_token,
|
||||
chat_id=body.chat_id,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _telegram_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _telegram_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.put("/notifications/webhook", response_model=WebhookSettingsResponse)
|
||||
@@ -193,20 +244,18 @@ def update_webhook_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> WebhookSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_webhook_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
url=body.url,
|
||||
secret_header=body.secret_header,
|
||||
secret=body.secret,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _webhook_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _webhook_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.put("/notifications/email", response_model=EmailSettingsResponse)
|
||||
@@ -215,13 +264,10 @@ def update_email_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EmailSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_email_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
smtp_host=body.smtp_host,
|
||||
smtp_port=body.smtp_port,
|
||||
smtp_user=body.smtp_user,
|
||||
@@ -233,7 +279,8 @@ def update_email_settings(
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _email_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _email_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.post("/notifications/telegram/test", response_model=ChannelTestResponse)
|
||||
|
||||
@@ -67,6 +67,9 @@ class Settings(BaseSettings):
|
||||
smtp_ssl: bool = False
|
||||
smtp_min_severity: str = "high"
|
||||
|
||||
notify_min_severity: str = "warning"
|
||||
notify_channels: str = "telegram,webhook,email"
|
||||
|
||||
# Порог «живости» агента
|
||||
sac_heartbeat_stale_minutes: int = 780
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from app.models.api_key import ApiKey
|
||||
from app.models.event import Event
|
||||
from app.models.host import Host
|
||||
from app.models.notification_channel import NotificationChannel
|
||||
from app.models.notification_policy import NotificationPolicy
|
||||
from app.models.problem import Problem, ProblemEvent
|
||||
|
||||
__all__ = ["ApiKey", "Event", "Host", "NotificationChannel", "Problem", "ProblemEvent"]
|
||||
__all__ = ["ApiKey", "Event", "Host", "NotificationChannel", "NotificationPolicy", "Problem", "ProblemEvent"]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from sqlalchemy import Boolean, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
POLICY_ROW_ID = 1
|
||||
|
||||
|
||||
class NotificationPolicy(Base):
|
||||
"""Singleton: глобальное правило severity → каналы (F-NOT-02 урезанно)."""
|
||||
|
||||
__tablename__ = "notification_policy"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
min_severity: Mapped[str] = mapped_column(String(16), default="warning")
|
||||
use_telegram: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
use_webhook: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
use_email: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Global notification policy: severity ≥ min → selected channels."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models.notification_channel import NotificationChannel
|
||||
from app.models.notification_policy import POLICY_ROW_ID, NotificationPolicy
|
||||
from app.services.notification_settings import (
|
||||
CHANNEL_EMAIL,
|
||||
CHANNEL_TELEGRAM,
|
||||
CHANNEL_WEBHOOK,
|
||||
VALID_SEVERITIES,
|
||||
)
|
||||
|
||||
DEFAULT_CHANNELS = frozenset({CHANNEL_TELEGRAM, CHANNEL_WEBHOOK, CHANNEL_EMAIL})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NotificationPolicyConfig:
|
||||
min_severity: str
|
||||
use_telegram: bool
|
||||
use_webhook: bool
|
||||
use_email: bool
|
||||
source: str # env | db
|
||||
|
||||
def allows_channel(self, channel: str) -> bool:
|
||||
if channel == CHANNEL_TELEGRAM:
|
||||
return self.use_telegram
|
||||
if channel == CHANNEL_WEBHOOK:
|
||||
return self.use_webhook
|
||||
if channel == CHANNEL_EMAIL:
|
||||
return self.use_email
|
||||
return False
|
||||
|
||||
|
||||
def _parse_channels_csv(value: str) -> tuple[bool, bool, bool]:
|
||||
parts = {p.strip().lower() for p in value.split(",") if p.strip()}
|
||||
return (
|
||||
CHANNEL_TELEGRAM in parts or "tg" in parts,
|
||||
CHANNEL_WEBHOOK in parts,
|
||||
CHANNEL_EMAIL in parts or "mail" in parts or "smtp" in parts,
|
||||
)
|
||||
|
||||
|
||||
def _policy_from_env() -> NotificationPolicyConfig:
|
||||
settings = get_settings()
|
||||
use_tg, use_wh, use_em = _parse_channels_csv(settings.notify_channels)
|
||||
min_sev = settings.notify_min_severity.strip() or "warning"
|
||||
if min_sev not in VALID_SEVERITIES:
|
||||
min_sev = "warning"
|
||||
return NotificationPolicyConfig(
|
||||
min_severity=min_sev,
|
||||
use_telegram=use_tg,
|
||||
use_webhook=use_wh,
|
||||
use_email=use_em,
|
||||
source="env",
|
||||
)
|
||||
|
||||
|
||||
def get_effective_notification_policy(db: Session | None = None) -> NotificationPolicyConfig:
|
||||
if db is None:
|
||||
from app.database import SessionLocal
|
||||
|
||||
session = SessionLocal()
|
||||
try:
|
||||
return get_effective_notification_policy(session)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
row = db.get(NotificationPolicy, POLICY_ROW_ID)
|
||||
env_cfg = _policy_from_env()
|
||||
if row is None:
|
||||
return env_cfg
|
||||
|
||||
min_sev = (row.min_severity or "").strip() or env_cfg.min_severity
|
||||
if min_sev not in VALID_SEVERITIES:
|
||||
min_sev = env_cfg.min_severity
|
||||
|
||||
return NotificationPolicyConfig(
|
||||
min_severity=min_sev,
|
||||
use_telegram=bool(row.use_telegram),
|
||||
use_webhook=bool(row.use_webhook),
|
||||
use_email=bool(row.use_email),
|
||||
source="db",
|
||||
)
|
||||
|
||||
|
||||
def _sync_channel_min_severity(db: Session, min_severity: str) -> None:
|
||||
for channel in (CHANNEL_TELEGRAM, CHANNEL_WEBHOOK, CHANNEL_EMAIL):
|
||||
row = db.get(NotificationChannel, channel)
|
||||
if row is not None:
|
||||
row.min_severity = min_severity
|
||||
|
||||
|
||||
def upsert_notification_policy(
|
||||
db: Session,
|
||||
*,
|
||||
min_severity: str,
|
||||
use_telegram: bool,
|
||||
use_webhook: bool,
|
||||
use_email: bool,
|
||||
) -> NotificationPolicyConfig:
|
||||
if min_severity not in VALID_SEVERITIES:
|
||||
raise ValueError(f"invalid min_severity: {min_severity}")
|
||||
|
||||
row = db.get(NotificationPolicy, POLICY_ROW_ID)
|
||||
if row is None:
|
||||
row = NotificationPolicy(
|
||||
id=POLICY_ROW_ID,
|
||||
min_severity=min_severity,
|
||||
use_telegram=use_telegram,
|
||||
use_webhook=use_webhook,
|
||||
use_email=use_email,
|
||||
)
|
||||
db.add(row)
|
||||
else:
|
||||
row.min_severity = min_severity
|
||||
row.use_telegram = use_telegram
|
||||
row.use_webhook = use_webhook
|
||||
row.use_email = use_email
|
||||
|
||||
_sync_channel_min_severity(db, min_severity)
|
||||
db.commit()
|
||||
db.refresh(row)
|
||||
return get_effective_notification_policy(db)
|
||||
@@ -0,0 +1,11 @@
|
||||
"""Severity ordering for notification policy gates."""
|
||||
|
||||
SEVERITY_ORDER = {"info": 10, "warning": 20, "high": 30, "critical": 40}
|
||||
|
||||
|
||||
def severity_value(value: str) -> int:
|
||||
return SEVERITY_ORDER.get(value, 0)
|
||||
|
||||
|
||||
def severity_meets_minimum(severity: str, minimum: str) -> bool:
|
||||
return severity_value(severity) >= severity_value(minimum)
|
||||
@@ -1,18 +1,33 @@
|
||||
"""Dispatch ingest notifications to all configured channels."""
|
||||
"""Dispatch ingest notifications per global policy (severity → channels)."""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import Event, Problem
|
||||
from app.services import email_notify, telegram_notify, webhook_notify
|
||||
from app.services.notification_policy import get_effective_notification_policy
|
||||
from app.services.notification_severity import severity_meets_minimum
|
||||
from app.services.notification_settings import CHANNEL_EMAIL, CHANNEL_TELEGRAM, CHANNEL_WEBHOOK
|
||||
|
||||
|
||||
def notify_event(event: Event, *, db: Session | None = None) -> None:
|
||||
telegram_notify.notify_event(event, db=db)
|
||||
webhook_notify.notify_event(event, db=db)
|
||||
email_notify.notify_event(event, db=db)
|
||||
policy = get_effective_notification_policy(db)
|
||||
if not severity_meets_minimum(event.severity, policy.min_severity):
|
||||
return
|
||||
if policy.use_telegram:
|
||||
telegram_notify.notify_event(event, db=db, apply_policy_gate=False)
|
||||
if policy.use_webhook:
|
||||
webhook_notify.notify_event(event, db=db, apply_policy_gate=False)
|
||||
if policy.use_email:
|
||||
email_notify.notify_event(event, db=db, apply_policy_gate=False)
|
||||
|
||||
|
||||
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None) -> None:
|
||||
telegram_notify.notify_problem(problem, event, db=db)
|
||||
webhook_notify.notify_problem(problem, event, db=db)
|
||||
email_notify.notify_problem(problem, event, db=db)
|
||||
policy = get_effective_notification_policy(db)
|
||||
if not severity_meets_minimum(problem.severity, policy.min_severity):
|
||||
return
|
||||
if policy.use_telegram:
|
||||
telegram_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
|
||||
if policy.use_webhook:
|
||||
webhook_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
|
||||
if policy.use_email:
|
||||
email_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
|
||||
|
||||
@@ -4,12 +4,11 @@ import httpx
|
||||
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 TelegramConfig, get_effective_telegram_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SEVERITY_ORDER = {"info": 10, "warning": 20, "high": 30, "critical": 40}
|
||||
|
||||
|
||||
class TelegramNotConfiguredError(Exception):
|
||||
"""Telegram disabled or missing token/chat_id."""
|
||||
@@ -21,10 +20,6 @@ class TelegramSendError(Exception):
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
def _severity_value(value: str) -> int:
|
||||
return SEVERITY_ORDER.get(value, 0)
|
||||
|
||||
|
||||
def send_telegram_text(message: str, *, config: TelegramConfig | None = None, force: bool = False) -> None:
|
||||
cfg = config or get_effective_telegram_config()
|
||||
if not force and not cfg.active:
|
||||
@@ -61,14 +56,11 @@ def send_telegram_test_message(*, config: TelegramConfig | None = None) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _should_notify_severity(severity: str, *, config: TelegramConfig) -> bool:
|
||||
min_level = _severity_value(config.min_severity)
|
||||
return _severity_value(severity) >= min_level
|
||||
|
||||
|
||||
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_telegram_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"
|
||||
message = (
|
||||
@@ -85,9 +77,11 @@ def notify_event(event: Event, *, db: Session | None = None) -> None:
|
||||
logger.exception("notify_event telegram 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_telegram_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 = ""
|
||||
|
||||
@@ -9,8 +9,8 @@ import httpx
|
||||
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 WebhookConfig, get_effective_webhook_config
|
||||
from app.services.telegram_notify import SEVERITY_ORDER
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,14 +25,6 @@ class WebhookSendError(Exception):
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
def _severity_value(value: str) -> int:
|
||||
return SEVERITY_ORDER.get(value, 0)
|
||||
|
||||
|
||||
def _should_notify_severity(severity: str, *, config: WebhookConfig) -> bool:
|
||||
return _severity_value(severity) >= _severity_value(config.min_severity)
|
||||
|
||||
|
||||
def _host_payload(entity: Event | Problem) -> dict[str, Any]:
|
||||
host = entity.host
|
||||
if host is None:
|
||||
@@ -120,9 +112,11 @@ def send_webhook_test_message(*, config: WebhookConfig | 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_webhook_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
|
||||
try:
|
||||
send_webhook_payload(build_event_payload(event), config=cfg)
|
||||
@@ -130,9 +124,11 @@ def notify_event(event: Event, *, db: Session | None = None) -> None:
|
||||
logger.exception("notify_event webhook 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_webhook_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
|
||||
try:
|
||||
send_webhook_payload(build_problem_payload(problem, event), config=cfg)
|
||||
|
||||
Reference in New Issue
Block a user