소스 검색

feat(alerting): add support for email notifications

bergquist 9 년 전
부모
커밋
212fd27252

+ 29 - 0
emails/templates/alert_notification.html

@@ -0,0 +1,29 @@
+<!-- This email is sent when an existing user is added to an organization -->
+
+[[Subject .Subject "Grafana Alert: [ [[.State]] ] [[.Name]]" ]]
+
+Alertstate: [[.State]]<br />
+[[.AlertPageUrl]]"<br />
+[[.DashboardLink]]"<br />
+[[.Description]]<br />
+
+[[if eq .State "Ok"]]
+    Everything is Ok     
+[[end]]
+
+[[if ne .State "Ok" ]]
+    <table class="row">
+        <tr>
+            <td class="expander">Serie</td>
+            <td class="expander">State</td>
+            <td class="expander">Actual value</td>
+        </tr>
+        [[ range $ta := .TriggeredAlerts]]            
+        <tr>
+            <td class="expander">[[$ta.Name]]</td>
+            <td class="expander">[[$ta.State]]</td>
+            <td class="expander">[[$ta.ActualValue]]</td>
+        </tr>
+        [[end]]
+    </table>
+[[end]]

+ 15 - 4
pkg/services/alerting/notifier.go

@@ -8,6 +8,7 @@ import (
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/alerting/alertstates"
+	"github.com/grafana/grafana/pkg/setting"
 )
 
 type NotifierImpl struct {
@@ -50,18 +51,28 @@ type EmailNotifier struct {
 
 func (this *EmailNotifier) Dispatch(alertResult *AlertResult) {
 	this.log.Info("Sending email")
+	grafanaUrl := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
+	if setting.AppSubUrl != "" {
+		grafanaUrl += "/" + setting.AppSubUrl
+	}
+
 	cmd := &m.SendEmailCommand{
 		Data: map[string]interface{}{
+			"Name":            "Name",
+			"State":           alertResult.State,
 			"Description":     alertResult.Description,
 			"TriggeredAlerts": alertResult.TriggeredAlerts,
+			"DashboardLink":   grafanaUrl + "/dashboard/db/alerting",
+			"AlertPageUrl":    grafanaUrl + "/alerting",
 		},
 		To:       []string{this.To},
-		Info:     "Alert result",
-		Massive:  false,
-		Template: "",
+		Template: "alert_notification.html",
 	}
 
-	bus.Dispatch(cmd)
+	err := bus.Dispatch(cmd)
+	if err != nil {
+		this.log.Error("Could not send alert notification as email", "error", err)
+	}
 }
 
 type WebhookNotifier struct {

+ 84 - 1
pkg/services/notifications/notifications_test.go

@@ -9,6 +9,12 @@ import (
 	. "github.com/smartystreets/goconvey/convey"
 )
 
+type testTriggeredAlert struct {
+	ActualValue float64
+	Name        string
+	State       string
+}
+
 func TestNotifications(t *testing.T) {
 
 	Convey("Given the notifications service", t, func() {
@@ -34,6 +40,83 @@ func TestNotifications(t *testing.T) {
 			So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com")
 			So(sentMsg.Body, ShouldNotContainSubstring, "Subject")
 		})
-	})
 
+		Convey("Alert notifications", func() {
+			Convey("When sending reset email password", func() {
+				cmd := &m.SendEmailCommand{
+					Data: map[string]interface{}{
+						"Name":          "Name",
+						"State":         "Critical",
+						"Description":   "Description",
+						"DashboardLink": "http://localhost:3000/dashboard/db/alerting",
+						"AlertPageUrl":  "http://localhost:3000/alerting",
+						"TriggeredAlerts": []testTriggeredAlert{
+							{Name: "desktop", State: "Critical", ActualValue: 13},
+							{Name: "mobile", State: "Warn", ActualValue: 5},
+						},
+					},
+					To:       []string{"asd@asd.com "},
+					Template: "alert_notification.html",
+				}
+
+				err := sendEmailCommandHandler(cmd)
+				So(err, ShouldBeNil)
+
+				So(sentMsg.Body, ShouldContainSubstring, "Alertstate: Critical")
+				So(sentMsg.Body, ShouldContainSubstring, "http://localhost:3000/dashboard/db/alerting")
+				So(sentMsg.Body, ShouldContainSubstring, "Critical")
+				So(sentMsg.Body, ShouldContainSubstring, "Warn")
+				So(sentMsg.Body, ShouldContainSubstring, "mobile")
+				So(sentMsg.Body, ShouldContainSubstring, "desktop")
+
+				So(sentMsg.Subject, ShouldContainSubstring, "Grafana Alert: [ Critical ] ")
+			})
+
+			Convey("given critical", func() {
+				cmd := &m.SendEmailCommand{
+					Data: map[string]interface{}{
+						"Name":          "Name",
+						"State":         "Warn",
+						"Description":   "Description",
+						"DashboardLink": "http://localhost:3000/dashboard/db/alerting",
+						"AlertPageUrl":  "http://localhost:3000/alerting",
+						"TriggeredAlerts": []testTriggeredAlert{
+							{Name: "desktop", State: "Critical", ActualValue: 13},
+							{Name: "mobile", State: "Warn", ActualValue: 5},
+						},
+					},
+					To:       []string{"asd@asd.com "},
+					Template: "alert_notification.html",
+				}
+
+				err := sendEmailCommandHandler(cmd)
+				So(err, ShouldBeNil)
+				So(sentMsg.Body, ShouldContainSubstring, "Alertstate: Warn")
+				So(sentMsg.Body, ShouldContainSubstring, "http://localhost:3000/dashboard/db/alerting")
+				So(sentMsg.Body, ShouldContainSubstring, "Critical")
+				So(sentMsg.Body, ShouldContainSubstring, "Warn")
+				So(sentMsg.Body, ShouldContainSubstring, "mobile")
+				So(sentMsg.Body, ShouldContainSubstring, "desktop")
+				So(sentMsg.Subject, ShouldContainSubstring, "Grafana Alert: [ Warn ]")
+			})
+
+			Convey("given ok", func() {
+				cmd := &m.SendEmailCommand{
+					Data: map[string]interface{}{
+						"Name":          "Name",
+						"State":         "Ok",
+						"Description":   "Description",
+						"DashboardLink": "http://localhost:3000/dashboard/db/alerting",
+						"AlertPageUrl":  "http://localhost:3000/alerting",
+					},
+					To:       []string{"asd@asd.com "},
+					Template: "alert_notification.html",
+				}
+
+				err := sendEmailCommandHandler(cmd)
+				So(err, ShouldBeNil)
+				So(sentMsg.Subject, ShouldContainSubstring, "Grafana Alert: [ Ok ]")
+			})
+		})
+	})
 }

