Move the ntfy alert sink to Settings, shared by all alert rules

Alert rules no longer carry their own ntfy topic/server; Settings now
holds one process-wide ntfy sink (with its own "send test notification"),
and the Alerts panel is left to just define rule conditions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 18:13:51 +01:00
co-authored by Claude Sonnet 5
parent ae4e550c1f
commit 18c2d72d22
9 changed files with 148 additions and 127 deletions
+12 -6
View File
@@ -98,6 +98,10 @@ export interface Settings {
max_logs: number | null
/** Id of the selected custom theme entry, or `null` if `color_scheme` alone applies. */
custom_theme_id: string | null
/** Ntfy topic every alert rule delivers to; `null` until configured. */
ntfy_topic: string | null
/** Ntfy server override; `null` means `https://ntfy.sh`. */
ntfy_server: string | null
}
export type CssEntryKind = 'imported' | 'linked'
@@ -148,15 +152,11 @@ export type AlertCondition =
| { kind: 'newvalue'; field_regex: string; capture_group: string }
| { kind: 'ratio'; query_a: string; query_b: string; op: CompareOp; ratio: number; window_ms: number }
// Only one sink kind exists today, but this stays a tagged union to match the backend.
export type AlertSink = { kind: 'ntfy'; topic: string; server: string | null }
export interface AlertRule {
id: string
name: string
enabled: boolean
condition: AlertCondition
sink: AlertSink
}
async function getJson<T>(path: string): Promise<T> {
@@ -297,10 +297,16 @@ export function updateSettings(update: {
maxLogs?: number | null
/** Omit to leave unchanged; `null` clears the selection (falls back to `colorScheme`). */
customThemeId?: string | null
/** Omit to leave unchanged; `null` clears the configured topic. */
ntfyTopic?: string | null
/** Omit to leave unchanged; `null` falls back to `https://ntfy.sh`. */
ntfyServer?: string | null
}): Promise<Settings> {
const body: Record<string, unknown> = { color_scheme: update.colorScheme }
if ('maxLogs' in update) body.max_logs = update.maxLogs
if ('customThemeId' in update) body.custom_theme_id = update.customThemeId
if ('ntfyTopic' in update) body.ntfy_topic = update.ntfyTopic
if ('ntfyServer' in update) body.ntfy_server = update.ntfyServer
return sendJson('/api/settings', 'PATCH', body)
}
@@ -384,13 +390,13 @@ export function fetchAlertRules(): Promise<AlertRule[]> {
return getJson('/api/settings/alerts')
}
export function createAlertRule(rule: { name: string; enabled: boolean; condition: AlertCondition; sink: AlertSink }): Promise<AlertRule> {
export function createAlertRule(rule: { name: string; enabled: boolean; condition: AlertCondition }): Promise<AlertRule> {
return postJson('/api/settings/alerts', rule)
}
export function updateAlertRule(
id: string,
rule: { name: string; enabled: boolean; condition: AlertCondition; sink: AlertSink },
rule: { name: string; enabled: boolean; condition: AlertCondition },
): Promise<AlertRule> {
return sendJson(`/api/settings/alerts/${encodeURIComponent(id)}`, 'PUT', rule)
}