Files
Debian 81a8217493 feat(alerts): Health-Alarme via Webhook + Email-SMTP
Sidebar → System → Alarme.

Migration 0021: alert_channels (kind=webhook|email, target, settings,
active) + alert_events (kind, severity=info/warning/error/critical,
subject, message, sent_to JSONB).

internal/services/alerts/:
  - Fire(kind, severity, subject, message) — broadcastet an alle
    aktiven Channels + persistiert Event mit per-Channel-Result
    (ok/error) in sent_to.
  - Webhook-Sender: POST JSON {kind, severity, subject, message,
    content, text, fired_at}. Slack/Discord/Teams akzeptieren das
    out-of-the-box ohne Adapter (content + text-Felder gleichzeitig).
  - Email-Sender: net/smtp + STARTTLS optional. Settings (smtp_host,
    smtp_port, username/password, from, use_tls) liegen in
    channel.settings JSONB.

internal/handlers/alerts.go: CRUD + POST /alerts/test + GET
/alerts/events (history).

Scheduler-Trigger:
  - cert.expiring  — TLS-Cert <14 Tage Restzeit (12h-dedupe pro cert)
                     severity warning, <3 Tage → error
  - cert.renew_failed       — Renewer-Cycle hat fails
  - cert.renewer.run_failed — Renewer-Cycle abgebrochen
  - backup.failed  — Scheduled Backup error
  - license.invalid — License-Server liefert valid=false

In-process Dedupe (12h TTL, map[key]time.Time) verhindert dass
identische Alerts in Schleifen feuern.

UI (pages/Alerts): Tabs Channels (CRUD-Tabelle, Add-Modal mit
conditional-Email-Fields) + History (200 letzte Events mit
severity-Tag + per-Channel-Delivery-Status). Header-Button
„Test-Alert" feuert einen Test-Event in alle aktiven Channels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:57:05 +02:00

50 lines
1.9 KiB
SQL

-- +goose Up
-- +goose StatementBegin
-- Health-Alarme: Webhook + Email-Notification bei kritischen Events.
-- Triggers leben im edgeguard-scheduler (cert-expiry, backup-fail,
-- license-verify-fail). Pro Event wird optional in jeden aktiven
-- Channel gepushed; das Ergebnis (success/error) landet in alert_events
-- damit der Operator-UI History+Failure-Reason zeigen kann.
CREATE TABLE IF NOT EXISTS alert_channels (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
kind TEXT NOT NULL,
target TEXT NOT NULL, -- webhook-URL oder "to"-Email
settings JSONB NOT NULL DEFAULT '{}'::jsonb,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT alert_channels_kind_check
CHECK (kind IN ('webhook', 'email'))
);
CREATE INDEX IF NOT EXISTS idx_alert_channels_active
ON alert_channels (active) WHERE active;
CREATE TABLE IF NOT EXISTS alert_events (
id BIGSERIAL PRIMARY KEY,
kind TEXT NOT NULL, -- cert.expiring | backup.failed | license.invalid | test
severity TEXT NOT NULL, -- info | warning | error | critical
subject TEXT NOT NULL,
message TEXT NOT NULL,
sent_to JSONB NOT NULL DEFAULT '[]'::jsonb, -- [{channel_id, ok, error}, ...]
fired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT alert_events_severity_check
CHECK (severity IN ('info', 'warning', 'error', 'critical'))
);
CREATE INDEX IF NOT EXISTS idx_alert_events_fired_at
ON alert_events (fired_at DESC);
CREATE INDEX IF NOT EXISTS idx_alert_events_kind
ON alert_events (kind);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS alert_events;
DROP TABLE IF EXISTS alert_channels;
-- +goose StatementEnd