Files
ProxmoxDash/email.go
2026-07-31 23:01:46 +03:00

131 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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())
}
}