Files
Nukumizu/internal/controller/pipes/email.go
T
NanamiAdmin 48533404fa
Build / ubuntu-latest (push) Canceled after 1m11s
Build / windows-latest (push) Canceled after 1m36s
feat: add incoming webhook API support with configurable endpoints
- Implemented the incoming webhook API to handle alerts from external applications.
- Added configuration options for webhook listening address, port, and endpoints in config.go.
- Created WebhookReceiverConfig and WebhookEndpointConfig structures to manage webhook settings.
- Developed WebhookHandler to process incoming requests, validate tokens, and deliver alerts to specified channels.
- Enhanced existing controller interfaces to support alert delivery.
- Updated message rendering to respect Markdown settings for different channels.
- Added tests for webhook functionality and ensured proper error handling.
2026-09-24 11:21:38 +08:00

140 lines
4.0 KiB
Go

package pipes
import (
"fmt"
gomail "gopkg.in/mail.v2"
"nukumizu-backend/config"
"nukumizu-backend/internal/controller"
"nukumizu-backend/internal/netproxy"
"nukumizu-backend/internal/node"
"nukumizu-backend/internal/template"
"nukumizu-backend/postLog"
)
// EmailController handles email notifications via SMTP.
type EmailController struct {
cfg config.EmailConfig
}
// NewEmailController creates a new Email controller.
func NewEmailController(cfg config.EmailConfig) *EmailController {
if cfg.NetworkUseProxy {
// Route SMTP through the HTTP CONNECT proxy. NetDialTimeout is
// gomail's documented hook for overriding how the SMTP connection is
// dialed. There is a single global email channel, so overriding it
// unconditionally when the flag is set is safe.
gomail.NetDialTimeout = netproxy.DialWithTimeout(true)
}
return &EmailController{cfg: cfg}
}
// Name returns the controller name.
func (e *EmailController) Name() string {
return "email"
}
// Start initializes the Email controller.
func (e *EmailController) Start() error {
if !e.cfg.Enabled {
postLog.Info("Email controller is disabled")
return nil
}
postLog.Info("Email controller started")
return nil
}
// Stop shuts down the Email controller.
func (e *EmailController) Stop() {
postLog.Info("Email controller stopped")
}
// IsEnabled returns whether the controller is enabled.
func (e *EmailController) IsEnabled() bool {
return e.cfg.Enabled
}
// IsMarkdown returns whether the channel renders Markdown, per its markdown
// setting in config.json.
func (e *EmailController) IsMarkdown() bool {
return e.cfg.Markdown
}
// SendStatusChange sends a status change notification via Email.
func (e *EmailController) SendStatusChange(change node.StatusChange) error {
if !e.cfg.Enabled {
return nil
}
if len(e.cfg.To) == 0 {
postLog.Debug("Email controller has no recipients configured")
return nil
}
cfg := config.C_globalConfig
params := template.BuildParamsFromStatusChange(change)
body := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, e.cfg.Markdown)
subject := fmt.Sprintf("Server Status Change: %s - %s", change.Name, change.Event)
return e.sendEmail(subject, body)
}
// SendServerList sends the server list via Email.
func (e *EmailController) SendServerList(onlineServers, offlineServers string) error {
if !e.cfg.Enabled || len(e.cfg.To) == 0 {
return nil
}
cfg := config.C_globalConfig
params := template.BuildParamsFromServerList()
body := template.Render(cfg.ControllerMessage.ServerList, params, e.cfg.Markdown)
return e.sendEmail("Server List", body)
}
// SendExecuteResult sends a command execution result via Email.
func (e *EmailController) SendExecuteResult(serverName, serverUUID, command, result string) error {
if !e.cfg.Enabled || len(e.cfg.To) == 0 {
return nil
}
cfg := config.C_globalConfig
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
body := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, e.cfg.Markdown)
subject := fmt.Sprintf("Command Result: %s on %s", command, serverName)
return e.sendEmail(subject, body)
}
// SendAlert sends an alert submitted through the incoming webhook API to the
// configured recipients.
func (e *EmailController) SendAlert(alert controller.Alert) error {
if !e.cfg.Enabled {
return nil
}
if len(e.cfg.To) == 0 {
postLog.Debug("Email controller has no recipients configured")
return nil
}
return e.sendEmail(alert.Subject, alert.Render(e.cfg.Markdown))
}
func (e *EmailController) sendEmail(subject, body string) error {
m := gomail.NewMessage()
m.SetHeader("From", e.cfg.From)
m.SetHeader("To", e.cfg.To...)
m.SetHeader("Subject", subject)
m.SetBody("text/plain", body)
d := gomail.NewDialer(e.cfg.SMTPHost, e.cfg.SMTPPort, e.cfg.Username, e.cfg.Password)
if err := d.DialAndSend(m); err != nil {
postLog.Warning("Failed to send email: " + err.Error())
return err
}
postLog.Debug("Email sent successfully to " + fmt.Sprintf("%v", e.cfg.To))
return nil
}