Browse Source

Merge branch 'notifier-refactor'

Torkel Ödegaard 9 years ago
parent
commit
31bd725559

+ 4 - 0
pkg/api/alerting.go

@@ -172,6 +172,10 @@ func DelAlert(c *middleware.Context) Response {
 	return Json(200, resp)
 }
 
+func GetAlertNotifiers(c *middleware.Context) Response {
+	return Json(200, alerting.GetNotifiers())
+}
+
 func GetAlertNotifications(c *middleware.Context) Response {
 	query := &models.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
 

+ 1 - 0
pkg/api/api.go

@@ -262,6 +262,7 @@ func Register(r *macaron.Macaron) {
 		})
 
 		r.Get("/alert-notifications", wrap(GetAlertNotifications))
+		r.Get("/alert-notifiers", wrap(GetAlertNotifiers))
 
 		r.Group("/alert-notifications", func() {
 			r.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))

+ 1 - 1
pkg/api/index.go

@@ -106,7 +106,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 	if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
 		alertChildNavs := []*dtos.NavLink{
 			{Text: "Alert List", Url: setting.AppSubUrl + "/alerting/list"},
-			{Text: "Notifications", Url: setting.AppSubUrl + "/alerting/notifications"},
+			{Text: "Notification channels", Url: setting.AppSubUrl + "/alerting/notifications"},
 		}
 
 		data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{

+ 2 - 0
pkg/metrics/metrics.go

@@ -47,6 +47,7 @@ var (
 	M_Alerting_Notification_Sent_PagerDuty Counter
 	M_Alerting_Notification_Sent_Victorops Counter
 	M_Alerting_Notification_Sent_OpsGenie  Counter
+	M_Alerting_Notification_Sent_Telegram  Counter
 	M_Aws_CloudWatch_GetMetricStatistics   Counter
 	M_Aws_CloudWatch_ListMetrics           Counter
 
@@ -114,6 +115,7 @@ func initMetricVars(settings *MetricSettings) {
 	M_Alerting_Notification_Sent_PagerDuty = RegCounter("alerting.notifications_sent", "type", "pagerduty")
 	M_Alerting_Notification_Sent_Victorops = RegCounter("alerting.notifications_sent", "type", "victorops")
 	M_Alerting_Notification_Sent_OpsGenie = RegCounter("alerting.notifications_sent", "type", "opsgenie")
+	M_Alerting_Notification_Sent_Telegram = RegCounter("alerting.notifications_sent", "type", "telegram")
 
 	M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics")
 	M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics")

+ 23 - 5
pkg/services/alerting/notifier.go

@@ -13,6 +13,14 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 )
 
+type NotifierPlugin struct {
+	Type            string          `json:"type"`
+	Name            string          `json:"name"`
+	Description     string          `json:"description"`
+	OptionsTemplate string          `json:"optionsTemplate"`
+	Factory         NotifierFactory `json:"-"`
+}
+
 type RootNotifier struct {
 	log log.Logger
 }
@@ -130,12 +138,12 @@ func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64, contex
 }
 
 func (n *RootNotifier) createNotifierFor(model *m.AlertNotification) (Notifier, error) {
-	factory, found := notifierFactories[model.Type]
+	notifierPlugin, found := notifierFactories[model.Type]
 	if !found {
 		return nil, errors.New("Unsupported notification type")
 	}
 
-	return factory(model)
+	return notifierPlugin.Factory(model)
 }
 
 func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
@@ -152,8 +160,18 @@ func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
 
 type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)
 
