- Replace all emoji characters with ASCII equivalents throughout codebase - Replace Unicode box-drawing characters (═║╔╗╚╝━─) with ASCII (+|-=) - Replace checkmarks (✓✗) with [OK]/[FAIL] markers - 59 files updated, 741 lines changed - Improves terminal compatibility and reduces visual noise
498 lines
17 KiB
Go
498 lines
17 KiB
Go
// Package notify - Notification templates
|
|
package notify
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// TemplateType represents the notification format type
|
|
type TemplateType string
|
|
|
|
const (
|
|
TemplateText TemplateType = "text"
|
|
TemplateHTML TemplateType = "html"
|
|
TemplateMarkdown TemplateType = "markdown"
|
|
TemplateSlack TemplateType = "slack"
|
|
)
|
|
|
|
// Templates holds notification templates
|
|
type Templates struct {
|
|
Subject string
|
|
TextBody string
|
|
HTMLBody string
|
|
}
|
|
|
|
// DefaultTemplates returns default notification templates
|
|
func DefaultTemplates() map[EventType]Templates {
|
|
return map[EventType]Templates{
|
|
EventBackupStarted: {
|
|
Subject: "[EXEC] Backup Started: {{.Database}} on {{.Hostname}}",
|
|
TextBody: backupStartedText,
|
|
HTMLBody: backupStartedHTML,
|
|
},
|
|
EventBackupCompleted: {
|
|
Subject: "[OK] Backup Completed: {{.Database}} on {{.Hostname}}",
|
|
TextBody: backupCompletedText,
|
|
HTMLBody: backupCompletedHTML,
|
|
},
|
|
EventBackupFailed: {
|
|
Subject: "[FAIL] Backup FAILED: {{.Database}} on {{.Hostname}}",
|
|
TextBody: backupFailedText,
|
|
HTMLBody: backupFailedHTML,
|
|
},
|
|
EventRestoreStarted: {
|
|
Subject: "[EXEC] Restore Started: {{.Database}} on {{.Hostname}}",
|
|
TextBody: restoreStartedText,
|
|
HTMLBody: restoreStartedHTML,
|
|
},
|
|
EventRestoreCompleted: {
|
|
Subject: "[OK] Restore Completed: {{.Database}} on {{.Hostname}}",
|
|
TextBody: restoreCompletedText,
|
|
HTMLBody: restoreCompletedHTML,
|
|
},
|
|
EventRestoreFailed: {
|
|
Subject: "[FAIL] Restore FAILED: {{.Database}} on {{.Hostname}}",
|
|
TextBody: restoreFailedText,
|
|
HTMLBody: restoreFailedHTML,
|
|
},
|
|
EventVerificationPassed: {
|
|
Subject: "[OK] Verification Passed: {{.Database}}",
|
|
TextBody: verificationPassedText,
|
|
HTMLBody: verificationPassedHTML,
|
|
},
|
|
EventVerificationFailed: {
|
|
Subject: "[FAIL] Verification FAILED: {{.Database}}",
|
|
TextBody: verificationFailedText,
|
|
HTMLBody: verificationFailedHTML,
|
|
},
|
|
EventDRDrillPassed: {
|
|
Subject: "[OK] DR Drill Passed: {{.Database}}",
|
|
TextBody: drDrillPassedText,
|
|
HTMLBody: drDrillPassedHTML,
|
|
},
|
|
EventDRDrillFailed: {
|
|
Subject: "[FAIL] DR Drill FAILED: {{.Database}}",
|
|
TextBody: drDrillFailedText,
|
|
HTMLBody: drDrillFailedHTML,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Template strings
|
|
const backupStartedText = `
|
|
Backup Operation Started
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Started At: {{formatTime .Timestamp}}
|
|
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
`
|
|
|
|
const backupStartedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #3498db;">[EXEC] Backup Started</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Started At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
</div>
|
|
`
|
|
|
|
const backupCompletedText = `
|
|
Backup Operation Completed Successfully
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Completed: {{formatTime .Timestamp}}
|
|
{{with .Details}}
|
|
{{if .size}}Size: {{.size}}{{end}}
|
|
{{if .duration}}Duration: {{.duration}}{{end}}
|
|
{{if .path}}Path: {{.path}}{{end}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
`
|
|
|
|
const backupCompletedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #27ae60;">[OK] Backup Completed</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Completed:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{with .Details}}
|
|
{{if .size}}<tr><td style="padding: 8px; font-weight: bold;">Size:</td><td style="padding: 8px;">{{.size}}</td></tr>{{end}}
|
|
{{if .duration}}<tr><td style="padding: 8px; font-weight: bold;">Duration:</td><td style="padding: 8px;">{{.duration}}</td></tr>{{end}}
|
|
{{if .path}}<tr><td style="padding: 8px; font-weight: bold;">Path:</td><td style="padding: 8px;">{{.path}}</td></tr>{{end}}
|
|
{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px; color: #27ae60;">{{.Message}}</p>{{end}}
|
|
</div>
|
|
`
|
|
|
|
const backupFailedText = `
|
|
[WARN] BACKUP FAILED [WARN]
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Failed At: {{formatTime .Timestamp}}
|
|
{{if .Error}}
|
|
Error: {{.Error}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
|
|
Please investigate immediately.
|
|
`
|
|
|
|
const backupFailedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #e74c3c;">[FAIL] Backup FAILED</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Failed At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{if .Error}}<tr><td style="padding: 8px; font-weight: bold; color: #e74c3c;">Error:</td><td style="padding: 8px; color: #e74c3c;">{{.Error}}</td></tr>{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
<p style="margin-top: 20px; color: #e74c3c; font-weight: bold;">Please investigate immediately.</p>
|
|
</div>
|
|
`
|
|
|
|
const restoreStartedText = `
|
|
Restore Operation Started
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Started At: {{formatTime .Timestamp}}
|
|
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
`
|
|
|
|
const restoreStartedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #3498db;">[EXEC] Restore Started</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Started At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
</div>
|
|
`
|
|
|
|
const restoreCompletedText = `
|
|
Restore Operation Completed Successfully
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Completed: {{formatTime .Timestamp}}
|
|
{{with .Details}}
|
|
{{if .duration}}Duration: {{.duration}}{{end}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
`
|
|
|
|
const restoreCompletedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #27ae60;">[OK] Restore Completed</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Completed:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{with .Details}}
|
|
{{if .duration}}<tr><td style="padding: 8px; font-weight: bold;">Duration:</td><td style="padding: 8px;">{{.duration}}</td></tr>{{end}}
|
|
{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px; color: #27ae60;">{{.Message}}</p>{{end}}
|
|
</div>
|
|
`
|
|
|
|
const restoreFailedText = `
|
|
[WARN] RESTORE FAILED [WARN]
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Failed At: {{formatTime .Timestamp}}
|
|
{{if .Error}}
|
|
Error: {{.Error}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
|
|
Please investigate immediately.
|
|
`
|
|
|
|
const restoreFailedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #e74c3c;">[FAIL] Restore FAILED</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Failed At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{if .Error}}<tr><td style="padding: 8px; font-weight: bold; color: #e74c3c;">Error:</td><td style="padding: 8px; color: #e74c3c;">{{.Error}}</td></tr>{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
<p style="margin-top: 20px; color: #e74c3c; font-weight: bold;">Please investigate immediately.</p>
|
|
</div>
|
|
`
|
|
|
|
const verificationPassedText = `
|
|
Backup Verification Passed
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Verified: {{formatTime .Timestamp}}
|
|
{{with .Details}}
|
|
{{if .checksum}}Checksum: {{.checksum}}{{end}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
`
|
|
|
|
const verificationPassedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #27ae60;">[OK] Verification Passed</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Verified:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{with .Details}}
|
|
{{if .checksum}}<tr><td style="padding: 8px; font-weight: bold;">Checksum:</td><td style="padding: 8px; font-family: monospace;">{{.checksum}}</td></tr>{{end}}
|
|
{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px; color: #27ae60;">{{.Message}}</p>{{end}}
|
|
</div>
|
|
`
|
|
|
|
const verificationFailedText = `
|
|
[WARN] VERIFICATION FAILED [WARN]
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Failed At: {{formatTime .Timestamp}}
|
|
{{if .Error}}
|
|
Error: {{.Error}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
|
|
Backup integrity may be compromised. Please investigate.
|
|
`
|
|
|
|
const verificationFailedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #e74c3c;">[FAIL] Verification FAILED</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Failed At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{if .Error}}<tr><td style="padding: 8px; font-weight: bold; color: #e74c3c;">Error:</td><td style="padding: 8px; color: #e74c3c;">{{.Error}}</td></tr>{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
<p style="margin-top: 20px; color: #e74c3c; font-weight: bold;">Backup integrity may be compromised. Please investigate.</p>
|
|
</div>
|
|
`
|
|
|
|
const drDrillPassedText = `
|
|
DR Drill Test Passed
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Tested At: {{formatTime .Timestamp}}
|
|
{{with .Details}}
|
|
{{if .tables_restored}}Tables: {{.tables_restored}}{{end}}
|
|
{{if .rows_validated}}Rows: {{.rows_validated}}{{end}}
|
|
{{if .duration}}Duration: {{.duration}}{{end}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
|
|
Backup restore capability verified.
|
|
`
|
|
|
|
const drDrillPassedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #27ae60;">[OK] DR Drill Passed</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Tested At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{with .Details}}
|
|
{{if .tables_restored}}<tr><td style="padding: 8px; font-weight: bold;">Tables:</td><td style="padding: 8px;">{{.tables_restored}}</td></tr>{{end}}
|
|
{{if .rows_validated}}<tr><td style="padding: 8px; font-weight: bold;">Rows:</td><td style="padding: 8px;">{{.rows_validated}}</td></tr>{{end}}
|
|
{{if .duration}}<tr><td style="padding: 8px; font-weight: bold;">Duration:</td><td style="padding: 8px;">{{.duration}}</td></tr>{{end}}
|
|
{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px; color: #27ae60;">{{.Message}}</p>{{end}}
|
|
<p style="margin-top: 20px; color: #27ae60;">[OK] Backup restore capability verified</p>
|
|
</div>
|
|
`
|
|
|
|
const drDrillFailedText = `
|
|
[WARN] DR DRILL FAILED [WARN]
|
|
|
|
Database: {{.Database}}
|
|
Hostname: {{.Hostname}}
|
|
Failed At: {{formatTime .Timestamp}}
|
|
{{if .Error}}
|
|
Error: {{.Error}}
|
|
{{end}}
|
|
{{if .Message}}{{.Message}}{{end}}
|
|
|
|
Backup may not be restorable. Please investigate immediately.
|
|
`
|
|
|
|
const drDrillFailedHTML = `
|
|
<div style="font-family: Arial, sans-serif; padding: 20px;">
|
|
<h2 style="color: #e74c3c;">[FAIL] DR Drill FAILED</h2>
|
|
<table style="border-collapse: collapse; width: 100%; max-width: 600px;">
|
|
<tr><td style="padding: 8px; font-weight: bold;">Database:</td><td style="padding: 8px;">{{.Database}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Hostname:</td><td style="padding: 8px;">{{.Hostname}}</td></tr>
|
|
<tr><td style="padding: 8px; font-weight: bold;">Failed At:</td><td style="padding: 8px;">{{formatTime .Timestamp}}</td></tr>
|
|
{{if .Error}}<tr><td style="padding: 8px; font-weight: bold; color: #e74c3c;">Error:</td><td style="padding: 8px; color: #e74c3c;">{{.Error}}</td></tr>{{end}}
|
|
</table>
|
|
{{if .Message}}<p style="margin-top: 20px;">{{.Message}}</p>{{end}}
|
|
<p style="margin-top: 20px; color: #e74c3c; font-weight: bold;">Backup may not be restorable. Please investigate immediately.</p>
|
|
</div>
|
|
`
|
|
|
|
// TemplateRenderer renders notification templates
|
|
type TemplateRenderer struct {
|
|
templates map[EventType]Templates
|
|
funcMap template.FuncMap
|
|
}
|
|
|
|
// NewTemplateRenderer creates a new template renderer
|
|
func NewTemplateRenderer() *TemplateRenderer {
|
|
return &TemplateRenderer{
|
|
templates: DefaultTemplates(),
|
|
funcMap: template.FuncMap{
|
|
"formatTime": func(t time.Time) string {
|
|
return t.Format("2006-01-02 15:04:05 MST")
|
|
},
|
|
"upper": strings.ToUpper,
|
|
"lower": strings.ToLower,
|
|
},
|
|
}
|
|
}
|
|
|
|
// RenderSubject renders the subject template for an event
|
|
func (r *TemplateRenderer) RenderSubject(event *Event) (string, error) {
|
|
tmpl, ok := r.templates[event.Type]
|
|
if !ok {
|
|
return fmt.Sprintf("[%s] %s: %s", event.Severity, event.Type, event.Database), nil
|
|
}
|
|
|
|
return r.render(tmpl.Subject, event)
|
|
}
|
|
|
|
// RenderText renders the text body template for an event
|
|
func (r *TemplateRenderer) RenderText(event *Event) (string, error) {
|
|
tmpl, ok := r.templates[event.Type]
|
|
if !ok {
|
|
return event.Message, nil
|
|
}
|
|
|
|
return r.render(tmpl.TextBody, event)
|
|
}
|
|
|
|
// RenderHTML renders the HTML body template for an event
|
|
func (r *TemplateRenderer) RenderHTML(event *Event) (string, error) {
|
|
tmpl, ok := r.templates[event.Type]
|
|
if !ok {
|
|
return fmt.Sprintf("<p>%s</p>", event.Message), nil
|
|
}
|
|
|
|
return r.render(tmpl.HTMLBody, event)
|
|
}
|
|
|
|
// render executes a template with the given event
|
|
func (r *TemplateRenderer) render(templateStr string, event *Event) (string, error) {
|
|
tmpl, err := template.New("notification").Funcs(r.funcMap).Parse(templateStr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse template: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, event); err != nil {
|
|
return "", fmt.Errorf("failed to execute template: %w", err)
|
|
}
|
|
|
|
return strings.TrimSpace(buf.String()), nil
|
|
}
|
|
|
|
// SetTemplate sets a custom template for an event type
|
|
func (r *TemplateRenderer) SetTemplate(eventType EventType, templates Templates) {
|
|
r.templates[eventType] = templates
|
|
}
|
|
|
|
// RenderSlackMessage creates a Slack-formatted message
|
|
func (r *TemplateRenderer) RenderSlackMessage(event *Event) map[string]interface{} {
|
|
color := "#3498db" // blue
|
|
switch event.Severity {
|
|
case SeveritySuccess:
|
|
color = "#27ae60" // green
|
|
case SeverityWarning:
|
|
color = "#f39c12" // orange
|
|
case SeverityError, SeverityCritical:
|
|
color = "#e74c3c" // red
|
|
}
|
|
|
|
fields := []map[string]interface{}{
|
|
{
|
|
"title": "Database",
|
|
"value": event.Database,
|
|
"short": true,
|
|
},
|
|
{
|
|
"title": "Hostname",
|
|
"value": event.Hostname,
|
|
"short": true,
|
|
},
|
|
{
|
|
"title": "Event",
|
|
"value": string(event.Type),
|
|
"short": true,
|
|
},
|
|
{
|
|
"title": "Severity",
|
|
"value": string(event.Severity),
|
|
"short": true,
|
|
},
|
|
}
|
|
|
|
if event.Error != "" {
|
|
fields = append(fields, map[string]interface{}{
|
|
"title": "Error",
|
|
"value": event.Error,
|
|
"short": false,
|
|
})
|
|
}
|
|
|
|
for key, value := range event.Details {
|
|
fields = append(fields, map[string]interface{}{
|
|
"title": key,
|
|
"value": value,
|
|
"short": true,
|
|
})
|
|
}
|
|
|
|
subject, _ := r.RenderSubject(event)
|
|
|
|
return map[string]interface{}{
|
|
"attachments": []map[string]interface{}{
|
|
{
|
|
"color": color,
|
|
"title": subject,
|
|
"text": event.Message,
|
|
"fields": fields,
|
|
"footer": "dbbackup",
|
|
"ts": event.Timestamp.Unix(),
|
|
"mrkdwn_in": []string{"text", "fields"},
|
|
},
|
|
},
|
|
}
|
|
}
|