feat: Implement comprehensive multi-channel notification system with templating and SSL monitoring
- Add robust notification system supporting 7+ communication channels with intelligent message templating, resource monitoring, and SSL certificate alerts. Notification Channels: (Telegram, Discord, Slack, Signal, Email, Google Chat, Webhooks). - This notification system provides enterprise-grade alerting capabilities with extensive customization options and multi-channel redundancy for critical service monitoring.
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DiscordService handles Discord notifications
|
||||
type DiscordService struct{}
|
||||
|
||||
// NewDiscordService creates a new Discord notification service
|
||||
func NewDiscordService() *DiscordService {
|
||||
return &DiscordService{}
|
||||
}
|
||||
|
||||
// DiscordPayload represents the payload for Discord webhook
|
||||
type DiscordPayload struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
Embeds []DiscordEmbed `json:"embeds,omitempty"`
|
||||
}
|
||||
|
||||
// DiscordEmbed represents a Discord embed
|
||||
type DiscordEmbed struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Color int `json:"color,omitempty"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Fields []DiscordEmbedField `json:"fields,omitempty"`
|
||||
Footer *DiscordEmbedFooter `json:"footer,omitempty"`
|
||||
}
|
||||
|
||||
// DiscordEmbedField represents a field in Discord embed
|
||||
type DiscordEmbedField struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Inline bool `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
// DiscordEmbedFooter represents footer in Discord embed
|
||||
type DiscordEmbedFooter struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// SendNotification sends a notification via Discord webhook
|
||||
func (ds *DiscordService) SendNotification(config *AlertConfiguration, message string) error {
|
||||
if config.DiscordWebhookURL == "" {
|
||||
return fmt.Errorf("discord webhook URL is required")
|
||||
}
|
||||
|
||||
// Create rich embed for better formatting
|
||||
embed := ds.createDiscordEmbed(message)
|
||||
|
||||
payload := DiscordPayload{
|
||||
Embeds: []DiscordEmbed{embed},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal Discord payload: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.Post(config.DiscordWebhookURL, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("discord webhook request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("discord webhook error, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createDiscordEmbed creates a formatted Discord embed from the message
|
||||
func (ds *DiscordService) createDiscordEmbed(message string) DiscordEmbed {
|
||||
// Parse the message to extract structured information
|
||||
lines := strings.Split(message, "\n")
|
||||
|
||||
if len(lines) == 0 {
|
||||
return DiscordEmbed{
|
||||
Description: message,
|
||||
Color: ds.getDefaultColor(),
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
// Extract title from first line and determine status
|
||||
title := strings.TrimSpace(lines[0])
|
||||
color := ds.determineColorFromMessage(title)
|
||||
|
||||
// Create fields from the remaining lines
|
||||
var fields []DiscordEmbedField
|
||||
|
||||
for i, line := range lines {
|
||||
if i == 0 {
|
||||
continue // Skip title line
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse field from "• Key: Value" format
|
||||
if strings.HasPrefix(line, "•") || strings.HasPrefix(line, "-") {
|
||||
line = strings.TrimPrefix(line, "•")
|
||||
line = strings.TrimPrefix(line, "-")
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
fieldName := strings.TrimSpace(parts[0])
|
||||
fieldValue := strings.TrimSpace(parts[1])
|
||||
|
||||
if fieldName != "" && fieldValue != "" {
|
||||
fields = append(fields, DiscordEmbedField{
|
||||
Name: fieldName,
|
||||
Value: fieldValue,
|
||||
Inline: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// If not in key:value format, add as description field
|
||||
fields = append(fields, DiscordEmbedField{
|
||||
Name: "Details",
|
||||
Value: line,
|
||||
Inline: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no structured fields found, use the entire message as description
|
||||
if len(fields) == 0 {
|
||||
// Remove the first line from description since it's used as title
|
||||
description := ""
|
||||
if len(lines) > 1 {
|
||||
description = strings.Join(lines[1:], "\n")
|
||||
}
|
||||
|
||||
return DiscordEmbed{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Color: color,
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Footer: &DiscordEmbedFooter{
|
||||
Text: "CheckCle System Alert",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return DiscordEmbed{
|
||||
Title: title,
|
||||
Color: color,
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Fields: fields,
|
||||
Footer: &DiscordEmbedFooter{
|
||||
Text: "CheckCle System Alert",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// determineColorFromMessage determines Discord embed color based on message content
|
||||
func (ds *DiscordService) determineColorFromMessage(message string) int {
|
||||
messageLower := strings.ToLower(message)
|
||||
|
||||
// Red for critical/error states
|
||||
if strings.Contains(messageLower, "expired") ||
|
||||
strings.Contains(messageLower, "down") ||
|
||||
strings.Contains(messageLower, "failed") ||
|
||||
strings.Contains(messageLower, "error") ||
|
||||
strings.Contains(messageLower, "critical") ||
|
||||
strings.Contains(messageLower, "🚨") ||
|
||||
strings.Contains(messageLower, "🔴") {
|
||||
return 15158332 // Red (#E74C3C)
|
||||
}
|
||||
|
||||
// Orange for warnings
|
||||
if strings.Contains(messageLower, "expiring_soon") ||
|
||||
strings.Contains(messageLower, "expiring soon") ||
|
||||
strings.Contains(messageLower, "warning") ||
|
||||
strings.Contains(messageLower, "maintenance") ||
|
||||
strings.Contains(messageLower, "paused") ||
|
||||
strings.Contains(messageLower, "⚠️") ||
|
||||
strings.Contains(messageLower, "🟡") ||
|
||||
strings.Contains(messageLower, "🟠") {
|
||||
return 15105570 // Orange (#E67E22)
|
||||
}
|
||||
|
||||
// Green for success/up states
|
||||
if strings.Contains(messageLower, "up") ||
|
||||
strings.Contains(messageLower, "resolved") ||
|
||||
strings.Contains(messageLower, "success") ||
|
||||
strings.Contains(messageLower, "restored") ||
|
||||
strings.Contains(messageLower, "valid") ||
|
||||
strings.Contains(messageLower, "🟢") ||
|
||||
strings.Contains(messageLower, "✅") {
|
||||
return 3066993 // Green (#2ECC71)
|
||||
}
|
||||
|
||||
// Blue for info/default
|
||||
return 3447003 // Blue (#3498DB)
|
||||
}
|
||||
|
||||
// getDefaultColor returns the default Discord embed color (blue)
|
||||
func (ds *DiscordService) getDefaultColor() int {
|
||||
return 3447003 // Blue (#3498DB)
|
||||
}
|
||||
Reference in New Issue
Block a user