-var notifierFactories map[string]NotifierFactory = make(map[string]NotifierFactory)
+var notifierFactories map[string]*NotifierPlugin = make(map[string]*NotifierPlugin)
+
+func RegisterNotifier(plugin *NotifierPlugin) {
+	notifierFactories[plugin.Type] = plugin
+}
+
+func GetNotifiers() []*NotifierPlugin {
+	list := make([]*NotifierPlugin, 0)
+
+	for _, value := range notifierFactories {
+		list = append(list, value)
+	}
 
-func RegisterNotifier(typeName string, factory NotifierFactory) {
-	notifierFactories[typeName] = factory
+	return list
 }

+ 15 - 1
pkg/services/alerting/notifiers/email.go

@@ -13,7 +13,21 @@ import (
 )
 
 func init() {
-	alerting.RegisterNotifier("email", NewEmailNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "email",
+		Name:        "Email",
+		Description: "Sends notifications using Grafana server configured STMP settings",
+		Factory:     NewEmailNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Email addresses</h3>
+      <div class="gf-form">
+         <textarea rows="7" class="gf-form-input width-25" required ng-model="ctrl.model.settings.addresses"></textarea>
+      </div>
+      <div class="gf-form">
+      <span>You can enter multiple email addresses using a ";" separator</span>
+      </div>
+    `,
+	})
 }
 
 type EmailNotifier struct {

+ 22 - 1
pkg/services/alerting/notifiers/opsgenie.go

@@ -13,7 +13,28 @@ import (
 )
 
 func init() {
-	alerting.RegisterNotifier("opsgenie", NewOpsGenieNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "opsgenie",
+		Name:        "OpsGenie",
+		Description: "Sends notifications to OpsGenie",
+		Factory:     NewOpsGenieNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">OpsGenie settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">API Key</span>
+        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiKey" placeholder="OpsGenie API Key"></input>
+      </div>
+      <div class="gf-form">
+        <gf-form-switch
+           class="gf-form"
+           label="Auto close incidents"
+           label-class="width-14"
+           checked="ctrl.model.settings.autoClose"
+           tooltip="Automatically close alerts in OpseGenie once the alert goes back to ok.">
+        </gf-form-switch>
+      </div>
+    `,
+	})
 }
 
 var (

+ 22 - 1
pkg/services/alerting/notifiers/pagerduty.go

@@ -12,7 +12,28 @@ import (
 )
 
 func init() {
-	alerting.RegisterNotifier("pagerduty", NewPagerdutyNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "pagerduty",
+		Name:        "PagerDuty",
+		Description: "Sends notifications to PagerDuty",
+		Factory:     NewPagerdutyNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">PagerDuty settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-14">Integration Key</span>
+        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.integrationKey" placeholder="Pagerduty integeration Key"></input>
+      </div>
+      <div class="gf-form">
+        <gf-form-switch
+           class="gf-form"
+           label="Auto resolve incidents"
+           label-class="width-14"
+           checked="ctrl.model.settings.autoResolve"
+           tooltip="Resolve incidents in pagerduty once the alert goes back to ok.">
+        </gf-form-switch>
+      </div>
+    `,
+	})
 }
 
 var (

+ 36 - 1
pkg/services/alerting/notifiers/slack.go

@@ -13,7 +13,42 @@ import (
 )
 
 func init() {
-	alerting.RegisterNotifier("slack", NewSlackNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "slack",
+		Name:        "Slack",
+		Description: "Sends notifications using Grafana server configured STMP settings",
+		Factory:     NewSlackNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Slack settings</h3>
+      <div class="gf-form max-width-30">
+        <span class="gf-form-label width-6">Url</span>
+        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Slack incoming webhook url"></input>
+      </div>
+      <div class="gf-form max-width-30">
+        <span class="gf-form-label width-6">Recipient</span>
+        <input type="text"
+          class="gf-form-input max-width-30"
+          ng-model="ctrl.model.settings.recipient"
+          data-placement="right">
+        </input>
+        <info-popover mode="right-absolute">
+          Override default channel or user, use #channel-name or @username
+        </info-popover>
+      </div>
+      <div class="gf-form max-width-30">
+        <span class="gf-form-label width-6">Mention</span>
+        <input type="text"
+          class="gf-form-input max-width-30"
+          ng-model="ctrl.model.settings.mention"
+          data-placement="right">
+        </input>
+        <info-popover mode="right-absolute">
+          Mention a user or a group using @ when notifying in a channel
+        </info-popover>
+      </div>
+    `,
+	})
+
 }
 
 func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) {

+ 113 - 0
pkg/services/alerting/notifiers/telegram.go

@@ -0,0 +1,113 @@
+package notifiers
+
+import (
+	"fmt"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"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"
+)
+
+var (
+	telegeramApiUrl string = "https://api.telegram.org/bot%s/%s"
+)
+
+func init() {
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "telegram",
+		Name:        "Telegram",
+		Description: "Sends notifications to Telegram",
+		Factory:     NewOpsGenieNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Telegram API settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-9">BOT API Token</span>
+        <input type="text" required
+					class="gf-form-input"
+					ng-model="ctrl.model.settings.bottoken"
+					placeholder="Telegram BOT API Token"></input>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-9">Chat ID</span>
+        <input type="text" required
+					class="gf-form-input"
+					ng-model="ctrl.model.settings.chatid"
+					data-placement="right">
+        </input>
+        <info-popover mode="right-absolute">
+					Integer Telegram Chat Identifier
+        </info-popover>
+      </div>
+    `,
+	})
+
+}
+
+type TelegramNotifier struct {
+	NotifierBase
+	BotToken string
+	ChatID   string
+	log      log.Logger
+}
+
+func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
+	if model.Settings == nil {
+		return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
+	}
+
+	botToken := model.Settings.Get("bottoken").MustString()
+	chatId := model.Settings.Get("chatid").MustString()
+
+	if botToken == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"}
+	}
+
+	if chatId == "" {
+		return nil, alerting.ValidationError{Reason: "Could not find Chat Id in settings"}
+	}
+
+	return &TelegramNotifier{
+		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
+		BotToken:     botToken,
+		ChatID:       chatId,
+		log:          log.New("alerting.notifier.telegram"),
+	}, nil
+}
+
+func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
+	this.log.Info("Sending alert notification to", "bot_token", this.BotToken)
+	this.log.Info("Sending alert notification to", "chat_id", this.ChatID)
+	metrics.M_Alerting_Notification_Sent_Telegram.Inc(1)
+
+	bodyJSON := simplejson.New()
+
+	bodyJSON.Set("chat_id", this.ChatID)
+	bodyJSON.Set("parse_mode", "html")
+
+	message := fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
+
+	ruleUrl, err := evalContext.GetRuleUrl()
+	if err == nil {
+		message = message + fmt.Sprintf("URL: %s\n", ruleUrl)
+	}
+	bodyJSON.Set("text", message)
+
+	url := fmt.Sprintf(telegeramApiUrl, this.BotToken, "sendMessage")
+	body, _ := bodyJSON.MarshalJSON()
+
+	cmd := &m.SendWebhookSync{
+		Url:        url,
+		Body:       string(body),
+		HttpMethod: "POST",
+	}
+
+	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
+		this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name)
+		return err
+	}
+
+	return nil
+}

+ 55 - 0
pkg/services/alerting/notifiers/telegram_test.go

@@ -0,0 +1,55 @@
+package notifiers
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	m "github.com/grafana/grafana/pkg/models"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestTelegramNotifier(t *testing.T) {
+	Convey("Telegram 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:     "telegram_testing",
+					Type:     "telegram",
+					Settings: settingsJSON,
+				}
+
+				_, err := NewTelegramNotifier(model)
+				So(err, ShouldNotBeNil)
+			})
+
+			Convey("settings should trigger incident", func() {
+				json := `
+				{
+          "bottoken": "abcdefgh0123456789",
+					"chatid": "-1234567890"
+				}`
+
+				settingsJSON, _ := simplejson.NewJson([]byte(json))
+				model := &m.AlertNotification{
+					Name:     "telegram_testing",
+					Type:     "telegram",
+					Settings: settingsJSON,
+				}
+
+				not, err := NewTelegramNotifier(model)
+				telegramNotifier := not.(*TelegramNotifier)
+
+				So(err, ShouldBeNil)
+				So(telegramNotifier.Name, ShouldEqual, "telegram_testing")
+				So(telegramNotifier.Type, ShouldEqual, "telegram")
+				So(telegramNotifier.BotToken, ShouldEqual, "abcdefgh0123456789")
+				So(telegramNotifier.ChatID, ShouldEqual, "-1234567890")
+			})
+
+		})
+	})
+}

+ 13 - 1
pkg/services/alerting/notifiers/victorops.go

@@ -16,7 +16,19 @@ import (
 const AlertStateCritical = "CRITICAL"
 
 func init() {
-	alerting.RegisterNotifier("victorops", NewVictoropsNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "victorops",
+		Name:        "VictorOps",
+		Description: "Sends notifications to VictorOps",
+		Factory:     NewVictoropsNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">VictorOps settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-6">Url</span>
+        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="VictorOps url"></input>
+      </div>
+    `,
+	})
 }
 
 // NewVictoropsNotifier creates an instance of VictoropsNotifier that

