pushover.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package notifiers
  2. import (
  3. "fmt"
  4. "net/url"
  5. "strconv"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/log"
  8. "github.com/grafana/grafana/pkg/metrics"
  9. m "github.com/grafana/grafana/pkg/models"
  10. "github.com/grafana/grafana/pkg/services/alerting"
  11. )
  12. const PUSHOVER_ENDPOINT = "https://api.pushover.net/1/messages.json"
  13. func init() {
  14. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  15. Type: "pushover",
  16. Name: "Pushover",
  17. Description: "Sends HTTP POST request to the Pushover API",
  18. Factory: NewPushoverNotifier,
  19. OptionsTemplate: `
  20. <h3 class="page-heading">Pushover settings</h3>
  21. <div class="gf-form">
  22. <span class="gf-form-label width-10">API Token</span>
  23. <input type="text" class="gf-form-input" required placeholder="Application token" ng-model="ctrl.model.settings.apiToken"></input>
  24. </div>
  25. <div class="gf-form">
  26. <span class="gf-form-label width-10">User key(s)</span>
  27. <input type="text" class="gf-form-input" required placeholder="comma-separated list" ng-model="ctrl.model.settings.userKey"></input>
  28. </div>
  29. <div class="gf-form">
  30. <span class="gf-form-label width-10">Device(s) (optional)</span>
  31. <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>
  32. </div>
  33. <div class="gf-form">
  34. <span class="gf-form-label width-10">Priority</span>
  35. <select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.priority" ng-options="v as k for (k, v) in {
  36. Emergency: '2',
  37. High: '1',
  38. Normal: '0',
  39. Low: '-1',
  40. Lowest: '-2'
  41. }" ng-init="ctrl.model.settings.priority=ctrl.model.settings.priority||'0'"></select>
  42. </div>
  43. <div class="gf-form" ng-show="ctrl.model.settings.priority == '2'">
  44. <span class="gf-form-label width-10">Retry</span>
  45. <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>
  46. </div>
  47. <div class="gf-form" ng-show="ctrl.model.settings.priority == '2'">
  48. <span class="gf-form-label width-10">Expire</span>
  49. <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>
  50. </div>
  51. <div class="gf-form">
  52. <span class="gf-form-label width-10">Sound</span>
  53. <select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.sound" ng-options="s for s in [
  54. 'default',
  55. 'pushover',
  56. 'bike',
  57. 'bugle',
  58. 'cashregister',
  59. 'classical',
  60. 'cosmic',
  61. 'falling',
  62. 'gamelan',
  63. 'incoming',
  64. 'intermission',
  65. 'magic',
  66. 'mechanical',
  67. 'pianobar',
  68. 'siren',
  69. 'spacealarm',
  70. 'tugboat',
  71. 'alien',
  72. 'climb',
  73. 'persistent',
  74. 'echo',
  75. 'updown',
  76. 'none'
  77. ]" ng-init="ctrl.model.settings.sound=ctrl.model.settings.sound||'default'"></select>
  78. </div>
  79. `,
  80. })
  81. }
  82. func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
  83. userKey := model.Settings.Get("userKey").MustString()
  84. apiToken := model.Settings.Get("apiToken").MustString()
  85. device := model.Settings.Get("device").MustString()
  86. priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString())
  87. retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
  88. expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString())
  89. sound := model.Settings.Get("sound").MustString()
  90. if userKey == "" {
  91. return nil, alerting.ValidationError{Reason: "User key not given"}
  92. }
  93. if apiToken == "" {
  94. return nil, alerting.ValidationError{Reason: "API token not given"}
  95. }
  96. return &PushoverNotifier{
  97. NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
  98. UserKey: userKey,
  99. ApiToken: apiToken,
  100. Priority: priority,
  101. Retry: retry,
  102. Expire: expire,
  103. Device: device,
  104. Sound: sound,
  105. log: log.New("alerting.notifier.pushover"),
  106. }, nil
  107. }
  108. type PushoverNotifier struct {
  109. NotifierBase
  110. UserKey string
  111. ApiToken string
  112. Priority int
  113. Retry int
  114. Expire int
  115. Device string
  116. Sound string
  117. log log.Logger
  118. }
  119. func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
  120. metrics.M_Alerting_Notification_Sent_Pushover.Inc(1)
  121. ruleUrl, err := evalContext.GetRuleUrl()
  122. if err != nil {
  123. this.log.Error("Failed get rule link", "error", err)
  124. return err
  125. }
  126. message := evalContext.Rule.Message
  127. for idx, evt := range evalContext.EvalMatches {
  128. message += fmt.Sprintf("\n<b>%s</b>: %v", evt.Metric, evt.Value)
  129. if idx > 4 {
  130. break
  131. }
  132. }
  133. if evalContext.Error != nil {
  134. message += fmt.Sprintf("\n<b>Error message:</b> %s", evalContext.Error.Error())
  135. }
  136. if evalContext.ImagePublicUrl != "" {
  137. message += fmt.Sprintf("\n<a href=\"%s\">Show graph image</a>", evalContext.ImagePublicUrl)
  138. }
  139. q := url.Values{}
  140. q.Add("user", this.UserKey)
  141. q.Add("token", this.ApiToken)
  142. q.Add("priority", strconv.Itoa(this.Priority))
  143. if this.Priority == 2 {
  144. q.Add("retry", strconv.Itoa(this.Retry))
  145. q.Add("expire", strconv.Itoa(this.Expire))
  146. }
  147. if this.Device != "" {
  148. q.Add("device", this.Device)
  149. }
  150. if this.Sound != "default" {
  151. q.Add("sound", this.Sound)
  152. }
  153. q.Add("title", evalContext.GetNotificationTitle())
  154. q.Add("url", ruleUrl)
  155. q.Add("url_title", "Show dashboard with alert")
  156. q.Add("message", message)
  157. q.Add("html", "1")
  158. cmd := &m.SendWebhookSync{
  159. Url: PUSHOVER_ENDPOINT,
  160. HttpMethod: "POST",
  161. HttpHeader: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
  162. Body: q.Encode(),
  163. }
  164. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  165. this.log.Error("Failed to send pushover notification", "error", err, "webhook", this.Name)
  166. return err
  167. }
  168. return nil
  169. }