480 lines
18 KiB
Go
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
|
|
} |