pushover.go 6.2 KB

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