Commit 0d9b98da by Torkel Ödegaard

feat(alerting): progress on email notifications

parent 6cb1dafb
<!-- 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 { ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 { ...@@ -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) { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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 { ...@@ -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() { ...@@ -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 { ...@@ -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) { ...@@ -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 ...@@ -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 ...@@ -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, _, $) { ...@@ -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 { ...@@ -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 { ...@@ -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();
} }
......
///<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({ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 @@ ...@@ -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; ...@@ -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}}"}}
{{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}}" style="color: #E67612; text-decoration: none">Link to alert rule</a>
Everything is Ok
{{end}}
{{if ne .State "Ok" }} <br />
<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; ...@@ -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; ...@@ -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; ...@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment