diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index 1020f28f874..e60a967f3fa 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -50,6 +50,7 @@ var (
M_Alerting_Notification_Sent_OpsGenie Counter
M_Alerting_Notification_Sent_Telegram Counter
M_Alerting_Notification_Sent_Sensu Counter
+ M_Alerting_Notification_Sent_Pushover Counter
M_Aws_CloudWatch_GetMetricStatistics Counter
M_Aws_CloudWatch_ListMetrics Counter
@@ -120,6 +121,7 @@ func initMetricVars(settings *MetricSettings) {
M_Alerting_Notification_Sent_Telegram = RegCounter("alerting.notifications_sent", "type", "telegram")
M_Alerting_Notification_Sent_Sensu = RegCounter("alerting.notifications_sent", "type", "sensu")
M_Alerting_Notification_Sent_LINE = RegCounter("alerting.notifications_sent", "type", "LINE")
+ M_Alerting_Notification_Sent_Pushover = RegCounter("alerting.notifications_sent", "type", "pushover")
M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics")
M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics")
diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go
new file mode 100644
index 00000000000..c68ad421c62
--- /dev/null
+++ b/pkg/services/alerting/notifiers/pushover.go
@@ -0,0 +1,177 @@
+package notifiers
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+
+ "github.com/grafana/grafana/pkg/bus"
+ "github.com/grafana/grafana/pkg/log"
+ "github.com/grafana/grafana/pkg/metrics"
+ m "github.com/grafana/grafana/pkg/models"
+ "github.com/grafana/grafana/pkg/services/alerting"
+)
+
+const PUSHOVER_ENDPOINT = "https://api.pushover.net/1/messages.json"
+
+func init() {
+ alerting.RegisterNotifier(&alerting.NotifierPlugin{
+ Type: "pushover",
+ Name: "Pushover",
+ Description: "Sends HTTP POST request to the Pushover API",
+ Factory: NewPushoverNotifier,
+ OptionsTemplate: `
+
Pushover settings
+
+ API Token
+
+
+
+ User key(s)
+
+
+
+ Device(s) (optional)
+
+
+
+ Priority
+
+
+
+ Retry
+
+ Expire
+
+
+
+ Sound
+
+
+ `,
+ })
+}
+
+func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
+ userKey := model.Settings.Get("userKey").MustString()
+ apiToken := model.Settings.Get("apiToken").MustString()
+ device := model.Settings.Get("device").MustString()
+ priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString())
+ retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
+ expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString())
+ sound := model.Settings.Get("sound").MustString()
+
+ if userKey == "" {
+ return nil, alerting.ValidationError{Reason: "User key not given"}
+ }
+ if apiToken == "" {
+ return nil, alerting.ValidationError{Reason: "API token not given"}
+ }
+ return &PushoverNotifier{
+ NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+ UserKey: userKey,
+ ApiToken: apiToken,
+ Priority: priority,
+ Retry: retry,
+ Expire: expire,
+ Device: device,
+ Sound: sound,
+ log: log.New("alerting.notifier.pushover"),
+ }, nil
+}
+
+type PushoverNotifier struct {
+ NotifierBase
+ UserKey string
+ ApiToken string
+ Priority int
+ Retry int
+ Expire int
+ Device string
+ Sound string
+ log log.Logger
+}
+
+func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
+ metrics.M_Alerting_Notification_Sent_Pushover.Inc(1)
+ ruleUrl, err := evalContext.GetRuleUrl()
+ if err != nil {
+ this.log.Error("Failed get rule link", "error", err)
+ return err
+ }
+ message := evalContext.Rule.Message
+ for idx, evt := range evalContext.EvalMatches {
+ message += fmt.Sprintf("\n%s: %v", evt.Metric, evt.Value)
+ if idx > 4 {
+ break
+ }
+ }
+ if evalContext.Error != nil {
+ message += fmt.Sprintf("\nError message %s", evalContext.Error.Error())
+ }
+ q := url.Values{}
+ q.Add("user", this.UserKey)
+ q.Add("token", this.ApiToken)
+ q.Add("priority", strconv.Itoa(this.Priority))
+ if this.Priority == 2 {
+ q.Add("retry", strconv.Itoa(this.Retry))
+ q.Add("expire", strconv.Itoa(this.Expire))
+ }
+ if this.Device != "" {
+ q.Add("device", this.Device)
+ }
+ if this.Sound != "default" {
+ q.Add("sound", this.Sound)
+ }
+ q.Add("title", evalContext.GetNotificationTitle())
+ q.Add("url", ruleUrl)
+ q.Add("url_title", "Show dashboard with alert")
+ q.Add("message", message)
+ q.Add("html", "1")
+
+ cmd := &m.SendWebhookSync{
+ Url: PUSHOVER_ENDPOINT,
+ HttpMethod: "POST",
+ HttpHeader: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
+ Body: q.Encode(),
+ }
+
+ if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+ this.log.Error("Failed to send pushover notification", "error", err, "webhook", this.Name)
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go
new file mode 100644
index 00000000000..a3bac73e065
--- /dev/null
+++ b/pkg/services/alerting/notifiers/pushover_test.go
@@ -0,0 +1,58 @@
+package notifiers
+
+import (
+ "testing"
+
+ "github.com/grafana/grafana/pkg/components/simplejson"
+ m "github.com/grafana/grafana/pkg/models"
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func TestPushoverNotifier(t *testing.T) {
+ Convey("Pushover notifier tests", t, func() {
+
+ Convey("Parsing alert notification from settings", func() {
+ Convey("empty settings should return error", func() {
+ json := `{ }`
+
+ settingsJSON, _ := simplejson.NewJson([]byte(json))
+ model := &m.AlertNotification{
+ Name: "Pushover",
+ Type: "pushover",
+ Settings: settingsJSON,
+ }
+
+ _, err := NewPushoverNotifier(model)
+ So(err, ShouldNotBeNil)
+ })
+
+ Convey("from settings", func() {
+ json := `
+ {
+ "apiToken": "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve",
+ "userKey": "tzNZYf36y0ohWwXo4XoUrB61rz1A4o",
+ "priority": "1",
+ "sound": "pushover"
+ }`
+
+ settingsJSON, _ := simplejson.NewJson([]byte(json))
+ model := &m.AlertNotification{
+ Name: "Pushover",
+ Type: "pushover",
+ Settings: settingsJSON,
+ }
+
+ not, err := NewPushoverNotifier(model)
+ pushoverNotifier := not.(*PushoverNotifier)
+
+ So(err, ShouldBeNil)
+ So(pushoverNotifier.Name, ShouldEqual, "Pushover")
+ So(pushoverNotifier.Type, ShouldEqual, "pushover")
+ So(pushoverNotifier.ApiToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve")
+ So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o")
+ So(pushoverNotifier.Priority, ShouldEqual, 1)
+ So(pushoverNotifier.Sound, ShouldEqual, "pushover")
+ })
+ })
+ })
+}
diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts
index 6be36380144..b21e5363ad8 100644
--- a/public/app/features/alerting/alert_tab_ctrl.ts
+++ b/public/app/features/alerting/alert_tab_ctrl.ts
@@ -105,6 +105,7 @@ export class AlertTabCtrl {
case "pagerduty": return "fa fa-bullhorn";
case "opsgenie": return "fa fa-bell";
case "hipchat": return "fa fa-mail-forward";
+ case "pushover": return "fa fa-mobile";
}
}