Add group_alerts setting for per-run summary notifications

Allow choosing between one alert per item (default) and one grouped summary alert per run when any items need attention via general.group_alerts.
This commit is contained in:
Clarth 2026-11-13 20:37:28 -05:00
parent 73b6d38113
commit 674d0df703
4 changed files with 27 additions and 1 deletions

View file

@ -59,6 +59,7 @@ There are two config files. Both are ignored by git, so your values stay on your
| --- | --- | --- | | --- | --- | --- |
| `general.realert_hours` | `12` | Hours before an open item is alerted again. `0` turns repeats off. | | `general.realert_hours` | `12` | Hours before an open item is alerted again. `0` turns repeats off. |
| `general.notify_cleared` | `true` | Send a message when an item is cleared. | | `general.notify_cleared` | `true` | Send a message when an item is cleared. |
| `general.group_alerts` | `false` | `false`: one alert per item. `true`: one summary alert per run when anything needs attention. |
| `general.request_timeout` | `15` | Seconds to wait for each HTTP request. | | `general.request_timeout` | `15` | Seconds to wait for each HTTP request. |
| `general.state_file` | `state.db` | Where the alert history is stored. | | `general.state_file` | `state.db` | Where the alert history is stored. |
| `general.notifiers` | `["fluxer"]` | Notifiers to use. | | `general.notifiers` | `["fluxer"]` | Notifiers to use. |

View file

@ -9,6 +9,9 @@ realert_hours = 12
# Send a message when an item is no longer open (cleared in the admin UI). # Send a message when an item is no longer open (cleared in the admin UI).
notify_cleared = true notify_cleared = true
# Send one alert per item (false) or one summary alert per run when anything needs attention (true).
group_alerts = false
# Timeout in seconds for each request to the Fluxer API or webhook. # Timeout in seconds for each request to the Fluxer API or webhook.
request_timeout = 15 request_timeout = 15

View file

@ -38,6 +38,7 @@ class Settings:
request_timeout: float request_timeout: float
realert: timedelta | None realert: timedelta | None
notify_cleared: bool notify_cleared: bool
group_alerts: bool
state_file: Path state_file: Path
notifiers: tuple[str, ...] notifiers: tuple[str, ...]
fluxer_webhook_url: str | None fluxer_webhook_url: str | None
@ -90,6 +91,7 @@ def load_settings(config_path: Path) -> Settings:
request_timeout=float(general.get("request_timeout", 15)), request_timeout=float(general.get("request_timeout", 15)),
realert=timedelta(hours=realert_hours) if realert_hours > 0 else None, realert=timedelta(hours=realert_hours) if realert_hours > 0 else None,
notify_cleared=bool(general.get("notify_cleared", True)), notify_cleared=bool(general.get("notify_cleared", True)),
group_alerts=bool(general.get("group_alerts", False)),
state_file=PROJECT_ROOT / str(general.get("state_file", "state.db")), state_file=PROJECT_ROOT / str(general.get("state_file", "state.db")),
notifiers=notifiers, notifiers=notifiers,
fluxer_webhook_url=fluxer_webhook_url, fluxer_webhook_url=fluxer_webhook_url,

View file

@ -30,6 +30,23 @@ def _to_message(notice: Notice, admin_ui_url: str) -> Message:
) )
def _group_messages(notices: list[Notice], admin_ui_url: str) -> list[Message]:
if not notices:
return []
lines: list[str] = []
urgent = False
for notice in notices:
label = KIND_PREFIX[notice.kind]
detail = notice.detail.replace("\n", " / ")
lines.append(f"{label}: {notice.title} - {detail}")
urgent = urgent or notice.urgent
title = f"Admin alerts: {len(notices)} item{'s' if len(notices) != 1 else ''} need attention"
body = "\n".join(lines)
if len(body) > 4000:
body = body[:3997] + "..."
return [Message(title=title, body=body, link=admin_ui_url, urgent=urgent)]
def run(settings: Settings, dry_run: bool = False) -> int: def run(settings: Settings, dry_run: bool = False) -> int:
api = FluxerAPI(settings.api_url, settings.api_key, settings.request_timeout) api = FluxerAPI(settings.api_url, settings.api_key, settings.request_timeout)
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
@ -57,6 +74,9 @@ def run(settings: Settings, dry_run: bool = False) -> int:
try: try:
known = store.load() known = store.load()
plan = decide(now, seen, known, checked, settings.realert, settings.notify_cleared) plan = decide(now, seen, known, checked, settings.realert, settings.notify_cleared)
if settings.group_alerts:
messages = _group_messages(plan.notices, settings.admin_ui_url)
else:
messages = [_to_message(n, settings.admin_ui_url) for n in plan.notices] messages = [_to_message(n, settings.admin_ui_url) for n in plan.notices]
if dry_run: if dry_run: