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>
50 lines
1.9 KiB
SQL
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
|