+ 177 - 0
public/emails/alert_notification.html

@@ -0,0 +1,177 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<meta name="viewport" content="width=device-width" />
+   
+</head>
+<body style="-ms-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; line-height: 19px; margin: 0; min-width: 100%; padding: 0; text-align: left; width: 100% !important"><style type="text/css">
+body {
+width: 100% !important; min-width: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; margin: 0; padding: 0;
+}
+img {
+outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; width: auto; max-width: 100%; float: left; clear: both; display: block;
+}
+body {
+color: #222222; font-family: "Helvetica", "Arial", sans-serif; font-weight: normal; padding: 0; margin: 0; text-align: left; line-height: 1.3;
+}
+body {
+font-size: 14px; line-height: 19px;
+}
+a:hover {
+color: #2795b6 !important;
+}
+a:active {
+color: #2795b6 !important;
+}
+a:visited {
+color: #2ba6cb !important;
+}
+body {
+font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;
+}
+a:hover {
+color: #ff8f2b !important;
+}
+a:active {
+color: #F2821E !important;
+}
+a:visited {
+color: #E67612 !important;
+}
+.better-button:hover a {
+color: #FFFFFF !important; background-color: #F2821E; border: 1px solid #F2821E;
+}
+.better-button:visited a {
+color: #FFFFFF !important;
+}
+.better-button:active a {
+color: #FFFFFF !important;
+}
+@media only screen and (max-width: 600px) {
+  table[class="body"] img {
+    width: auto !important; height: auto !important;
+  }
+  table[class="body"] center {
+    min-width: 0 !important;
+  }
+  table[class="body"] .container {
+    width: 95% !important;
+  }
+  table[class="body"] .row {
+    width: 100% !important; display: block !important;
+  }
+  table[class="body"] .wrapper {
+    display: block !important; padding-right: 0 !important;
+  }
+  table[class="body"] .columns {
+    table-layout: fixed !important; float: none !important; width: 100% !important; padding-right: 0px !important; padding-left: 0px !important; display: block !important;
+  }
+  table[class="body"] table.columns td {
+    width: 100% !important;
+  }
+  table[class="body"] .columns td.six {
+    width: 50% !important;
+  }
+  table[class="body"] table.columns td.expander {
+    width: 1px !important;
+  }
+}
+</style>
+	<table class="body" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; border-collapse: collapse; border-spacing: 0; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; height: 100%; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; width: 100%">
+		<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+			<td class="center" align="center" valign="top" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: center; vertical-align: top; word-break: break-word">
+        <center style="min-width: 580px; width: 100%">
+
+          <table class="row header" style="background: #333; border-collapse: collapse; border-spacing: 0; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%" bgcolor="#333">
+            <tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+              <td class="center" align="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: center; vertical-align: top; word-break: break-word" valign="top">
+                <center style="min-width: 580px; width: 100%">
+
+                  <table class="container" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: inherit; vertical-align: top; width: 580px">
+                    <tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+                      <td class="wrapper last" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 10px 0px 0px; position: relative; text-align: left; vertical-align: top; word-break: break-word" align="left" valign="top">
+
+                        <table class="twelve columns" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: left; vertical-align: top; width: 580px">
+                          <tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+                            <td class="six sub-columns center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; min-width: 0px; padding: 0px 10px 10px 0px; text-align: center; vertical-align: top; width: 50%; word-break: break-word" align="center" valign="top">
+															<img src="http://docs.grafana.org/img/logo_transparent_200x75.png" style="-ms-interpolation-mode: bicubic; clear: both; display: inline; float: none; max-width: 100%; outline: none; text-decoration: none; width: 150px" align="none" />
+                            </td>
+														<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top"></td>
+                          </tr>
+                        </table>
+
+                      </td>
+                    </tr>
+                  </table>
+
+                </center>
+              </td>
+            </tr>
+          </table>
+
+					<table class="container" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: inherit; vertical-align: top; width: 580px">
+						<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+							<td style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; word-break: break-word" align="left" valign="top">
+								
+
+{{Subject .Subject "Grafana Alert: [ {{.State}} ] {{.Name}}" }}
+
+Alertstate: {{.State}}<br />
+{{.AlertPageUrl}}"<br />
+{{.DashboardLink}}"<br />
+{{.Description}}<br />
+
+{{if eq .State "Ok"}}
+    Everything is Ok     
+{{end}}
+
+{{if ne .State "Ok" }}
+    <table class="row" style="border-collapse: collapse; border-spacing: 0; display: block; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%">
+        <tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">Serie</td>
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">State</td>
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">Actual value</td>
+        </tr>
+        {{ range $ta := .TriggeredAlerts}}            
+        <tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.Name}}</td>
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.State}}</td>
+            <td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.ActualValue}}</td>
+        </tr>
+        {{end}}
+    </table>
+{{end}}
+
+								
+								<table class="row footer" style="border-collapse: collapse; border-spacing: 0; display: block; margin-top: 20px; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%">
+									<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+										<td class="wrapper last" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 10px 0px 0px; position: relative; text-align: left; vertical-align: top; word-break: break-word" align="left" valign="top">
+											<table class="twelve columns" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: left; vertical-align: top; width: 580px">
+												<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
+													<td align="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: left; vertical-align: top; word-break: break-word" valign="top">
+														<center style="min-width: 580px; width: 100%">
+															<p style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; line-height: 19px; margin: 0 0 10px; padding: 0; text-align: center" align="center">
+																Sent by <a href="{{.AppUrl}}" style="color: #E67612; text-decoration: none">Grafana v{{.BuildVersion}}</a>
+															</p>
+														</center>
+													</td>
+													<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top"></td>
+												</tr>
+											</table>
+										</td>
+									</tr>
+								</table>
+
+								
+							</td>
+						</tr>
+
+					</table>
+				</center>
+			</td>
+		</tr>
+
+	</table>
+</body>
+</html>