package notifications import ( "fmt" "strings" "time" "github.com/rcourtman/pulse-go-rewrite/internal/alerts" ) // EmailTemplate generates a professional HTML email template for alerts func EmailTemplate(alertList []*alerts.Alert, isSingle bool) (subject, htmlBody, textBody string) { if isSingle && len(alertList) == 1 { return singleAlertTemplate(alertList[0]) } return groupedAlertTemplate(alertList) } func singleAlertTemplate(alert *alerts.Alert) (subject, htmlBody, textBody string) { levelColor := "#ff6b6b" levelBg := "#fee" if alert.Level == "warning" { levelColor = "#ffd93d" levelBg = "#fffaeb" } // Properly format alert type (CPU, Memory, etc.) alertType := alert.Type switch strings.ToLower(alertType) { case "cpu": alertType = "CPU" case "memory": alertType = "Memory" case "disk": alertType = "Disk" case "io": alertType = "I/O" default: alertType = strings.Title(alertType) } subject = fmt.Sprintf("[Pulse Alert] %s: %s on %s", strings.Title(string(alert.Level)), alertType, alert.ResourceName) htmlBody = fmt.Sprintf(`

Pulse Monitoring Alert

%s Alert
%s
%s
Current Value
%s
Threshold
%s
Resource ID %s
Alert Type %s
Node %s
Instance %s
Started %s
Duration %s
`, levelBg, levelColor, levelColor, alert.Level, alert.ResourceName, alert.Message, formatMetricValue(alert.Type, alert.Value), formatMetricThreshold(alert.Type, alert.Threshold), alert.ResourceID, alertType, alert.Node, alert.Instance, alert.StartTime.Format("Jan 2, 2006 at 3:04 PM"), formatDuration(time.Since(alert.StartTime)), ) // Plain text version textBody = fmt.Sprintf(`PULSE MONITORING ALERT %s ALERT: %s Resource: %s (%s) Type: %s Current Value: %s (Threshold: %s) Message: %s Details: - Node: %s - Instance: %s - Started: %s - Duration: %s This is an automated notification from Pulse Monitoring. View alerts and configure settings in your Pulse dashboard.`, strings.ToUpper(string(alert.Level)), alert.ResourceName, alert.ResourceName, alert.ResourceID, alert.Type, formatMetricValue(alert.Type, alert.Value), formatMetricThreshold(alert.Type, alert.Threshold), alert.Message, alert.Node, alert.Instance, alert.StartTime.Format("Jan 2, 2006 at 3:04 PM"), formatDuration(time.Since(alert.StartTime)), ) return subject, htmlBody, textBody } func groupedAlertTemplate(alertList []*alerts.Alert) (subject, htmlBody, textBody string) { critical := 0 warning := 0 for _, alert := range alertList { if alert.Level == "critical" { critical++ } else { warning++ } } // Subject line if critical > 0 && warning > 0 { subject = fmt.Sprintf("[Pulse Alert] %d Critical, %d Warning alerts", critical, warning) } else if critical > 0 { subject = fmt.Sprintf("[Pulse Alert] %d Critical alert%s", critical, pluralize(critical)) } else { subject = fmt.Sprintf("[Pulse Alert] %d Warning alert%s", warning, pluralize(warning)) } // Build alert rows var alertRows strings.Builder for _, alert := range alertList { levelColor := "#ff6b6b" if alert.Level == "warning" { levelColor = "#ffd93d" } alertRows.WriteString(fmt.Sprintf(`
%s
%s on %s
%s
%s
of %s
%s ago `, levelColor, alert.ResourceName, alert.Type, alert.Node, levelColor, alert.Level, formatMetricValue(alert.Type, alert.Value), formatMetricThreshold(alert.Type, alert.Threshold), formatDuration(time.Since(alert.StartTime)), )) } htmlBody = fmt.Sprintf(`

Pulse Monitoring Alert Summary

%d New Alert%s

`, len(alertList), pluralize(len(alertList))) if critical > 0 { htmlBody += fmt.Sprintf(`
%d
Critical
`, critical) } if warning > 0 { htmlBody += fmt.Sprintf(`
%d
Warning
`, warning) } htmlBody += fmt.Sprintf(`
%s
Resource Level Value Duration
`, alertRows.String()) // Plain text version var textBuilder strings.Builder textBuilder.WriteString("PULSE MONITORING ALERT SUMMARY\n\n") textBuilder.WriteString(fmt.Sprintf("%d New Alert%s\n", len(alertList), pluralize(len(alertList)))) if critical > 0 { textBuilder.WriteString(fmt.Sprintf("Critical: %d\n", critical)) } if warning > 0 { textBuilder.WriteString(fmt.Sprintf("Warning: %d\n", warning)) } textBuilder.WriteString("\nAlert Details:\n") textBuilder.WriteString("─────────────────────────────────────────────────────────────\n") for i, alert := range alertList { textBuilder.WriteString(fmt.Sprintf("\n%d. %s (%s)\n", i+1, alert.ResourceName, alert.ResourceID)) textBuilder.WriteString(fmt.Sprintf(" Level: %s | Type: %s\n", strings.ToUpper(string(alert.Level)), alert.Type)) textBuilder.WriteString(fmt.Sprintf(" Value: %s (Threshold: %s)\n", formatMetricValue(alert.Type, alert.Value), formatMetricThreshold(alert.Type, alert.Threshold))) textBuilder.WriteString(fmt.Sprintf(" Node: %s | Started: %s ago\n", alert.Node, formatDuration(time.Since(alert.StartTime)))) textBuilder.WriteString(fmt.Sprintf(" Message: %s\n", alert.Message)) } textBuilder.WriteString("\n─────────────────────────────────────────────────────────────\n") textBuilder.WriteString("This is an automated notification from Pulse Monitoring.\n") textBuilder.WriteString("Configure alert settings in the Pulse dashboard.") textBody = textBuilder.String() return subject, htmlBody, textBody } func formatDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf("%d seconds", int(d.Seconds())) } else if d < time.Hour { return fmt.Sprintf("%d minutes", int(d.Minutes())) } else if d < 24*time.Hour { return fmt.Sprintf("%.1f hours", d.Hours()) } return fmt.Sprintf("%.1f days", d.Hours()/24) } func pluralize(count int) string { if count == 1 { return "" } return "s" } // formatMetricValue formats a metric value with the appropriate unit func formatMetricValue(metricType string, value float64) string { switch strings.ToLower(metricType) { case "diskread", "diskwrite", "networkin", "networkout": return fmt.Sprintf("%.1f MB/s", value) case "temperature": return fmt.Sprintf("%.1f°C", value) case "cpu", "memory", "disk", "usage": return fmt.Sprintf("%.1f%%", value) default: return fmt.Sprintf("%.1f", value) } } // formatMetricThreshold formats a metric threshold with the appropriate unit func formatMetricThreshold(metricType string, threshold float64) string { switch strings.ToLower(metricType) { case "diskread", "diskwrite", "networkin", "networkout": return fmt.Sprintf("%.0f MB/s", threshold) case "temperature": return fmt.Sprintf("%.0f°C", threshold) case "cpu", "memory", "disk", "usage": return fmt.Sprintf("%.0f%%", threshold) default: return fmt.Sprintf("%.0f", threshold) } }