Add configurable email notification digests

This commit is contained in:
maxim
2026-07-31 23:01:46 +03:00
parent 58ec47b188
commit a987667233
5 changed files with 288 additions and 2 deletions

130
email.go Normal file
View File

@@ -0,0 +1,130 @@
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/smtp"
"sort"
"strings"
"time"
)
func filterEmailAlerts(alerts []Alert, settings EmailSettings) []Alert {
allowedSeverity, allowedSource := map[string]bool{}, map[string]bool{}
for _, value := range settings.Severities {
allowedSeverity[value] = true
}
for _, value := range settings.Sources {
allowedSource[value] = true
}
var result []Alert
for _, alert := range alerts {
if allowedSeverity[alert.Severity] && allowedSource[alert.Source] {
result = append(result, alert)
}
}
return result
}
func sendAlertEmail(settings EmailSettings, alerts []Alert, test bool) error {
subject := "Proxmox Dashboard: сводка уведомлений"
body := "Система не обнаружила выбранных активных событий."
if test {
subject = "Proxmox Dashboard: тест почты"
body = "Почтовые уведомления настроены правильно."
}
if len(alerts) > 0 {
groups := map[string][]Alert{}
for _, alert := range alerts {
groups[alert.Severity] = append(groups[alert.Severity], alert)
}
var lines []string
for _, severity := range []string{"critical", "warning", "info"} {
items := groups[severity]
if len(items) == 0 {
continue
}
title := map[string]string{"critical": "КРИТИЧЕСКИЕ", "warning": "ПРЕДУПРЕЖДЕНИЯ", "info": "ИНФОРМАЦИЯ"}[severity]
lines = append(lines, title)
sort.Slice(items, func(i, j int) bool { return items[i].Title < items[j].Title })
for _, item := range items {
lines = append(lines, fmt.Sprintf("• [%s] %s\n %s", item.Source, item.Title, item.Message))
}
}
body = strings.Join(lines, "\n\n")
subject = fmt.Sprintf("Proxmox Dashboard: событий — %d", len(alerts))
}
message := []byte("From: " + settings.From + "\r\nTo: " + settings.To + "\r\nSubject: " + subject + "\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n" + body + "\r\n")
address := net.JoinHostPort(settings.Host, fmt.Sprint(settings.Port))
var client *smtp.Client
if settings.Port == 465 {
connection, err := tls.Dial("tcp", address, &tls.Config{ServerName: settings.Host, MinVersion: tls.VersionTLS12})
if err != nil {
return err
}
client, err = smtp.NewClient(connection, settings.Host)
if err != nil {
return err
}
} else {
var err error
client, err = smtp.Dial(address)
if err != nil {
return err
}
if ok, _ := client.Extension("STARTTLS"); ok {
if err = client.StartTLS(&tls.Config{ServerName: settings.Host, MinVersion: tls.VersionTLS12}); err != nil {
return err
}
}
}
defer client.Close()
if settings.Username != "" {
if err := client.Auth(smtp.PlainAuth("", settings.Username, settings.Password, settings.Host)); err != nil {
return err
}
}
if err := client.Mail(settings.From); err != nil {
return err
}
if err := client.Rcpt(settings.To); err != nil {
return err
}
writer, err := client.Data()
if err != nil {
return err
}
if _, err = writer.Write(message); err != nil {
return err
}
if err = writer.Close(); err != nil {
return err
}
return client.Quit()
}
func runEmailNotifier(collect func() (dashboardMetrics, error), store *Store) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
settings := store.EmailSettings(true)
if !settings.Enabled || time.Since(store.LastEmailAt()) < time.Duration(settings.IntervalMinutes)*time.Minute {
continue
}
metrics, err := collect()
if err != nil {
continue
}
alerts := filterEmailAlerts(metrics.Alerts, settings)
if len(alerts) == 0 {
continue
}
if err := sendAlertEmail(settings, alerts, false); err != nil {
log.Printf("Не удалось отправить почтовую сводку: %v", err)
continue
}
store.SetLastEmailAt(time.Now())
}
}