+ 29 - 1
pkg/services/alerting/notifiers/webhook.go

@@ -10,7 +10,35 @@ import (
 )
 
 func init() {
-	alerting.RegisterNotifier("webhook", NewWebHookNotifier)
+	alerting.RegisterNotifier(&alerting.NotifierPlugin{
+		Type:        "webhook",
+		Name:        "webhook",
+		Description: "Sends HTTP POST request to a URL",
+		Factory:     NewWebHookNotifier,
+		OptionsTemplate: `
+      <h3 class="page-heading">Webhook settings</h3>
+      <div class="gf-form">
+        <span class="gf-form-label width-10">Url</span>
+        <input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url"></input>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-10">Http Method</span>
+        <div class="gf-form-select-wrapper width-14">
+          <select class="gf-form-input" ng-model="ctrl.model.settings.httpMethod" ng-options="t for t in ['POST', 'PUT']">
+          </select>
+        </div>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-10">Username</span>
+        <input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.username"></input>
+      </div>
+      <div class="gf-form">
+        <span class="gf-form-label width-10">Password</span>
+        <input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.password"></input>
+      </div>
+    `,
+	})
+
 }
 
 func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) {

+ 41 - 28
public/app/features/alerting/notification_edit_ctrl.ts

@@ -2,38 +2,46 @@
 
 import angular from 'angular';
 import _ from 'lodash';
-import coreModule from '../../core/core_module';
 import config from 'app/core/config';
+import {appEvents, coreModule} from 'app/core/core';
 
 export class AlertNotificationEditCtrl {
-  model: any;
   theForm: any;
   testSeverity: string = "critical";
+  notifiers: any;
+  notifierTemplateId: string;
+
+  model: any;
+  defaults: any = {
+    type: 'email',
+    settings: {
+      httpMethod: 'POST',
+      autoResolve: true,
+    },
+    isDefault: false
+  };
 
   /** @ngInject */
-  constructor(private $routeParams, private backendSrv, private $scope, private $location) {
-    if ($routeParams.id) {
-      this.loadNotification($routeParams.id);
-    } else {
-      this.model = {
-        type: 'email',
-        settings: {
-          httpMethod: 'POST',
-          autoResolve: true,
-        },
-        isDefault: false
-      };
-    }
-  }
+  constructor(private $routeParams, private backendSrv, private $location, private $templateCache) {
+    this.backendSrv.get(`/api/alert-notifiers`).then(notifiers => {
+      this.notifiers = notifiers;
 
-  loadNotification(id) {
-    this.backendSrv.get(`/api/alert-notifications/${id}`).then(result => {
-      this.model = result;
-    });
-  }
+      // add option templates
+      for (let notifier of this.notifiers) {
+        this.$templateCache.put(this.getNotifierTemplateId(notifier.type), notifier.optionsTemplate);
+      }
 
-  isNew() {
-    return this.model.id === undefined;
+      if (!this.$routeParams.id) {
+        return this.model;
+      }
+
+      return this.backendSrv.get(`/api/alert-notifications/${this.$routeParams.id}`).then(result => {
+        return result;
+      });
+    }).then(model => {
+      this.model = model;
+      this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
+    });
   }
 
   save() {
@@ -44,18 +52,23 @@ export class AlertNotificationEditCtrl {
     if (this.model.id) {
       this.backendSrv.put(`/api/alert-notifications/${this.model.id}`, this.model).then(res => {
         this.model = res;
-        this.$scope.appEvent('alert-success', ['Notification updated', '']);
+        appEvents.emit('alert-success', ['Notification updated', '']);
       });
     } else {
       this.backendSrv.post(`/api/alert-notifications`, this.model).then(res => {
-        this.$scope.appEvent('alert-success', ['Notification created', '']);
+        appEvents.emit('alert-success', ['Notification created', '']);
         this.$location.path('alerting/notifications');
       });
     }
   }
 
+  getNotifierTemplateId(type) {
+    return `notifier-options-${type}`;
+  }
+
   typeChanged() {
     this.model.settings = {};
+    this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
   }
 
   testNotification() {
@@ -70,9 +83,9 @@ export class AlertNotificationEditCtrl {
     };
 
     this.backendSrv.post(`/api/alert-notifications/test`, payload)
-      .then(res => {
-        this.$scope.appEvent('alert-succes', ['Test notification sent', '']);
-      });
+    .then(res => {
+      appEvents.emit('alert-succes', ['Test notification sent', '']);
+    });
   }
 }
 

+ 8 - 109
public/app/features/alerting/partials/notification_edit.html

@@ -1,16 +1,17 @@
 <navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
 	<a href="alerting/notifications" class="navbar-page-btn">
-		<i class="fa fa-fw fa-envelope-o"></i>
-		Notifications
+		<i class="fa fa-fw fa-rss"></i>
+		Notification channels
 	</a>
 </navbar>
 
-<div class="page-container" >
+<div class="page-container">
   <div class="page-header">
-    <h1>Alert notification</h1>
+    <h1 ng-show="ctrl.model.id">Edit Channel</h1>
+    <h1 ng-show="!ctrl.model.id">New Channel</h1>
   </div>
 
-  <form name="ctrl.theForm">
+  <form name="ctrl.theForm" ng-if="ctrl.notifiers">
     <div class="gf-form-group">
       <div class="gf-form">
         <span class="gf-form-label width-12">Name</span>
@@ -19,7 +20,7 @@
       <div class="gf-form">
         <span class="gf-form-label width-12">Type</span>
         <div class="gf-form-select-wrapper width-15">
-          <select class="gf-form-input" ng-model="ctrl.model.type" ng-options="t for t in ['webhook', 'email', 'slack', 'pagerduty', 'victorops', 'opsgenie']" ng-change="ctrl.typeChanged(notification, $index)">
+          <select class="gf-form-input" ng-model="ctrl.model.type" ng-options="t.type as t.name for t in ctrl.notifiers" ng-change="ctrl.typeChanged(notification, $index)">
           </select>
         </div>
       </div>
@@ -34,109 +35,7 @@
       </div>
     </div>
 
-    <div class="gf-form-group" ng-if="ctrl.model.type === 'webhook'">
-      <h3 class="page-heading">Webhook settings</h3>
-      <div class="gf-form">
-        <span class="gf-form-label width-10">Url</span>
-        <input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url"></input>
-      </div>
-      <div class="gf-form">
-        <span class="gf-form-label width-10">Http Method</span>
-        <div class="gf-form-select-wrapper width-14">
-          <select class="gf-form-input" ng-model="ctrl.model.settings.httpMethod" ng-options="t for t in ['POST', 'PUT']">
-          </select>
-        </div>
-      </div>
-      <div class="gf-form">
-        <span class="gf-form-label width-10">Username</span>
-        <input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.username"></input>
-      </div>
-      <div class="gf-form">
-        <span class="gf-form-label width-10">Password</span>
-        <input type="text" class="gf-form-input max-width-14" ng-model="ctrl.model.settings.password"></input>
-      </div>
-    </div>
-
-    <div class="gf-form-group" ng-if="ctrl.model.type === 'slack'">
-      <h3 class="page-heading">Slack settings</h3>
-      <div class="gf-form max-width-30">
-        <span class="gf-form-label width-6">Url</span>
-        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Slack incoming webhook url"></input>
-      </div>
-      <div class="gf-form max-width-30">
-        <span class="gf-form-label width-6">Recipient</span>
-        <input type="text"
-          class="gf-form-input max-width-30"
-          ng-model="ctrl.model.settings.recipient"
-          data-placement="right">
-        </input>
-        <info-popover mode="right-absolute">
-          Override default channel or user, use #channel-name or @username
-        </info-popover>
-      </div>
-      <div class="gf-form max-width-30">
-        <span class="gf-form-label width-6">Mention</span>
-        <input type="text"
-          class="gf-form-input max-width-30"
-          ng-model="ctrl.model.settings.mention"
-          data-placement="right">
-        </input>
-        <info-popover mode="right-absolute">
-          Mention a user or a group using @ when notifying in a channel
-        </info-popover>
-      </div>
-    </div>
-
-    <div class="gf-form-group" ng-if="ctrl.model.type === 'victorops'">
-      <h3 class="page-heading">VictorOps settings</h3>
-      <div class="gf-form">
-        <span class="gf-form-label width-6">Url</span>
-        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Victorops url"></input>
-      </div>
-    </div>
-
-    <div class="gf-form-group section" ng-if="ctrl.model.type === 'email'">
-      <h3 class="page-heading">Email addresses</h3>
-      <div class="gf-form">
-        <textarea rows="7" class="gf-form-input width-25" required ng-model="ctrl.model.settings.addresses"></textarea>
-      </div>
-      <div class="gf-form">
-        <span>You can enter multiple email addresses using a ";" separator</span>
-      </div>
-    </div>
-
-    <div class="gf-form-group" ng-if="ctrl.model.type === 'pagerduty'">
-      <h3 class="page-heading">Pagerduty settings</h3>
-      <div class="gf-form">
-        <span class="gf-form-label width-14">Integration Key</span>
-        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.integrationKey" placeholder="Pagerduty integeration Key"></input>
-      </div>
-      <div class="gf-form">
-        <gf-form-switch
-           class="gf-form"
-           label="Auto resolve incidents"
-           label-class="width-14"
-           checked="ctrl.model.settings.autoResolve"
-           tooltip="Resolve incidents in pagerduty once the alert goes back to ok.">
-        </gf-form-switch>
-      </div>
-    </div>
-
-    <div class="gf-form-group" ng-if="ctrl.model.type === 'opsgenie'">
-      <h3 class="page-heading">OpsGenie settings</h3>
-      <div class="gf-form">
-        <span class="gf-form-label width-14">API Key</span>
-        <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiKey" placeholder="OpsGenie API Key"></input>
-      </div>
-      <div class="gf-form">
-        <gf-form-switch
-           class="gf-form"
-           label="Auto close incidents"
-           label-class="width-14"
-           checked="ctrl.model.settings.autoClose"
-           tooltip="Automatically close alerts in OpseGenie once the alert goes back to ok.">
-        </gf-form-switch>
-      </div>
+    <div class="gf-form-group" ng-include src="ctrl.notifierTemplateId">
     </div>
 
     <div class="gf-form-group">

+ 2 - 2
public/app/features/alerting/partials/notifications_list.html

@@ -3,10 +3,10 @@
 
 <div class="page-container" >
 	<div class="page-header">
-		<h1>Alert notifications</h1>
+		<h1>Notification channels</h1>
     <a href="alerting/notification/new" class="btn btn-success pull-right">
       <i class="fa fa-plus"></i>
-      New Notification
+      New Channel
     </a>
   </div>