Files
patrickandClaude Sonnet 5.5 347f55cf7c
test / pytest (3.11) (push) Waiting to run
test / pytest (3.13) (push) Waiting to run
feat: Phase 6c baseline, ack, host alarms, outage notice, send limit, privacy mode, UI link
Includes atomic migrations, per-rule reminder semantics, first-run helper script
and the note on the undecided UI chat idea.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 03:06:23 +01:00

77 lines
3.5 KiB
Python

from ..chat import MessageRef
from ..db import Database
from ..models import (AI_PENDING, AI_SKIPPED, HOST_SERVICE_NAME, KIND_FLAPPING, KIND_PROBLEM, KIND_RECOVERY,
KIND_REMINDER, STATE_OK, Event)
RECENT_LIMIT = 200
DAY_SECONDS = 86400
INSERT_ALERT_SQL = (
"INSERT INTO alerts(ts, host, service, kind, state, output, suppressed, suppress_reason, "
"ai_status, message_group) VALUES (?,?,?,?,?,?,?,?,?,?)"
)
class AlertRepository:
def __init__(self, db: Database):
self._db = db
def insert_alerts(self, events: list[Event], message_group: str) -> list[int]:
return [
self._db.insert(INSERT_ALERT_SQL, (
e.ts, e.host, e.service, e.kind, e.state, e.output, int(e.suppressed),
e.suppress_reason, AI_PENDING if e.wants_ai else AI_SKIPPED, message_group,
))
for e in events
]
def add_delivery(self, alert_id: int, ref: MessageRef) -> None:
self._db.execute(
"INSERT INTO deliveries(alert_id, backend, target, message_id) VALUES (?,?,?,?)",
(alert_id, ref.backend, ref.target, ref.message_id),
)
def set_ai(self, alert_id: int, status: str, text: str | None) -> None:
self._db.execute("UPDATE alerts SET ai_status=?, ai_text=? WHERE id=?", (status, text, alert_id))
def services_for_message(self, backend: str, target: str, message_id: str) -> list[tuple[str, str]]:
rows = self._db.query(
"SELECT DISTINCT a.host, a.service FROM deliveries d JOIN alerts a ON a.id=d.alert_id "
"WHERE d.backend=? AND d.target=? AND d.message_id=? ORDER BY a.host, a.service",
(backend, target, message_id),
)
return [(r["host"], r["service"]) for r in rows]
def service_for_alert(self, alert_id: int) -> tuple[str, str] | None:
rows = self._db.query("SELECT host, service FROM alerts WHERE id=?", (alert_id,))
return (rows[0]["host"], rows[0]["service"]) if rows else None
def report_counts(self, host: str, service: str) -> tuple[int, int]:
rows = self._db.query(
"SELECT COUNT(*) AS total, COALESCE(SUM(CASE WHEN suppressed=0 THEN 1 ELSE 0 END), 0) AS sent "
"FROM alerts WHERE host=? AND service=? AND kind IN (?,?,?) AND id > "
"COALESCE((SELECT MAX(id) FROM alerts WHERE host=? AND service=? AND kind=?), 0)",
(host, service, KIND_PROBLEM, KIND_FLAPPING, KIND_REMINDER, host, service, KIND_RECOVERY),
)
return rows[0]["total"], rows[0]["sent"]
def recent(self, limit: int = RECENT_LIMIT) -> list[dict]:
rows = self._db.query("SELECT * FROM alerts ORDER BY ts DESC, id DESC LIMIT ?", (limit,))
return [dict(row) for row in rows]
def get(self, alert_id: int) -> dict | None:
rows = self._db.query("SELECT * FROM alerts WHERE id=?", (alert_id,))
return dict(rows[0]) if rows else None
def count_since(self, since_ts: int) -> int:
return self._db.query("SELECT COUNT(*) AS n FROM alerts WHERE ts>=?", (since_ts,))[0]["n"]
def open_problems(self) -> list[dict]:
rows = self._db.query(
"SELECT host, service, state, output, last_state_change, in_downtime, flapping, reported_state, "
"flap_reported, ack_state, host_held "
"FROM service_states WHERE state!=? ORDER BY (service=?) DESC, state DESC, host, service",
(STATE_OK, HOST_SERVICE_NAME),
)
return [dict(row) for row in rows]