mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): progress on email notifications
This commit is contained in:
parent
6cb1dafb1d
commit
0d9b98da6d
@ -1,31 +1,12 @@
|
|||||||
<!-- This email is sent when an existing user is added to an organization -->
|
[[Subject .Subject "Grafana Alert: [[.Severity]] [[.RuleName]]"]]
|
||||||
|
|
||||||
[[Subject .Subject "Grafana Alert: [ [[.State]] ] [[.Name]]" ]]
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
Alertstate: [[.State]]<br />
|
Alert rule: [[.RuleName]]<br>
|
||||||
[[.AlertPageUrl]]<br />
|
Alert state: [[.RuleState]]<br>
|
||||||
[[.DashboardLink]]<br />
|
|
||||||
[[.Description]]<br />
|
|
||||||
|
|
||||||
[[if eq .State "Ok"]]
|
<a href="[[.RuleLink]]">Link to alert rule</a>
|
||||||
Everything is Ok
|
|
||||||
[[end]]
|
|
||||||
|
|
||||||
<img src="[[.DashboardImage]]" />
|
<br>
|
||||||
|
|
||||||
[[if ne .State "Ok" ]]
|
|
||||||
<table class="row">
|
|
||||||
<tr>
|
|
||||||
<td class="expander">Serie</td>
|
|
||||||
<td class="expander">State</td>
|
|
||||||
<td class="expander">Actual value</td>
|
|
||||||
</tr>
|
|
||||||
[[ range $ta := .TriggeredAlerts]]
|
|
||||||
<tr>
|
|
||||||
<td class="expander">[[$ta.Name]]</td>
|
|
||||||
<td class="expander">[[$ta.State]]</td>
|
|
||||||
<td class="expander">[[$ta.ActualValue]]</td>
|
|
||||||
</tr>
|
|
||||||
[[end]]
|
|
||||||
</table>
|
|
||||||
[[end]]
|
|
||||||
|
@ -104,7 +104,7 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response {
|
|||||||
dtoRes.Logs = append(dtoRes.Logs, &dtos.AlertTestResultLog{Message: log.Message, Data: log.Data})
|
dtoRes.Logs = append(dtoRes.Logs, &dtos.AlertTestResultLog{Message: log.Message, Data: log.Data})
|
||||||
}
|
}
|
||||||
|
|
||||||
dtoRes.Timing = fmt.Sprintf("%1.3fs", res.GetDurationSeconds())
|
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
|
||||||
|
|
||||||
return Json(200, dtoRes)
|
return Json(200, dtoRes)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ type AlertTestCommand struct {
|
|||||||
|
|
||||||
type AlertTestResult struct {
|
type AlertTestResult struct {
|
||||||
Firing bool `json:"firing"`
|
Firing bool `json:"firing"`
|
||||||
Timing string `json:"timing"`
|
TimeMs string `json:"timeMs"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Logs []*AlertTestResultLog `json:"logs,omitempty"`
|
Logs []*AlertTestResultLog `json:"logs,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,9 @@ func getFilters(filterStrArray []string) map[string]log15.Lvl {
|
|||||||
|
|
||||||
for _, filterStr := range filterStrArray {
|
for _, filterStr := range filterStrArray {
|
||||||
parts := strings.Split(filterStr, ":")
|
parts := strings.Split(filterStr, ":")
|
||||||
filterMap[parts[0]] = getLogLevelFromString(parts[1])
|
if len(parts) > 1 {
|
||||||
|
filterMap[parts[0]] = getLogLevelFromString(parts[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterMap
|
return filterMap
|
||||||
|
@ -60,6 +60,8 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
|
|||||||
model := &AlertRule{}
|
model := &AlertRule{}
|
||||||
model.Id = ruleDef.Id
|
model.Id = ruleDef.Id
|
||||||
model.OrgId = ruleDef.OrgId
|
model.OrgId = ruleDef.OrgId
|
||||||
|
model.DashboardId = ruleDef.DashboardId
|
||||||
|
model.PanelId = ruleDef.PanelId
|
||||||
model.Name = ruleDef.Name
|
model.Name = ruleDef.Name
|
||||||
model.Description = ruleDef.Description
|
model.Description = ruleDef.Description
|
||||||
model.Frequency = ruleDef.Frequency
|
model.Frequency = ruleDef.Frequency
|
||||||
@ -67,8 +69,11 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
|
|||||||
model.State = ruleDef.State
|
model.State = ruleDef.State
|
||||||
|
|
||||||
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||||
if id, ok := v.(int64); ok {
|
jsonModel := simplejson.NewFromAny(v)
|
||||||
model.Notifications = append(model.Notifications, int64(id))
|
if id, err := jsonModel.Get("id").Int64(); err != nil {
|
||||||
|
return nil, AlertValidationError{Reason: "Invalid notification schema"}
|
||||||
|
} else {
|
||||||
|
model.Notifications = append(model.Notifications, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +49,12 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"reducer": {"type": "avg", "params": []},
|
"reducer": {"type": "avg", "params": []},
|
||||||
"evaluator": {"type": ">", "params": [100]}
|
"evaluator": {"type": ">", "params": [100]}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"notifications": [
|
||||||
|
{"id": 1134},
|
||||||
|
{"id": 22}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -91,6 +95,10 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
So(evaluator.Type, ShouldEqual, ">")
|
So(evaluator.Type, ShouldEqual, ">")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Can read notifications", func() {
|
||||||
|
So(len(alertRule.Notifications), ShouldEqual, 2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ type HandlerImpl struct {
|
|||||||
|
|
||||||
func NewHandler() *HandlerImpl {
|
func NewHandler() *HandlerImpl {
|
||||||
return &HandlerImpl{
|
return &HandlerImpl{
|
||||||
log: log.New("alerting.executor"),
|
log: log.New("alerting.handler"),
|
||||||
alertJobTimeout: time.Second * 5,
|
alertJobTimeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ func (e *HandlerImpl) Execute(context *AlertResultContext) {
|
|||||||
context.EndTime = time.Now()
|
context.EndTime = time.Now()
|
||||||
e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id)
|
e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id)
|
||||||
case <-context.DoneChan:
|
case <-context.DoneChan:
|
||||||
e.log.Debug("Job Execution done", "timing", context.GetDurationSeconds(), "alertId", context.Rule.Id, "firing", context.Firing)
|
e.log.Debug("Job Execution done", "timeMs", context.GetDurationMs(), "alertId", context.Rule.Id, "firing", context.Firing)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ type AlertResultContext struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AlertResultContext) GetDurationSeconds() float64 {
|
func (a *AlertResultContext) GetDurationMs() float64 {
|
||||||
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000000)
|
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlertResultContext(rule *AlertRule) *AlertResultContext {
|
func NewAlertResultContext(rule *AlertRule) *AlertResultContext {
|
||||||
|
@ -23,6 +23,8 @@ func NewRootNotifier() *RootNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *RootNotifier) Notify(context *AlertResultContext) {
|
func (n *RootNotifier) Notify(context *AlertResultContext) {
|
||||||
|
n.log.Info("Sending notifications for", "ruleId", context.Rule.Id)
|
||||||
|
|
||||||
notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications)
|
notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.log.Error("Failed to read notifications", "error", err)
|
n.log.Error("Failed to read notifications", "error", err)
|
||||||
@ -70,20 +72,22 @@ type EmailNotifier struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *EmailNotifier) Notify(context *AlertResultContext) {
|
func (this *EmailNotifier) Notify(context *AlertResultContext) {
|
||||||
this.log.Info("Sending alert notification to %v", this.Addresses)
|
this.log.Info("Sending alert notification to", "addresses", this.Addresses)
|
||||||
|
|
||||||
slugQuery := &m.GetDashboardSlugByIdQuery{Id: context.Rule.DashboardId}
|
slugQuery := &m.GetDashboardSlugByIdQuery{Id: context.Rule.DashboardId}
|
||||||
if err := bus.Dispatch(slugQuery); err != nil {
|
if err := bus.Dispatch(slugQuery); err != nil {
|
||||||
this.log.Error("Failed to load dashboard", "error", err)
|
this.log.Error("Failed to load dashboard", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dashboardSlug := slugQuery.Result
|
|
||||||
|
ruleLink := fmt.Sprintf("%sdashboard/db/%s?fullscreen&edit&tab=alert&panelId=%d", setting.AppUrl, slugQuery.Result, context.Rule.PanelId)
|
||||||
|
|
||||||
cmd := &m.SendEmailCommand{
|
cmd := &m.SendEmailCommand{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"RuleName": context.Rule.Name,
|
"RuleState": context.Rule.State,
|
||||||
"Severity": context.Rule.Severity,
|
"RuleName": context.Rule.Name,
|
||||||
"RuleLink": setting.ToAbsUrl("dashboard/db/" + dashboardSlug),
|
"Severity": context.Rule.Severity,
|
||||||
|
"RuleLink": ruleLink,
|
||||||
},
|
},
|
||||||
To: this.Addresses,
|
To: this.Addresses,
|
||||||
Template: "alert_notification.html",
|
Template: "alert_notification.html",
|
||||||
@ -91,7 +95,7 @@ func (this *EmailNotifier) Notify(context *AlertResultContext) {
|
|||||||
|
|
||||||
err := bus.Dispatch(cmd)
|
err := bus.Dispatch(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.log.Error("Failed tosend alert notification email", "error", err)
|
this.log.Error("Failed to send alert notification email", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,13 @@ type AlertRuleReader struct {
|
|||||||
serverID string
|
serverID string
|
||||||
serverPosition int
|
serverPosition int
|
||||||
clusterSize int
|
clusterSize int
|
||||||
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleReader() *AlertRuleReader {
|
func NewRuleReader() *AlertRuleReader {
|
||||||
ruleReader := &AlertRuleReader{}
|
ruleReader := &AlertRuleReader{
|
||||||
|
log: log.New("alerting.ruleReader"),
|
||||||
|
}
|
||||||
|
|
||||||
go ruleReader.initReader()
|
go ruleReader.initReader()
|
||||||
return ruleReader
|
return ruleReader
|
||||||
@ -40,17 +43,19 @@ func (arr *AlertRuleReader) initReader() {
|
|||||||
|
|
||||||
func (arr *AlertRuleReader) Fetch() []*AlertRule {
|
func (arr *AlertRuleReader) Fetch() []*AlertRule {
|
||||||
cmd := &m.GetAllAlertsQuery{}
|
cmd := &m.GetAllAlertsQuery{}
|
||||||
err := bus.Dispatch(cmd)
|
|
||||||
|
|
||||||
if err != nil {
|
if err := bus.Dispatch(cmd); err != nil {
|
||||||
log.Error(1, "Alerting: ruleReader.fetch(): Could not load alerts", err)
|
arr.log.Error("Could not load alerts", "error", err)
|
||||||
return []*AlertRule{}
|
return []*AlertRule{}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]*AlertRule, len(cmd.Result))
|
res := make([]*AlertRule, 0)
|
||||||
for i, ruleDef := range cmd.Result {
|
for _, ruleDef := range cmd.Result {
|
||||||
model, _ := NewAlertRuleFromDBModel(ruleDef)
|
if model, err := NewAlertRuleFromDBModel(ruleDef); err != nil {
|
||||||
res[i] = model
|
arr.log.Error("Could not build alert model for rule", "ruleId", ruleDef.Id, "error", err)
|
||||||
|
} else {
|
||||||
|
res = append(res, model)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -17,7 +17,8 @@ type ResultHandlerImpl struct {
|
|||||||
|
|
||||||
func NewResultHandler() *ResultHandlerImpl {
|
func NewResultHandler() *ResultHandlerImpl {
|
||||||
return &ResultHandlerImpl{
|
return &ResultHandlerImpl{
|
||||||
log: log.New("alerting.resultHandler"),
|
log: log.New("alerting.resultHandler"),
|
||||||
|
notifier: NewRootNotifier(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,6 @@ func (handler *ResultHandlerImpl) Handle(result *AlertResultContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Rule.State = newState
|
result.Rule.State = newState
|
||||||
//handler.log.Debug("will notify about new state", "new state", result.State)
|
handler.notifier.Notify(result)
|
||||||
//handler.notifier.Notify(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package sqlstore
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
@ -63,8 +64,10 @@ func getAlertNotifications(query *m.GetAlertNotificationsQuery, sess *xorm.Sessi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(query.Ids) > 0 {
|
if len(query.Ids) > 0 {
|
||||||
sql.WriteString(` AND alert_notification.id IN (?)`)
|
sql.WriteString(` AND alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + ")")
|
||||||
params = append(params, query.Ids)
|
for _, v := range query.Ids {
|
||||||
|
params = append(params, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]*m.AlertNotification, 0)
|
results := make([]*m.AlertNotification, 0)
|
||||||
|
@ -115,6 +115,11 @@ function (angular, _, $) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if no edit state cleanup tab parm
|
||||||
|
if (!this.state.edit) {
|
||||||
|
delete this.state.tab;
|
||||||
|
}
|
||||||
|
|
||||||
$location.search(this.serializeToUrl());
|
$location.search(this.serializeToUrl());
|
||||||
this.syncState();
|
this.syncState();
|
||||||
};
|
};
|
||||||
|
@ -95,10 +95,10 @@ export class PanelCtrl {
|
|||||||
this.editModeInitiated = true;
|
this.editModeInitiated = true;
|
||||||
this.events.emit('init-edit-mode', null);
|
this.events.emit('init-edit-mode', null);
|
||||||
|
|
||||||
var routeParams = this.$injector.get('$routeParams');
|
var urlTab = (this.$injector.get('$routeParams').tab || '').toLowerCase();
|
||||||
if (routeParams.editorTab) {
|
if (urlTab) {
|
||||||
this.editorTabs.forEach((tab, i) => {
|
this.editorTabs.forEach((tab, i) => {
|
||||||
if (tab.title === routeParams.editorTab) {
|
if (tab.title.toLowerCase() === urlTab) {
|
||||||
this.editorTabIndex = i;
|
this.editorTabIndex = i;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -109,7 +109,7 @@ export class PanelCtrl {
|
|||||||
this.editorTabIndex = newIndex;
|
this.editorTabIndex = newIndex;
|
||||||
var route = this.$injector.get('$route');
|
var route = this.$injector.get('$route');
|
||||||
|
|
||||||
route.current.params.editorTab = this.editorTabs[newIndex].title;
|
route.current.params.tab = this.editorTabs[newIndex].title.toLowerCase();
|
||||||
route.updateParams();
|
route.updateParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
///<reference path="../../../headers/common.d.ts" />
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QueryPartDef,
|
QueryPartDef,
|
||||||
@ -28,7 +26,6 @@ var reducerAvgDef = new QueryPartDef({
|
|||||||
export class AlertTabCtrl {
|
export class AlertTabCtrl {
|
||||||
panel: any;
|
panel: any;
|
||||||
panelCtrl: any;
|
panelCtrl: any;
|
||||||
metricTargets;
|
|
||||||
testing: boolean;
|
testing: boolean;
|
||||||
testResult: any;
|
testResult: any;
|
||||||
|
|
||||||
@ -50,37 +47,57 @@ export class AlertTabCtrl {
|
|||||||
{text: 'Warning', value: 'warning'},
|
{text: 'Warning', value: 'warning'},
|
||||||
];
|
];
|
||||||
addNotificationSegment;
|
addNotificationSegment;
|
||||||
|
notifications;
|
||||||
|
alertNotifications;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, private $timeout, private backendSrv, private dashboardSrv, private uiSegmentSrv) {
|
constructor(private $scope, private $timeout, private backendSrv, private dashboardSrv, private uiSegmentSrv) {
|
||||||
this.panelCtrl = $scope.ctrl;
|
this.panelCtrl = $scope.ctrl;
|
||||||
this.panel = this.panelCtrl.panel;
|
this.panel = this.panelCtrl.panel;
|
||||||
$scope.ctrl = this;
|
this.$scope.ctrl = this;
|
||||||
|
}
|
||||||
|
|
||||||
this.metricTargets = this.panel.targets.map(val => val);
|
$onInit() {
|
||||||
this.addNotificationSegment = uiSegmentSrv.newPlusButton();
|
this.addNotificationSegment = this.uiSegmentSrv.newPlusButton();
|
||||||
|
|
||||||
this.initModel();
|
this.initModel();
|
||||||
|
|
||||||
// set panel alert edit mode
|
// set panel alert edit mode
|
||||||
$scope.$on("$destroy", () => {
|
this.$scope.$on("$destroy", () => {
|
||||||
this.panelCtrl.editingAlert = false;
|
this.panelCtrl.editingAlert = false;
|
||||||
this.panelCtrl.render();
|
this.panelCtrl.render();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
getNotifications() {
|
// build notification model
|
||||||
|
this.notifications = [];
|
||||||
|
this.alertNotifications = [];
|
||||||
|
|
||||||
return this.backendSrv.get('/api/alert-notifications').then(res => {
|
return this.backendSrv.get('/api/alert-notifications').then(res => {
|
||||||
return res.map(item => {
|
this.notifications = res;
|
||||||
return this.uiSegmentSrv.newSegment(item.name);
|
|
||||||
|
_.each(this.alert.notifications, item => {
|
||||||
|
var model = _.findWhere(this.notifications, {id: item.id});
|
||||||
|
if (model) {
|
||||||
|
this.alertNotifications.push(model);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNotifications() {
|
||||||
|
return Promise.resolve(this.notifications.map(item => {
|
||||||
|
return this.uiSegmentSrv.newSegment(item.name);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
notificationAdded() {
|
notificationAdded() {
|
||||||
this.alert.notifications.push({
|
var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});
|
||||||
name: this.addNotificationSegment.value
|
if (!model) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.alertNotifications.push({name: model.name});
|
||||||
|
this.alert.notifications.push({id: model.id});
|
||||||
|
|
||||||
// reset plus button
|
// reset plus button
|
||||||
this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
|
this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
|
||||||
@ -89,6 +106,7 @@ export class AlertTabCtrl {
|
|||||||
|
|
||||||
removeNotification(index) {
|
removeNotification(index) {
|
||||||
this.alert.notifications.splice(index, 1);
|
this.alert.notifications.splice(index, 1);
|
||||||
|
this.alertNotifications.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
initModel() {
|
initModel() {
|
||||||
|
@ -132,7 +132,7 @@ class GraphCtrl extends MetricsPanelCtrl {
|
|||||||
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
|
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
|
||||||
|
|
||||||
if (config.alertingEnabled) {
|
if (config.alertingEnabled) {
|
||||||
this.addEditorTab('Alerting', graphAlertEditor, 5);
|
this.addEditorTab('Alert', graphAlertEditor, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logScales = {
|
this.logScales = {
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
<h5 class="section-heading">Notifications</h5>
|
<h5 class="section-heading">Notifications</h5>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form max-width-30">
|
<div class="gf-form max-width-30">
|
||||||
<span class="gf-form-label" ng-repeat="nc in ctrl.alert.notifications">
|
<span class="gf-form-label" ng-repeat="nc in ctrl.alertNotifications">
|
||||||
{{nc.name}}
|
{{nc.name}}
|
||||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeNotification($index)"></i>
|
<i class="fa fa-remove pointer" ng-click="ctrl.removeNotification($index)"></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -113,37 +113,18 @@ color: #FFFFFF !important;
|
|||||||
<table class="container" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: inherit; vertical-align: top; width: 580px">
|
<table class="container" style="border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: inherit; vertical-align: top; width: 580px">
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
||||||
<td style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; word-break: break-word" align="left" valign="top">
|
<td style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; word-break: break-word" align="left" valign="top">
|
||||||
|
{{Subject .Subject "Grafana Alert: {{.Severity}} {{.RuleName}}"}}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
{{Subject .Subject "Grafana Alert: [ {{.State}} ] {{.Name}}" }}
|
Alert rule: {{.RuleName}}<br />
|
||||||
|
Alert state: {{.RuleState}}<br />
|
||||||
|
|
||||||
Alertstate: {{.State}}<br />
|
<a href="{{.RuleLink}}" style="color: #E67612; text-decoration: none">Link to alert rule</a>
|
||||||
{{.AlertPageUrl}}<br />
|
|
||||||
{{.DashboardLink}}<br />
|
|
||||||
{{.Description}}<br />
|
|
||||||
|
|
||||||
{{if eq .State "Ok"}}
|
<br />
|
||||||
Everything is Ok
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if ne .State "Ok" }}
|
|
||||||
<img src="{{.DashboardImage}}" style="-ms-interpolation-mode: bicubic; clear: both; display: block; float: left; max-width: 100%; outline: none; text-decoration: none; width: auto" align="left" />
|
|
||||||
|
|
||||||
<table class="row" style="border-collapse: collapse; border-spacing: 0; display: block; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%">
|
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">Serie</td>
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">State</td>
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">Actual value</td>
|
|
||||||
</tr>
|
|
||||||
{{ range $ta := .TriggeredAlerts}}
|
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.Name}}</td>
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.State}}</td>
|
|
||||||
<td class="expander" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0; text-align: left; vertical-align: top; visibility: hidden; width: 0px; word-break: break-word" align="left" valign="top">{{$ta.ActualValue}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
<table class="row footer" style="border-collapse: collapse; border-spacing: 0; display: block; margin-top: 20px; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%">
|
<table class="row footer" style="border-collapse: collapse; border-spacing: 0; display: block; margin-top: 20px; padding: 0px; position: relative; text-align: left; vertical-align: top; width: 100%">
|
||||||
|
@ -149,7 +149,7 @@ color: #FFFFFF !important;
|
|||||||
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
||||||
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
||||||
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.AppUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; border: 1px solid #ff8f2b; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Log in now</a></td>
|
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.AppUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border: 1px solid #ff8f2b; border-radius: 2px; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Log in now</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
@ -147,7 +147,7 @@ color: #FFFFFF !important;
|
|||||||
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
||||||
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
||||||
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.LinkUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; border: 1px solid #ff8f2b; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Accept Invitation</a></td>
|
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.LinkUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border: 1px solid #ff8f2b; border-radius: 2px; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Accept Invitation</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
@ -148,7 +148,7 @@ color: #FFFFFF !important;
|
|||||||
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
<td class="center" style="-moz-hyphens: auto; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px 0px 10px; text-align: center; vertical-align: top; word-break: break-word" align="center" valign="top">
|
||||||
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
<table class="better-button" align="center" border="0" cellspacing="0" cellpadding="0" style="border-collapse: collapse; border-spacing: 0; margin-bottom: 20px; margin-top: 10px; padding: 0; text-align: left; vertical-align: top">
|
||||||
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
<tr style="padding: 0; text-align: left; vertical-align: top" align="left">
|
||||||
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.SignUpUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; border: 1px solid #ff8f2b; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Complete Sign Up</a></td>
|
<td align="center" class="better-button" bgcolor="#ff8f2b" style="-moz-border-radius: 2px; -moz-hyphens: auto; -webkit-border-radius: 2px; -webkit-font-smoothing: antialiased; -webkit-hyphens: auto; -webkit-text-size-adjust: none; border-collapse: collapse !important; border-radius: 2px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: normal; hyphens: auto; line-height: 19px; margin: 0; padding: 0px; text-align: left; vertical-align: top; word-break: break-word" valign="top"><a href="{{.SignUpUrl}}" target="_blank" style="-moz-border-radius: 2px; -webkit-border-radius: 2px; border: 1px solid #ff8f2b; border-radius: 2px; color: #FFF; display: inline-block; padding: 12px 25px; text-decoration: none">Complete Sign Up</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user