Dorian 224681f1e0 feat: add webhook notification system with Settings UI (REMOTE-03)
Webhook module with HTTP delivery, HMAC-SHA256 signing, and event
filtering. RPC handlers for get-config, configure, and test endpoints.
Settings page gains webhook configuration section with URL, secret,
event toggles, and test button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 12:55:13 +00:00

84 lines
2.9 KiB
Rust

use super::RpcHandler;
use crate::webhooks;
use anyhow::Result;
use tracing::info;
impl RpcHandler {
/// webhook.get-config — Get current webhook configuration.
pub(super) async fn handle_webhook_get_config(&self) -> Result<serde_json::Value> {
let config = webhooks::load_config(&self.config.data_dir).await?;
Ok(serde_json::json!({
"enabled": config.enabled,
"url": config.url,
"events": config.events,
"has_secret": config.secret.is_some(),
}))
}
/// webhook.configure — Update webhook configuration.
pub(super) async fn handle_webhook_configure(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let mut config = webhooks::load_config(&self.config.data_dir).await?;
if let Some(enabled) = params.get("enabled").and_then(|v| v.as_bool()) {
config.enabled = enabled;
}
if let Some(url) = params.get("url").and_then(|v| v.as_str()) {
config.url = url.to_string();
}
if let Some(secret) = params.get("secret").and_then(|v| v.as_str()) {
config.secret = if secret.is_empty() {
None
} else {
Some(secret.to_string())
};
}
if let Some(events) = params.get("events") {
if let Ok(parsed) = serde_json::from_value::<Vec<webhooks::WebhookEvent>>(events.clone())
{
config.events = parsed;
}
}
webhooks::save_config(&self.config.data_dir, &config).await?;
info!("Webhook config updated: enabled={}, url={}", config.enabled, config.url);
Ok(serde_json::json!({
"configured": true,
"enabled": config.enabled,
"url": config.url,
}))
}
/// webhook.test — Send a test webhook notification.
pub(super) async fn handle_webhook_test(&self) -> Result<serde_json::Value> {
let config = webhooks::load_config(&self.config.data_dir).await?;
if !config.enabled || config.url.is_empty() {
anyhow::bail!("Webhook is not configured. Set a URL and enable it first.");
}
let payload = webhooks::WebhookPayload {
event: webhooks::WebhookEvent::ContainerCrash,
title: "Test Notification".to_string(),
message: "This is a test webhook from your Archipelago node.".to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
node_id: {
let (data, _) = self.state_manager.get_snapshot().await;
data.server_info.id
},
details: Some(serde_json::json!({"test": true})),
};
webhooks::send_webhook(&self.config.data_dir, payload).await;
Ok(serde_json::json!({
"sent": true,
"url": config.url,
}))
}
}