This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Package notify formats and delivers monitor state-change alerts to
|
||||
// notification channels. It depends only on models so services can call it
|
||||
// without an import cycle.
|
||||
|
||||
|
||||
|
||||
package notify
|
||||
|
||||
import (
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/mrhid6/vantage/server/internal/models"
|
||||
)
|
||||
|
||||
// Event describes a monitor state transition worth alerting on.
|
||||
|
||||
type Event struct {
|
||||
MonitorName string
|
||||
Type string
|
||||
@@ -20,7 +20,7 @@ type Event struct {
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// title is a short one-line summary used by the text-based channels.
|
||||
|
||||
func (e Event) title() string {
|
||||
verb := "recovered"
|
||||
if e.NewStatus == models.StatusDown {
|
||||
@@ -33,7 +33,7 @@ func (e Event) title() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Dispatch delivers ev to a single channel, formatting per channel type.
|
||||
|
||||
func Dispatch(ch models.NotificationChannel, ev Event) error {
|
||||
switch ch.Type {
|
||||
case models.ChannelWebhook:
|
||||
@@ -51,7 +51,7 @@ func Dispatch(ch models.NotificationChannel, ev Event) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Test delivers a synthetic event so users can verify a channel's configuration.
|
||||
|
||||
func Test(ch models.NotificationChannel) error {
|
||||
return Dispatch(ch, Event{
|
||||
MonitorName: "Test monitor",
|
||||
|
||||
@@ -28,7 +28,7 @@ func postJSON(target string, payload any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatchWebhook posts the full event as JSON to a user-supplied URL.
|
||||
|
||||
func dispatchWebhook(ch models.NotificationChannel, ev Event) error {
|
||||
target := ch.Config["url"]
|
||||
if target == "" {
|
||||
|
||||
@@ -13,13 +13,13 @@ import (
|
||||
|
||||
const smtpTimeout = 15 * time.Second
|
||||
|
||||
// dispatchSMTP sends the alert as a plain-text email. Config keys: host, port,
|
||||
// username, password, from, to. Auth is skipped when username is empty. Port 465
|
||||
// uses implicit TLS; other ports use STARTTLS when the server advertises it.
|
||||
//
|
||||
// It dials with a timeout and sets a connection deadline so an unreachable or
|
||||
// misconfigured SMTP host fails fast instead of hanging the request until the OS
|
||||
// TCP timeout (which resets the upstream proxy connection).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func dispatchSMTP(ch models.NotificationChannel, ev Event) error {
|
||||
host := ch.Config["host"]
|
||||
port := ch.Config["port"]
|
||||
@@ -36,7 +36,7 @@ func dispatchSMTP(ch models.NotificationChannel, ev Event) error {
|
||||
}
|
||||
_ = conn.SetDeadline(time.Now().Add(smtpTimeout))
|
||||
|
||||
// Implicit TLS on 465; otherwise start plain and upgrade via STARTTLS.
|
||||
|
||||
if port == "465" {
|
||||
conn = tls.Client(conn, &tls.Config{ServerName: host})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/mrhid6/vantage/server/internal/models"
|
||||
)
|
||||
|
||||
// App theme colors (mirrors web/tailwind.config.ts).
|
||||
|
||||
const (
|
||||
colBg = "#0f1117"
|
||||
colSurface = "#1a1d27"
|
||||
@@ -23,7 +23,7 @@ const (
|
||||
colDanger = "#ef4444"
|
||||
)
|
||||
|
||||
// statusColor returns the accent color for a monitor status.
|
||||
|
||||
func statusColor(status string) string {
|
||||
switch status {
|
||||
case models.StatusUp:
|
||||
@@ -35,8 +35,8 @@ func statusColor(status string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// buildMIME assembles a multipart/alternative message (plain + HTML) with the
|
||||
// standard email headers, ready to hand to the SMTP DATA command.
|
||||
|
||||
|
||||
func buildMIME(from, to, subject, text, htmlBody string) ([]byte, error) {
|
||||
var buf strings.Builder
|
||||
w := multipart.NewWriter(&buf)
|
||||
@@ -66,7 +66,6 @@ func buildMIME(from, to, subject, text, htmlBody string) ([]byte, error) {
|
||||
return []byte(head.String() + buf.String()), nil
|
||||
}
|
||||
|
||||
// htmlEmail renders the alert as a dark-themed HTML email matching the app.
|
||||
func htmlEmail(ev Event) string {
|
||||
accent := statusColor(ev.NewStatus)
|
||||
label := "Recovered"
|
||||
@@ -77,7 +76,7 @@ func htmlEmail(ev Event) string {
|
||||
esc := html.EscapeString
|
||||
row := func(k, v string) string {
|
||||
if v == "" {
|
||||
v = "—"
|
||||
v = ""
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
`<tr>`+
|
||||
@@ -151,7 +150,7 @@ func htmlEmail(ev Event) string {
|
||||
)
|
||||
}
|
||||
|
||||
// textEmail renders the plain-text fallback.
|
||||
|
||||
func textEmail(ev Event) string {
|
||||
return strings.Join([]string{
|
||||
ev.title(),
|
||||
|
||||
Reference in New Issue
Block a user