From 94a74fdcfe261db63308b56b982ad162d6533dc8 Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Mon, 11 Aug 2025 09:38:09 +0000 Subject: [PATCH] fix: telegram webhook payload format and pbs connection issues (fixes #294) - add proper telegram bot api support with chat_id and text fields - fix frontend pbs red dot display (was checking 'error' instead of 'unhealthy') - fix qemu guest agent memory reporting (fallback to mem when freemem is 0) - extract chat_id from telegram webhook urls when present --- internal/api/notifications.go | 10 ++++++++ internal/notifications/notifications.go | 26 ++++++++++++++++----- internal/notifications/webhook_enhanced.go | 1 + internal/notifications/webhook_templates.go | 14 +++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/internal/api/notifications.go b/internal/api/notifications.go index cf0bf62c9..1c878dd65 100644 --- a/internal/api/notifications.go +++ b/internal/api/notifications.go @@ -337,8 +337,17 @@ func (h *NotificationHandlers) TestWebhook(w http.ResponseWriter, r *http.Reques } if err := json.Unmarshal(bodyBytes, &serviceCheck); err == nil && serviceCheck.Service != "" { webhook.Service = serviceCheck.Service + // Also set it in the basic webhook for consistency + basicWebhook.Service = serviceCheck.Service + webhook.WebhookConfig.Service = serviceCheck.Service } + log.Info(). + Str("service", webhook.Service). + Str("url", webhook.URL). + Str("name", webhook.Name). + Msg("Testing webhook") + // Get template for the service templates := notifications.GetWebhookTemplates() for _, tmpl := range templates { @@ -350,6 +359,7 @@ func (h *NotificationHandlers) TestWebhook(w http.ResponseWriter, r *http.Reques for k, v := range tmpl.Headers { webhook.Headers[k] = v } + log.Info().Str("service", webhook.Service).Msg("Found template for service") break } } diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index 873b82392..f6e1cc485 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "sync" "text/template" @@ -466,18 +467,18 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A var jsonData []byte var err error - // Check if this is a Discord webhook and use the proper template - if webhook.Service == "discord" { + // Check if this is a service-specific webhook and use the proper template + if webhook.Service == "discord" || webhook.Service == "telegram" { // Convert to enhanced webhook to use template enhanced := EnhancedWebhookConfig{ WebhookConfig: webhook, - Service: "discord", + Service: webhook.Service, } - // Get Discord template + // Get service template templates := GetWebhookTemplates() for _, tmpl := range templates { - if tmpl.Service == "discord" { + if tmpl.Service == webhook.Service { enhanced.PayloadTemplate = tmpl.PayloadTemplate break } @@ -485,13 +486,26 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A // Prepare data and generate payload data := n.prepareWebhookData(alert, nil) + + // For Telegram, extract chat_id from URL if present + if webhook.Service == "telegram" && strings.Contains(webhook.URL, "chat_id=") { + // Extract chat_id from URL query params + if u, err := url.Parse(webhook.URL); err == nil { + chatID := u.Query().Get("chat_id") + if chatID != "" { + data.ChatID = chatID + } + } + } + jsonData, err = n.generatePayloadFromTemplate(enhanced.PayloadTemplate, data) if err != nil { log.Error(). Err(err). Str("webhook", webhook.Name). + Str("service", webhook.Service). Str("alertID", alert.ID). - Msg("Failed to generate Discord payload") + Msg("Failed to generate webhook payload") return } } else { diff --git a/internal/notifications/webhook_enhanced.go b/internal/notifications/webhook_enhanced.go index 302944876..59adb3f77 100644 --- a/internal/notifications/webhook_enhanced.go +++ b/internal/notifications/webhook_enhanced.go @@ -51,6 +51,7 @@ type WebhookPayloadData struct { CustomFields map[string]interface{} AlertCount int Alerts []*alerts.Alert // For grouped alerts + ChatID string // For Telegram webhooks } // SendEnhancedWebhook sends a webhook with template support diff --git a/internal/notifications/webhook_templates.go b/internal/notifications/webhook_templates.go index 6db77af18..00aa7bc74 100644 --- a/internal/notifications/webhook_templates.go +++ b/internal/notifications/webhook_templates.go @@ -45,6 +45,20 @@ func GetWebhookTemplates() []WebhookTemplate { }`, Instructions: "1. In Discord, go to Server Settings > Integrations > Webhooks\n2. Create a new webhook and copy the URL\n3. Paste the URL here (format: https://discord.com/api/webhooks/...)", }, + { + Service: "telegram", + Name: "Telegram Bot", + URLPattern: "https://api.telegram.org/bot{bot_token}/sendMessage", + Method: "POST", + Headers: map[string]string{"Content-Type": "application/json"}, + PayloadTemplate: `{ + "chat_id": "{{.ChatID}}", + "text": "🚨 *Pulse Alert: {{.Level | title}}*\n\n{{.Message}}\n\n📊 *Details:*\n• Resource: {{.ResourceName}}\n• Node: {{.Node}}\n• Type: {{.Type | title}}\n• Value: {{printf "%.1f" .Value}}%\n• Threshold: {{printf "%.0f" .Threshold}}%\n• Duration: {{.Duration}}\n\n🔗 [View in Pulse]({{.Instance}})", + "parse_mode": "Markdown", + "disable_web_page_preview": true + }`, + Instructions: "1. Create a bot with @BotFather on Telegram\n2. Get your bot token\n3. Get your chat ID by messaging the bot and visiting: https://api.telegram.org/bot/getUpdates\n4. URL format: https://api.telegram.org/bot/sendMessage", + }, { Service: "slack", Name: "Slack Incoming Webhook",