From f77799837e0f44d99e810be5b4a7d82a05b89643 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 20 Oct 2016 15:06:59 +0200 Subject: [PATCH] feat(alerting): support for attached graphs in alert notifications closes #6183 --- emails/templates/alert_notification.html | 7 +- pkg/models/notifications.go | 11 +- pkg/services/alerting/notifiers/email.go | 29 +- pkg/services/alerting/test_notification.go | 5 +- pkg/services/notifications/email.go | 13 +- pkg/services/notifications/mailer.go | 127 +++++-- pkg/services/notifications/notifications.go | 11 +- public/emails/alert_notification.html | 171 +++++----- public/emails/invited_to_org.html | 108 +++--- public/emails/new_user_invite.html | 112 +++--- public/emails/reset_password.html | 102 +++--- public/emails/signup_started.html | 106 +++--- public/emails/welcome_on_signup.html | 108 +++--- .../alexcesaro/quotedprintable.v3/LICENSE | 20 ++ .../alexcesaro/quotedprintable.v3/README.md | 16 + .../quotedprintable.v3/encodedword.go | 279 +++++++++++++++ .../alexcesaro/quotedprintable.v3/pool.go | 26 ++ .../quotedprintable.v3/pool_go12.go | 24 ++ .../alexcesaro/quotedprintable.v3/reader.go | 121 +++++++ .../alexcesaro/quotedprintable.v3/writer.go | 166 +++++++++ vendor/gopkg.in/gomail.v2/CHANGELOG.md | 20 ++ vendor/gopkg.in/gomail.v2/CONTRIBUTING.md | 20 ++ vendor/gopkg.in/gomail.v2/LICENSE | 20 ++ vendor/gopkg.in/gomail.v2/README.md | 92 +++++ vendor/gopkg.in/gomail.v2/auth.go | 49 +++ vendor/gopkg.in/gomail.v2/doc.go | 5 + vendor/gopkg.in/gomail.v2/message.go | 322 ++++++++++++++++++ vendor/gopkg.in/gomail.v2/mime.go | 21 ++ vendor/gopkg.in/gomail.v2/mime_go14.go | 25 ++ vendor/gopkg.in/gomail.v2/send.go | 116 +++++++ vendor/gopkg.in/gomail.v2/smtp.go | 202 +++++++++++ vendor/gopkg.in/gomail.v2/writeto.go | 306 +++++++++++++++++ vendor/vendor.json | 16 +- 33 files changed, 2358 insertions(+), 418 deletions(-) create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go create mode 100644 vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go create mode 100644 vendor/gopkg.in/gomail.v2/CHANGELOG.md create mode 100644 vendor/gopkg.in/gomail.v2/CONTRIBUTING.md create mode 100644 vendor/gopkg.in/gomail.v2/LICENSE create mode 100644 vendor/gopkg.in/gomail.v2/README.md create mode 100644 vendor/gopkg.in/gomail.v2/auth.go create mode 100644 vendor/gopkg.in/gomail.v2/doc.go create mode 100644 vendor/gopkg.in/gomail.v2/message.go create mode 100644 vendor/gopkg.in/gomail.v2/mime.go create mode 100644 vendor/gopkg.in/gomail.v2/mime_go14.go create mode 100644 vendor/gopkg.in/gomail.v2/send.go create mode 100644 vendor/gopkg.in/gomail.v2/smtp.go create mode 100644 vendor/gopkg.in/gomail.v2/writeto.go diff --git a/emails/templates/alert_notification.html b/emails/templates/alert_notification.html index 2c9f78ce6fb..d0d69faa106 100644 --- a/emails/templates/alert_notification.html +++ b/emails/templates/alert_notification.html @@ -65,7 +65,12 @@
- + [[if ne .ImageLink "" ]] + Alerting Panel + [[end]] + [[if ne .EmbededImage "" ]] + Alerting Panel + [[end]]
diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index abbc6ec7e27..5be5f6438ae 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -5,11 +5,12 @@ import "errors" var ErrInvalidEmailCode = errors.New("Invalid or expired email code") type SendEmailCommand struct { - To []string - Template string - Data map[string]interface{} - Massive bool - Info string + To []string + Template string + Data map[string]interface{} + Massive bool + Info string + EmbededFiles []string } type SendEmailCommandSync struct { diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index 05a1665470e..c7f7bf00e3b 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -1,8 +1,7 @@ package notifiers import ( - "encoding/base64" - "io/ioutil" + "os" "strings" "github.com/grafana/grafana/pkg/bus" @@ -47,14 +46,6 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { return err } - imageLink := evalContext.ImagePublicUrl - if imageLink == "" { - imageBytes, err := ioutil.ReadFile(evalContext.ImageOnDiskPath) - if err == nil { - imageLink = "data:image/jpg;base64," + base64.StdEncoding.EncodeToString(imageBytes) - } - } - cmd := &m.SendEmailCommandSync{ SendEmailCommand: m.SendEmailCommand{ Data: map[string]interface{}{ @@ -64,15 +55,27 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { "StateModel": evalContext.GetStateModel(), "Message": evalContext.Rule.Message, "RuleUrl": ruleUrl, - "ImageLink": evalContext.ImagePublicUrl, + "ImageLink": "", + "EmbededImage": "", "AlertPageUrl": setting.AppUrl + "alerting", "EvalMatches": evalContext.EvalMatches, }, - To: this.Addresses, - Template: "alert_notification.html", + To: this.Addresses, + Template: "alert_notification.html", + EmbededFiles: []string{}, }, } + if evalContext.ImagePublicUrl != "" { + cmd.Data["ImageLink"] = evalContext.ImagePublicUrl + } else { + file, err := os.Stat(evalContext.ImageOnDiskPath) + if err == nil { + cmd.EmbededFiles = []string{evalContext.ImageOnDiskPath} + cmd.Data["EmbededImage"] = file.Name() + } + } + err = bus.DispatchCtx(evalContext.Ctx, cmd) if err != nil { diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index b21ee7b24d9..fd908d6f95d 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -37,9 +37,7 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error { return err } - notifier.sendNotifications(createTestEvalContext(), []Notifier{notifiers}) - - return nil + return notifier.sendNotifications(createTestEvalContext(), []Notifier{notifiers}) } func createTestEvalContext() *EvalContext { @@ -53,7 +51,6 @@ func createTestEvalContext() *EvalContext { ctx := NewEvalContext(context.TODO(), testRule) ctx.ImagePublicUrl = "http://grafana.org/assets/img/blog/mixed_styles.png" - ctx.IsTestRun = true ctx.Firing = true ctx.Error = nil diff --git a/pkg/services/notifications/email.go b/pkg/services/notifications/email.go index f81f3e1007b..792acdbaca3 100644 --- a/pkg/services/notifications/email.go +++ b/pkg/services/notifications/email.go @@ -6,12 +6,13 @@ import ( ) type Message struct { - To []string - From string - Subject string - Body string - Massive bool - Info string + To []string + From string + Subject string + Body string + Massive bool + Info string + EmbededFiles []string } // create mail content diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 3047065dd0d..2d58a1e5c71 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -14,12 +14,14 @@ import ( "net/mail" "net/smtp" "os" + "strconv" "strings" "time" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" + "gopkg.in/gomail.v2" ) var mailQueue chan *Message @@ -154,40 +156,102 @@ func sendToSmtpServer(recipients []string, msgContent []byte) error { } func buildAndSend(msg *Message) (int, error) { - log.Trace("Sending mails to: %s", strings.Join(msg.To, "; ")) - - // get message body - content := msg.Content() - - if len(msg.To) == 0 { - return 0, fmt.Errorf("empty receive emails") - } else if len(msg.Body) == 0 { - return 0, fmt.Errorf("empty email body") + m := gomail.NewMessage() + m.SetHeader("From", msg.From) + m.SetHeader("To", msg.To[0]) + m.SetHeader("Subject", msg.Subject) + for _, file := range msg.EmbededFiles { + m.Embed(file) } - if msg.Massive { - // send mail to multiple emails one by one - num := 0 - for _, to := range msg.To { - body := []byte("To: " + to + "\r\n" + content) - err := sendToSmtpServer([]string{to}, body) - if err != nil { - return num, err - } - num++ - } - return num, nil - } else { - body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content) + m.SetBody("text/html", msg.Body) - // send to multiple emails in one message - err := sendToSmtpServer(msg.To, body) + host, port, err := net.SplitHostPort(setting.Smtp.Host) + + if err != nil { + return 0, err + } + iPort, err := strconv.Atoi(port) + if err != nil { + return 0, err + } + + d := gomail.NewPlainDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password) + + tlsconfig := &tls.Config{ + InsecureSkipVerify: setting.Smtp.SkipVerify, + ServerName: host, + } + + if setting.Smtp.CertFile != "" { + cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile) if err != nil { return 0, err - } else { - return 1, nil } + tlsconfig.Certificates = []tls.Certificate{cert} } + + d.TLSConfig = tlsconfig + if err := d.DialAndSend(m); err != nil { + return 0, err + } + + return 0, nil + /* + m := email.NewHTMLMessage(msg.Subject, msg.Body) + m.From = mail.Address{Name: "From", Address: "alerting@grafana.org"} + m.To = msg.To + + log.Info2("Attaching file", "file", msg.Attachment) + if err := m.Attach(msg.Attachment); err != nil { + return 0, err + } + + // send it + host, _, _ := net.SplitHostPort(setting.Smtp.Host) + + auth := smtp.PlainAuth("", setting.Smtp.User, setting.Smtp.Password, host) + if err := email.Send(setting.Smtp.Host, auth, m); err != nil { + return 0, err + } + + return 0, nil + + log.Trace("Sending mails to: %s", strings.Join(msg.To, "; ")) + + // get message body + content := msg.Content() + + if len(msg.To) == 0 { + return 0, fmt.Errorf("empty receive emails") + } else if len(msg.Body) == 0 { + return 0, fmt.Errorf("empty email body") + } + + if msg.Massive { + // send mail to multiple emails one by one + num := 0 + for _, to := range msg.To { + body := []byte("To: " + to + "\r\n" + content) + err := sendToSmtpServer([]string{to}, body) + if err != nil { + return num, err + } + num++ + } + return num, nil + } else { + body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content) + + // send to multiple emails in one message + err := sendToSmtpServer(msg.To, body) + if err != nil { + return 0, err + } else { + return 1, nil + } + } + */ } func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { @@ -229,9 +293,10 @@ func buildEmailMessage(cmd *m.SendEmailCommand) (*Message, error) { } return &Message{ - To: cmd.To, - From: setting.Smtp.FromAddress, - Subject: subjectBuffer.String(), - Body: buffer.String(), + To: cmd.To, + From: setting.Smtp.FromAddress, + Subject: subjectBuffer.String(), + Body: buffer.String(), + EmbededFiles: cmd.EmbededFiles, }, nil } diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index 9aa30b94edd..db36e4e96fb 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -88,11 +88,12 @@ func subjectTemplateFunc(obj map[string]interface{}, value string) string { func sendEmailCommandHandlerSync(ctx context.Context, cmd *m.SendEmailCommandSync) error { message, err := buildEmailMessage(&m.SendEmailCommand{ - Data: cmd.Data, - Info: cmd.Info, - Massive: cmd.Massive, - Template: cmd.Template, - To: cmd.To, + Data: cmd.Data, + Info: cmd.Info, + Massive: cmd.Massive, + Template: cmd.Template, + To: cmd.To, + EmbededFiles: cmd.EmbededFiles, }) if err != nil { diff --git a/public/emails/alert_notification.html b/public/emails/alert_notification.html index e91ab6450fe..15668c1924c 100644 --- a/public/emails/alert_notification.html +++ b/public/emails/alert_notification.html @@ -5,7 +5,7 @@ - - - -
-
+ + +
+
- - -
-
+ + +
+
- - -
+ + +
- - -
- + + + - +
+
@@ -200,21 +200,21 @@ text-decoration: underline;
- - - +
+ + - - +
+
{{Subject .Subject "{{.Title}}"}} - - -
- - -
-

{{.Title}}

+ + +
+ + +
+

{{.Title}}

@@ -222,13 +222,13 @@ text-decoration: underline;
- - -
- - -
-

{{.Message}}

+ + +
+ + +
+

{{.Message}}

@@ -237,26 +237,26 @@ text-decoration: underline;
{{if ne .State "ok" }} - - -
-
- - -
-
Metric name
+ + +
+
+ + + - {{range .EvalMatches}} - - + - {{end}} @@ -267,13 +267,18 @@ text-decoration: underline;
+
Metric name
-
Value
+
+
Value
-
{{.Metric}}
+
+
{{.Metric}}
-
{{.Value}}
+
+
{{.Value}}
{{end}} - - -
- - -
- + + +
+ + +
+ {{if ne .ImageLink "" }} + Alerting Panel + {{end}} + {{if ne .EmbededImage "" }} + Alerting Panel + {{end}}
@@ -282,25 +287,25 @@ text-decoration: underline;
- - -
- - -
- - -
- View your Alert rule + + +
+ + + -
+ + +
+ View your Alert rule
- - -
- Go to the Alerts page + + + +
+ Go to the Alerts page
@@ -318,20 +323,20 @@ text-decoration: underline;
- - -