mirror of
https://github.com/Dvorinka/beszel.git
synced 2026-06-05 05:42:56 +00:00
Initial commit: Beszel fork with Domain Locker integration
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type DiscordProvider struct {
|
||||
settings notification.DiscordSettings
|
||||
}
|
||||
|
||||
func NewDiscordProvider(settings notification.DiscordSettings) *DiscordProvider {
|
||||
return &DiscordProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *DiscordProvider) Validate() error {
|
||||
if p.settings.WebhookURL == "" {
|
||||
return fmt.Errorf("Discord webhook URL is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DiscordProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
color := 0x00ff00 // Green for UP
|
||||
if msg.Status == "DOWN" {
|
||||
color = 0xff0000 // Red for DOWN
|
||||
}
|
||||
|
||||
embed := map[string]interface{}{
|
||||
"title": msg.Title,
|
||||
"description": msg.Body,
|
||||
"color": color,
|
||||
"timestamp": msg.Timestamp.Format(time.RFC3339),
|
||||
"fields": []map[string]interface{}{
|
||||
{
|
||||
"name": "Monitor",
|
||||
"value": msg.MonitorName,
|
||||
"inline": true,
|
||||
},
|
||||
{
|
||||
"name": "Status",
|
||||
"value": msg.Status,
|
||||
"inline": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if msg.MonitorURL != "" {
|
||||
embed["fields"] = append(embed["fields"].([]map[string]interface{}), map[string]interface{}{
|
||||
"name": "URL",
|
||||
"value": msg.MonitorURL,
|
||||
"inline": false,
|
||||
})
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"embeds": []map[string]interface{}{embed},
|
||||
}
|
||||
|
||||
if p.settings.Username != "" {
|
||||
payload["username"] = p.settings.Username
|
||||
}
|
||||
if p.settings.AvatarURL != "" {
|
||||
payload["avatar_url"] = p.settings.AvatarURL
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", p.settings.WebhookURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("discord webhook request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("discord webhook returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
// EmailProvider implements email notifications via SMTP
|
||||
type EmailProvider struct {
|
||||
settings notification.EmailSettings
|
||||
}
|
||||
|
||||
// NewEmailProvider creates a new email provider
|
||||
func NewEmailProvider(settings notification.EmailSettings) *EmailProvider {
|
||||
return &EmailProvider{settings: settings}
|
||||
}
|
||||
|
||||
// Validate checks if the email settings are valid
|
||||
func (p *EmailProvider) Validate() error {
|
||||
if p.settings.SMTPHost == "" {
|
||||
return fmt.Errorf("SMTP host is required")
|
||||
}
|
||||
if p.settings.SMTPPort == 0 {
|
||||
return fmt.Errorf("SMTP port is required")
|
||||
}
|
||||
if p.settings.FromEmail == "" {
|
||||
return fmt.Errorf("from email is required")
|
||||
}
|
||||
if p.settings.ToEmail == "" {
|
||||
return fmt.Errorf("to email is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends an email notification
|
||||
func (p *EmailProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("[%s] %s - %s", msg.Status, msg.MonitorName, msg.Title)
|
||||
body := p.formatBody(msg)
|
||||
|
||||
// Build email content
|
||||
email := fmt.Sprintf(
|
||||
"From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||||
p.settings.FromEmail,
|
||||
p.settings.ToEmail,
|
||||
subject,
|
||||
body,
|
||||
)
|
||||
|
||||
// Connect to SMTP server
|
||||
addr := fmt.Sprintf("%s:%d", p.settings.SMTPHost, p.settings.SMTPPort)
|
||||
|
||||
var auth smtp.Auth
|
||||
if p.settings.SMTPUser != "" {
|
||||
auth = smtp.PlainAuth("", p.settings.SMTPUser, p.settings.SMTPPassword, p.settings.SMTPHost)
|
||||
}
|
||||
|
||||
// Send email
|
||||
if p.settings.UseTLS {
|
||||
return p.sendTLS(addr, auth, email)
|
||||
}
|
||||
|
||||
return smtp.SendMail(
|
||||
addr,
|
||||
auth,
|
||||
p.settings.FromEmail,
|
||||
[]string{p.settings.ToEmail},
|
||||
[]byte(email),
|
||||
)
|
||||
}
|
||||
|
||||
// sendTLS sends email using TLS
|
||||
func (p *EmailProvider) sendTLS(addr string, auth smtp.Auth, email string) error {
|
||||
conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: p.settings.SMTPHost})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect via TLS: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client, err := smtp.NewClient(conn, p.settings.SMTPHost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SMTP client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if auth != nil {
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("authentication failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.Mail(p.settings.FromEmail); err != nil {
|
||||
return fmt.Errorf("failed to set sender: %w", err)
|
||||
}
|
||||
if err := client.Rcpt(p.settings.ToEmail); err != nil {
|
||||
return fmt.Errorf("failed to set recipient: %w", err)
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get data writer: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(email))
|
||||
if err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("failed to write email: %w", err)
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close data writer: %w", err)
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
// formatBody formats the email body
|
||||
func (p *EmailProvider) formatBody(msg *notification.NotificationMessage) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(fmt.Sprintf("Monitor: %s\n", msg.MonitorName))
|
||||
if msg.MonitorURL != "" {
|
||||
b.WriteString(fmt.Sprintf("URL: %s\n", msg.MonitorURL))
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("Status: %s\n", msg.Status))
|
||||
b.WriteString(fmt.Sprintf("Time: %s\n", msg.Timestamp.Format("2006-01-02 15:04:05")))
|
||||
|
||||
if msg.Ping > 0 {
|
||||
b.WriteString(fmt.Sprintf("Response Time: %dms\n", msg.Ping))
|
||||
}
|
||||
|
||||
if msg.Message != "" {
|
||||
b.WriteString(fmt.Sprintf("\nMessage: %s\n", msg.Message))
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf("\n%s\n", msg.Body))
|
||||
|
||||
return b.String()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type GotifyProvider struct {
|
||||
settings notification.GotifySettings
|
||||
}
|
||||
|
||||
func NewGotifyProvider(settings notification.GotifySettings) *GotifyProvider {
|
||||
return &GotifyProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *GotifyProvider) Validate() error {
|
||||
if p.settings.ServerURL == "" {
|
||||
return fmt.Errorf("Gotify server URL is required")
|
||||
}
|
||||
if p.settings.AppToken == "" {
|
||||
return fmt.Errorf("Gotify app token is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *GotifyProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"title": msg.Title,
|
||||
"message": msg.Body,
|
||||
"priority": p.settings.Priority,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("%s/message?token=%s", p.settings.ServerURL, p.settings.AppToken)
|
||||
|
||||
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gotify request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("gotify returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type PushoverProvider struct {
|
||||
settings notification.PushoverSettings
|
||||
}
|
||||
|
||||
func NewPushoverProvider(settings notification.PushoverSettings) *PushoverProvider {
|
||||
return &PushoverProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *PushoverProvider) Validate() error {
|
||||
if p.settings.AppToken == "" {
|
||||
return fmt.Errorf("Pushover app token is required")
|
||||
}
|
||||
if p.settings.UserKey == "" {
|
||||
return fmt.Errorf("Pushover user key is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PushoverProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("token", p.settings.AppToken)
|
||||
data.Set("user", p.settings.UserKey)
|
||||
data.Set("title", msg.Title)
|
||||
data.Set("message", msg.Body)
|
||||
data.Set("priority", fmt.Sprintf("%d", p.settings.Priority))
|
||||
|
||||
if p.settings.Device != "" {
|
||||
data.Set("device", p.settings.Device)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.PostForm("https://api.pushover.net/1/messages.json", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pushover API request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("pushover API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type SlackProvider struct {
|
||||
settings notification.SlackSettings
|
||||
}
|
||||
|
||||
func NewSlackProvider(settings notification.SlackSettings) *SlackProvider {
|
||||
return &SlackProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *SlackProvider) Validate() error {
|
||||
if p.settings.WebhookURL == "" {
|
||||
return fmt.Errorf("Slack webhook URL is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SlackProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
color := "good" // Green for UP
|
||||
if msg.Status == "DOWN" {
|
||||
color = "danger" // Red for DOWN
|
||||
}
|
||||
|
||||
fields := []map[string]string{
|
||||
{
|
||||
"title": "Monitor",
|
||||
"value": msg.MonitorName,
|
||||
"short": "true",
|
||||
},
|
||||
{
|
||||
"title": "Status",
|
||||
"value": msg.Status,
|
||||
"short": "true",
|
||||
},
|
||||
}
|
||||
|
||||
if msg.MonitorURL != "" {
|
||||
fields = append(fields, map[string]string{
|
||||
"title": "URL",
|
||||
"value": msg.MonitorURL,
|
||||
"short": "false",
|
||||
})
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"attachments": []map[string]interface{}{
|
||||
{
|
||||
"color": color,
|
||||
"title": msg.Title,
|
||||
"text": msg.Body,
|
||||
"fields": fields,
|
||||
"timestamp": msg.Timestamp.Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if p.settings.Username != "" {
|
||||
payload["username"] = p.settings.Username
|
||||
}
|
||||
if p.settings.Channel != "" {
|
||||
payload["channel"] = p.settings.Channel
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", p.settings.WebhookURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("slack webhook request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("slack webhook returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type TelegramProvider struct {
|
||||
settings notification.TelegramSettings
|
||||
}
|
||||
|
||||
func NewTelegramProvider(settings notification.TelegramSettings) *TelegramProvider {
|
||||
return &TelegramProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) Validate() error {
|
||||
if p.settings.BotToken == "" {
|
||||
return fmt.Errorf("Telegram bot token is required")
|
||||
}
|
||||
if p.settings.ChatID == "" {
|
||||
return fmt.Errorf("Telegram chat ID is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TelegramProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
icon := "✅"
|
||||
if msg.Status == "DOWN" {
|
||||
icon = "❌"
|
||||
}
|
||||
|
||||
text := fmt.Sprintf("%s *%s*\n\n"+
|
||||
"*Monitor:* %s\n"+
|
||||
"*Status:* %s\n"+
|
||||
"*Time:* %s",
|
||||
icon,
|
||||
msg.Title,
|
||||
msg.MonitorName,
|
||||
msg.Status,
|
||||
msg.Timestamp.Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
|
||||
if msg.MonitorURL != "" {
|
||||
text += fmt.Sprintf("\n*URL:* %s", msg.MonitorURL)
|
||||
}
|
||||
|
||||
if msg.Ping > 0 {
|
||||
text += fmt.Sprintf("\n*Response Time:* %dms", msg.Ping)
|
||||
}
|
||||
|
||||
if msg.Message != "" {
|
||||
text += fmt.Sprintf("\n\n*Message:* %s", msg.Message)
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", p.settings.BotToken)
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("chat_id", p.settings.ChatID)
|
||||
data.Set("text", text)
|
||||
data.Set("parse_mode", "Markdown")
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.PostForm(apiURL, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("telegram API request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("telegram API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/notification"
|
||||
)
|
||||
|
||||
type WebhookProvider struct {
|
||||
settings notification.WebhookSettings
|
||||
}
|
||||
|
||||
func NewWebhookProvider(settings notification.WebhookSettings) *WebhookProvider {
|
||||
return &WebhookProvider{settings: settings}
|
||||
}
|
||||
|
||||
func (p *WebhookProvider) Validate() error {
|
||||
if p.settings.URL == "" {
|
||||
return fmt.Errorf("webhook URL is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WebhookProvider) Send(msg *notification.NotificationMessage) error {
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
method := p.settings.Method
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
body := p.formatBody(msg)
|
||||
req, err := http.NewRequest(method, p.settings.URL, bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
for k, v := range p.settings.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("webhook request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("webhook returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WebhookProvider) formatBody(msg *notification.NotificationMessage) string {
|
||||
if p.settings.BodyTemplate != "" {
|
||||
return p.settings.BodyTemplate
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"title": msg.Title,
|
||||
"body": msg.Body,
|
||||
"monitor": msg.MonitorName,
|
||||
"url": msg.MonitorURL,
|
||||
"status": msg.Status,
|
||||
"timestamp": msg.Timestamp,
|
||||
"ping": msg.Ping,
|
||||
"message": msg.Message,
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(data)
|
||||
return string(b)
|
||||
}
|
||||
Reference in New Issue
Block a user