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(`
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
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(`
%d New Alert%s
`,
len(alertList), pluralize(len(alertList)))
if critical > 0 {
htmlBody += fmt.Sprintf(`
`, critical)
}
if warning > 0 {
htmlBody += fmt.Sprintf(`
`, warning)
}
htmlBody += fmt.Sprintf(`
| Resource |
Level |
Value |
Duration |
%s
`, 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)
}
}