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
+11 -8
View File
@@ -423,7 +423,7 @@ async fn create_alert_rule(
Json(body): Json<CreateAlertRuleRequest>,
) -> impl IntoResponse {
let mut settings = state.settings.lock().unwrap();
match settings.create_alert_rule(&state.settings_path, body.name, body.enabled, body.condition, body.sink) {
match settings.create_alert_rule(&state.settings_path, body.name, body.enabled, body.condition) {
Ok(rule) => (StatusCode::CREATED, Json(rule)).into_response(),
Err(err) => alert_error_response(err),
}
@@ -435,7 +435,7 @@ async fn update_alert_rule(
Json(body): Json<UpdateAlertRuleRequest>,
) -> impl IntoResponse {
let mut settings = state.settings.lock().unwrap();
match settings.update_alert_rule(&state.settings_path, &id, body.name, body.enabled, body.condition, body.sink) {
match settings.update_alert_rule(&state.settings_path, &id, body.name, body.enabled, body.condition) {
Ok(rule) => Json(rule).into_response(),
Err(err) => alert_error_response(err),
}
@@ -481,6 +481,12 @@ async fn update_settings(
if let Some(custom_theme_id) = body.custom_theme_id {
settings.custom_theme_id = custom_theme_id;
}
if let Some(ntfy_topic) = body.ntfy_topic {
settings.ntfy_topic = ntfy_topic;
}
if let Some(ntfy_server) = body.ntfy_server {
settings.ntfy_server = ntfy_server;
}
match settings.save(&state.settings_path) {
Ok(()) => (),
Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
@@ -756,8 +762,7 @@ mod tests {
json!({
"name": "error spike",
"enabled": true,
"condition": { "kind": "immediate", "query": "regex(\"error\")" },
"sink": { "kind": "ntfy", "topic": "alerts", "server": null }
"condition": { "kind": "immediate", "query": "regex(\"error\")" }
}),
)
.await;
@@ -776,8 +781,7 @@ mod tests {
json!({
"name": "renamed",
"enabled": false,
"condition": { "kind": "immediate", "query": "regex(\"warn\")" },
"sink": { "kind": "ntfy", "topic": "alerts", "server": null }
"condition": { "kind": "immediate", "query": "regex(\"warn\")" }
}),
)
.await;
@@ -807,8 +811,7 @@ mod tests {
json!({
"name": "x",
"enabled": true,
"condition": { "kind": "immediate", "query": "regex(\"x\")" },
"sink": { "kind": "ntfy", "topic": "x", "server": null }
"condition": { "kind": "immediate", "query": "regex(\"x\")" }
})
.to_string(),
))