mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 06:22:35 +08:00
Add Pushover alert notifications
Pushover is a service for getting real-time notifications on your mobile devices and desktop computers: https://pushover.net
This commit is contained in:

committed by
Alfred Krohmer

parent
900230890f
commit
f069aae576
@ -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")
|
||||
|
177
pkg/services/alerting/notifiers/pushover.go
Normal file
177
pkg/services/alerting/notifiers/pushover.go
Normal file
@ -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: `
|
||||
<h3 class="page-heading">Pushover settings</h3>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">API Token</span>
|
||||
<input type="text" class="gf-form-input" required placeholder="Application token" ng-model="ctrl.model.settings.apiToken"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">User key(s)</span>
|
||||
<input type="text" class="gf-form-input" required placeholder="comma-separated list" ng-model="ctrl.model.settings.userKey"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Device(s) (optional)</span>
|
||||
<input type="text" class="gf-form-input" placeholder="comma-separated list; leave empty to send to all devices" ng-model="ctrl.model.settings.device"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Priority</span>
|
||||
<select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.priority" ng-options="v as k for (k, v) in {
|
||||
Emergency: '2',
|
||||
High: '1',
|
||||
Normal: '0',
|
||||
Low: '-1',
|
||||
Lowest: '-2'
|
||||
}" ng-init="ctrl.model.settings.priority=ctrl.model.settings.priority||'0'"></select>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.model.settings.priority == '2'">
|
||||
<span class="gf-form-label width-10">Retry</span>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-required="ctrl.model.settings.priority == '2'" placeholder="minimum 30 seconds" ng-model="ctrl.model.settings.retry" ng-init="ctrl.model.settings.retry=ctrl.model.settings.retry||'60'></input>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.model.settings.priority == '2'">
|
||||
<span class="gf-form-label width-10">Expire</span>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-required="ctrl.model.settings.priority == '2'" placeholder="maximum 86400 seconds" ng-model="ctrl.model.settings.expire" ng-init="ctrl.model.settings.expire=ctrl.model.settings.expire||'3600'"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Sound</span>
|
||||
<select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.sound" ng-options="s for s in [
|
||||
'default',
|
||||
'pushover',
|
||||
'bike',
|
||||
'bugle',
|
||||
'cashregister',
|
||||
'classical',
|
||||
'cosmic',
|
||||
'falling',
|
||||
'gamelan',
|
||||
'incoming',
|
||||
'intermission',
|
||||
'magic',
|
||||
'mechanical',
|
||||
'pianobar',
|
||||
'siren',
|
||||
'spacealarm',
|
||||
'tugboat',
|
||||
'alien',
|
||||
'climb',
|
||||
'persistent',
|
||||
'echo',
|
||||
'updown',
|
||||
'none'
|
||||
]" ng-init="ctrl.model.settings.sound=ctrl.model.settings.sound||'default'"></select>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
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<b>%s</b>: %v", evt.Metric, evt.Value)
|
||||
if idx > 4 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if evalContext.Error != nil {
|
||||
message += fmt.Sprintf("\n<b>Error message</b> %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
|
||||
}
|
58
pkg/services/alerting/notifiers/pushover_test.go
Normal file
58
pkg/services/alerting/notifiers/pushover_test.go
Normal file
@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user