Files
2026-07-13 12:31:02 +08:00

480 lines
18 KiB
Go

package notification
import (
"fmt"
"strings"
"service-operation/pocketbase"
)
// ServerNotificationManager handles server-specific notifications
type ServerNotificationManager struct {
pbClient *pocketbase.PocketBaseClient
services map[string]NotificationService
}
// NewServerNotificationManager creates a new server notification manager
func NewServerNotificationManager(pbClient *pocketbase.PocketBaseClient, services map[string]NotificationService) *ServerNotificationManager {
return &ServerNotificationManager{
pbClient: pbClient,
services: services,
}
}
// SendServiceNotification sends notification for a service based on its configuration
func (snm *ServerNotificationManager) SendServiceNotification(payload *NotificationPayload, notificationID, templateID string) error {
// log.Printf("📨 SendServiceNotification called with notification_id: %s, template_id: %s", notificationID, templateID)
// log.Printf("📨 Payload: %+v", payload)
if notificationID == "" {
// log.Printf("❌ Notification ID is required but was empty")
return fmt.Errorf("notification ID is required")
}
// Parse multiple notification IDs
notificationIDs := parseNotificationIDs(notificationID)
if len(notificationIDs) == 0 {
// log.Printf("❌ No valid notification IDs found")
return fmt.Errorf("no valid notification IDs found")
}
var errors []string
successCount := 0
// Send notification to each channel
for _, id := range notificationIDs {
// log.Printf("📤 Processing notification ID: %s", id)
// Check if notification is enabled
if !isNotificationEnabled(snm.pbClient, id) {
// log.Printf("⚠️ Notification %s is disabled, skipping", id)
continue
}
// Get alert configuration
alertConfig, err := getAlertConfiguration(snm.pbClient, id)
if err != nil {
// log.Printf("❌ Failed to get alert configuration for %s: %v", id, err)
errors = append(errors, fmt.Sprintf("failed to get config for %s: %v", id, err))
continue
}
// Get template if provided
var template *ServerNotificationTemplate
if templateID != "" {
template, err = getNotificationTemplate(snm.pbClient, templateID)
if err != nil {
// log.Printf("⚠️ Warning: failed to get template %s for notification %s: %v", templateID, id, err)
}
}
// Generate message from template or use default
message := snm.generateMessage(payload, template)
// log.Printf("📝 Generated message for %s: %s", id, message)
// Send notification using appropriate service
service, exists := snm.services[alertConfig.NotificationType]
if !exists {
// log.Printf("❌ Unsupported notification type for %s: %s", id, alertConfig.NotificationType)
errors = append(errors, fmt.Sprintf("unsupported notification type for %s: %s", id, alertConfig.NotificationType))
continue
}
err = service.SendNotification(alertConfig, message)
if err != nil {
// log.Printf("❌ Failed to send notification via %s for %s: %v", alertConfig.NotificationType, id, err)
errors = append(errors, fmt.Sprintf("failed to send via %s for %s: %v", alertConfig.NotificationType, id, err))
} else {
// log.Printf("✅ Successfully sent notification via %s for %s", alertConfig.NotificationType, id)
successCount++
}
}
// Report results
if successCount > 0 {
// log.Printf("✅ Successfully sent %d out of %d notifications", successCount, len(notificationIDs))
}
if len(errors) > 0 {
// log.Printf("❌ Errors occurred: %v", errors)
if successCount == 0 {
return fmt.Errorf("all notifications failed: %v", errors)
}
// If some succeeded, just log errors but don't return error
// log.Printf("⚠️ Some notifications failed but %d succeeded", successCount)
}
return nil
}
// SendResourceNotification sends notification for specific resource alerts (CPU, RAM, Disk, etc.)
func (snm *ServerNotificationManager) SendResourceNotification(payload *NotificationPayload, notificationID, templateID, resourceType string) error {
// log.Printf("📨 SendResourceNotification called for resource: %s", resourceType)
// log.Printf("📨 Notification ID: %s, Template ID: %s", notificationID, templateID)
// log.Printf("📨 Payload: %+v", payload)
if notificationID == "" {
// log.Printf("❌ Notification ID is required but was empty")
return fmt.Errorf("notification ID is required")
}
// Parse multiple notification IDs
notificationIDs := parseNotificationIDs(notificationID)
if len(notificationIDs) == 0 {
// log.Printf("❌ No valid notification IDs found")
return fmt.Errorf("no valid notification IDs found")
}
var errors []string
successCount := 0
// Send notification to each channel
for _, id := range notificationIDs {
// log.Printf("📤 Processing resource notification ID: %s", id)
// Check if notification is enabled
if !isNotificationEnabled(snm.pbClient, id) {
// log.Printf("⚠️ Notification %s is disabled, skipping", id)
continue
}
// Get alert configuration
alertConfig, err := getAlertConfiguration(snm.pbClient, id)
if err != nil {
// log.Printf("❌ Failed to get alert configuration for %s: %v", id, err)
errors = append(errors, fmt.Sprintf("failed to get config for %s: %v", id, err))
continue
}
// Get template if provided
var template *ServerNotificationTemplate
if templateID != "" {
template, err = getNotificationTemplate(snm.pbClient, templateID)
if err != nil {
// log.Printf("⚠️ Warning: failed to get template %s for notification %s: %v", templateID, id, err)
}
}
// Generate resource-specific message
message := snm.generateResourceMessage(payload, template, resourceType)
// log.Printf("📝 Generated resource message for %s: %s", id, message)
// Send notification
service, exists := snm.services[alertConfig.NotificationType]
if !exists {
// log.Printf("❌ Unsupported notification type for %s: %s", id, alertConfig.NotificationType)
errors = append(errors, fmt.Sprintf("unsupported notification type for %s: %s", id, alertConfig.NotificationType))
continue
}
err = service.SendNotification(alertConfig, message)
if err != nil {
// log.Printf("❌ Failed to send resource notification via %s for %s: %v", alertConfig.NotificationType, id, err)
errors = append(errors, fmt.Sprintf("failed to send via %s for %s: %v", alertConfig.NotificationType, id, err))
} else {
// log.Printf("✅ Successfully sent resource notification via %s for %s", alertConfig.NotificationType, id)
successCount++
}
}
// Report results
if successCount > 0 {
// log.Printf("✅ Successfully sent %d out of %d resource notifications", successCount, len(notificationIDs))
}
if len(errors) > 0 {
// log.Printf("❌ Resource notification errors occurred: %v", errors)
if successCount == 0 {
return fmt.Errorf("all resource notifications failed: %v", errors)
}
// If some succeeded, just log errors but don't return error
// log.Printf("⚠️ Some resource notifications failed but %d succeeded", successCount)
}
return nil
}
// generateResourceMessage creates notification message for specific resource alerts
func (snm *ServerNotificationManager) generateResourceMessage(payload *NotificationPayload, template *ServerNotificationTemplate, resourceType string) string {
var baseMessage string
// Select appropriate resource message from template based on status
if template != nil {
// log.Printf("🔧 Using template for resource type: %s with status: %s", resourceType, payload.Status)
// Check if this is a recovery/restore notification
if payload.Status == "up" {
// Use restore messages for recovery notifications
switch strings.ToLower(resourceType) {
case "cpu":
baseMessage = template.RestoreCPUMessage
case "ram", "memory":
baseMessage = template.RestoreRAMMessage
case "disk":
baseMessage = template.RestoreDiskMessage
case "network":
baseMessage = template.RestoreNetworkMessage
case "cpu_temp", "cpu_temperature":
baseMessage = template.RestoreCPUTempMessage
case "disk_io":
baseMessage = template.RestoreDiskIOMessage
default:
baseMessage = template.UpMessage // fallback to general up message
}
} else {
// Use regular alert messages for warning/down notifications
switch strings.ToLower(resourceType) {
case "cpu":
baseMessage = template.CPUMessage
case "ram", "memory":
baseMessage = template.RAMMessage
case "disk":
baseMessage = template.DiskMessage
case "network":
baseMessage = template.NetworkMessage
case "cpu_temp", "cpu_temperature":
baseMessage = template.CPUTempMessage
case "disk_io":
baseMessage = template.DiskIOMessage
default:
baseMessage = template.WarningMessage // fallback to warning message
}
}
// log.Printf("📝 Template message selected for %s (status: %s): %s", resourceType, payload.Status, baseMessage)
}
// If no template or empty message, use default
if baseMessage == "" {
// log.Printf("🔧 Using default message for resource %s", resourceType)
baseMessage = snm.getDefaultResourceMessage(payload, resourceType)
}
// Replace placeholders with actual values
message := snm.replacePlaceholders(baseMessage, payload)
// log.Printf("📝 Final resource message after placeholder replacement: %s", message)
return message
}
// getDefaultResourceMessage provides default messages for resource alerts
func (snm *ServerNotificationManager) getDefaultResourceMessage(payload *NotificationPayload, resourceType string) string {
statusEmoji := "⚠️"
statusText := "Alert"
if payload.Status == "up" {
statusEmoji = "✅"
statusText = "Recovery"
} else if payload.Status == "down" {
statusEmoji = "❌"
statusText = "Critical"
}
switch strings.ToLower(resourceType) {
case "cpu":
if payload.Status == "up" {
return fmt.Sprintf("%s CPU %s: Server %s CPU usage has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.CPUUsage)
}
return fmt.Sprintf("%s CPU %s: Server %s CPU usage is %s", statusEmoji, statusText, payload.ServiceName, payload.CPUUsage)
case "ram", "memory":
if payload.Status == "up" {
return fmt.Sprintf("%s RAM %s: Server %s RAM usage has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.RAMUsage)
}
return fmt.Sprintf("%s RAM %s: Server %s RAM usage is %s", statusEmoji, statusText, payload.ServiceName, payload.RAMUsage)
case "disk":
if payload.Status == "up" {
return fmt.Sprintf("%s Disk %s: Server %s disk usage has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.DiskUsage)
}
return fmt.Sprintf("%s Disk %s: Server %s disk usage is %s", statusEmoji, statusText, payload.ServiceName, payload.DiskUsage)
case "network":
if payload.Status == "up" {
return fmt.Sprintf("%s Network %s: Server %s network usage has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.NetworkUsage)
}
return fmt.Sprintf("%s Network %s: Server %s network usage is %s", statusEmoji, statusText, payload.ServiceName, payload.NetworkUsage)
case "cpu_temp", "cpu_temperature":
if payload.Status == "up" {
return fmt.Sprintf("%s CPU Temperature %s: Server %s CPU temperature has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.CPUTemp)
}
return fmt.Sprintf("%s CPU Temperature %s: Server %s CPU temperature is %s", statusEmoji, statusText, payload.ServiceName, payload.CPUTemp)
case "disk_io":
if payload.Status == "up" {
return fmt.Sprintf("%s Disk I/O %s: Server %s disk I/O has returned to normal: %s", statusEmoji, statusText, payload.ServiceName, payload.DiskIO)
}
return fmt.Sprintf("%s Disk I/O %s: Server %s disk I/O is %s", statusEmoji, statusText, payload.ServiceName, payload.DiskIO)
default:
if payload.Status == "up" {
return fmt.Sprintf("%s Resource %s: Server %s %s has recovered", statusEmoji, statusText, payload.ServiceName, payload.Message)
}
return fmt.Sprintf("%s Resource %s: Server %s %s", statusEmoji, statusText, payload.ServiceName, payload.Message)
}
}
// generateMessage creates notification message from template or default
func (snm *ServerNotificationManager) generateMessage(payload *NotificationPayload, template *ServerNotificationTemplate) string {
var baseMessage string
// Select appropriate message based on status and template
if template != nil {
// log.Printf("🔧 Using template for status: %s", strings.ToLower(payload.Status))
switch strings.ToLower(payload.Status) {
case "up":
baseMessage = template.UpMessage
case "down":
baseMessage = template.DownMessage
case "warning":
baseMessage = template.WarningMessage
case "paused":
baseMessage = template.PausedMessage
default:
baseMessage = template.UpMessage // fallback
}
// log.Printf("📝 Template message selected: %s", baseMessage)
}
// If no template or empty message, use default
if baseMessage == "" {
// log.Printf("🔧 Using default message (no template or empty template message)")
baseMessage = snm.getDefaultMessage(payload)
}
// Replace placeholders with actual values
message := snm.replacePlaceholders(baseMessage, payload)
// log.Printf("📝 Final message after placeholder replacement: %s", message)
return message
}
// replacePlaceholders replaces all placeholders in the message with actual values
func (snm *ServerNotificationManager) replacePlaceholders(message string, payload *NotificationPayload) string {
// Service/Server name placeholders
message = strings.ReplaceAll(message, "${service_name}", payload.ServiceName)
message = strings.ReplaceAll(message, "${server_name}", payload.ServiceName) // For server notifications
// Status placeholder
message = strings.ReplaceAll(message, "${status}", strings.ToUpper(payload.Status))
// Host/IP placeholders
message = strings.ReplaceAll(message, "${host}", payload.Host)
message = strings.ReplaceAll(message, "${ip}", payload.Host) // Alternative placeholder
message = strings.ReplaceAll(message, "${ip_address}", payload.Host) // New server monitoring placeholder
// Hostname placeholder
message = strings.ReplaceAll(message, "${hostname}", payload.Hostname)
// Port placeholder
if payload.Port > 0 {
message = strings.ReplaceAll(message, "${port}", fmt.Sprintf("%d", payload.Port))
} else {
message = strings.ReplaceAll(message, "${port}", "N/A")
}
// Response time placeholder
if payload.ResponseTime > 0 {
message = strings.ReplaceAll(message, "${response_time}", fmt.Sprintf("%dms", payload.ResponseTime))
} else {
message = strings.ReplaceAll(message, "${response_time}", "N/A")
}
// Timestamp placeholders
message = strings.ReplaceAll(message, "${timestamp}", payload.Timestamp.Format("2006-01-02 15:04:05"))
message = strings.ReplaceAll(message, "${time}", payload.Timestamp.Format("2006-01-02 15:04:05"))
message = strings.ReplaceAll(message, "${date}", payload.Timestamp.Format("2006-01-02"))
// Service type placeholder
message = strings.ReplaceAll(message, "${service_type}", payload.ServiceType)
// Error message placeholder
if payload.ErrorMessage != "" {
message = strings.ReplaceAll(message, "${error}", payload.ErrorMessage)
message = strings.ReplaceAll(message, "${error_message}", payload.ErrorMessage)
} else {
message = strings.ReplaceAll(message, "${error}", "")
message = strings.ReplaceAll(message, "${error_message}", "")
}
// Custom message placeholder
if payload.Message != "" {
message = strings.ReplaceAll(message, "${message}", payload.Message)
}
// Server monitoring specific placeholders
if payload.CPUUsage != "" {
message = strings.ReplaceAll(message, "${cpu_usage}", payload.CPUUsage)
} else {
message = strings.ReplaceAll(message, "${cpu_usage}", "N/A")
}
if payload.RAMUsage != "" {
message = strings.ReplaceAll(message, "${ram_usage}", payload.RAMUsage)
} else {
message = strings.ReplaceAll(message, "${ram_usage}", "N/A")
}
if payload.DiskUsage != "" {
message = strings.ReplaceAll(message, "${disk_usage}", payload.DiskUsage)
} else {
message = strings.ReplaceAll(message, "${disk_usage}", "N/A")
}
if payload.NetworkUsage != "" {
message = strings.ReplaceAll(message, "${network_usage}", payload.NetworkUsage)
} else {
message = strings.ReplaceAll(message, "${network_usage}", "N/A")
}
if payload.CPUTemp != "" {
message = strings.ReplaceAll(message, "${cpu_temp}", payload.CPUTemp)
} else {
message = strings.ReplaceAll(message, "${cpu_temp}", "N/A")
}
if payload.DiskIO != "" {
message = strings.ReplaceAll(message, "${disk_io}", payload.DiskIO)
} else {
message = strings.ReplaceAll(message, "${disk_io}", "N/A")
}
if payload.Threshold != "" {
message = strings.ReplaceAll(message, "${threshold}", payload.Threshold)
} else {
message = strings.ReplaceAll(message, "${threshold}", "N/A")
}
return message
}
// getDefaultMessage provides a default notification message
func (snm *ServerNotificationManager) getDefaultMessage(payload *NotificationPayload) string {
statusEmoji := "✅"
if payload.Status == "down" {
statusEmoji = "❌"
} else if payload.Status == "warning" {
statusEmoji = "⚠️"
}
message := fmt.Sprintf("%s Server Alert\n\n", statusEmoji)
message += fmt.Sprintf("Server: %s\n", payload.ServiceName)
message += fmt.Sprintf("Status: %s\n", strings.ToUpper(payload.Status))
message += fmt.Sprintf("IP Address: %s\n", payload.Host)
if payload.CPUUsage != "" {
message += fmt.Sprintf("CPU Usage: %s\n", payload.CPUUsage)
}
if payload.RAMUsage != "" {
message += fmt.Sprintf("RAM Usage: %s\n", payload.RAMUsage)
}
if payload.DiskUsage != "" {
message += fmt.Sprintf("Disk Usage: %s\n", payload.DiskUsage)
}
message += fmt.Sprintf("Time: %s\n", payload.Timestamp.Format("2006-01-02 15:04:05"))
if payload.ErrorMessage != "" {
message += fmt.Sprintf("Error: %s\n", payload.ErrorMessage)
}
return message
}