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 />
[[.AlertPageUrl]]<br />
[[.DashboardLink]]<br />
[[.Description]]<br />
Alert rule: [[.RuleName]]<br>
Alert state: [[.RuleState]]<br>
[[if eq .State "Ok"]]
Everything is Ok
[[end]]
<a href="[[.RuleLink]]">Link to alert rule</a>
<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.Timing = fmt.Sprintf("%1.3fs", res.GetDurationSeconds())
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
return Json(200, dtoRes)
}
......
......@@ -34,7 +34,7 @@ type AlertTestCommand struct {
type AlertTestResult struct {
Firing bool `json:"firing"`
Timing string `json:"timing"`
TimeMs string `json:"timeMs"`
Error string `json:"error,omitempty"`
Logs []*AlertTestResultLog `json:"logs,omitempty"`
}
......
......@@ -116,7 +116,9 @@ func getFilters(filterStrArray []string) map[string]log15.Lvl {
for _, filterStr := range filterStrArray {
parts := strings.Split(filterStr, ":")
filterMap[parts[0]] = getLogLevelFromString(parts[1])
if len(parts) > 1 {
filterMap[parts[0]] = getLogLevelFromString(parts[1])
}
}
return filterMap
......
......@@ -60,6 +60,8 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
model := &AlertRule{}
model.Id = ruleDef.Id
model.OrgId = ruleDef.OrgId
model.DashboardId = ruleDef.DashboardId
model.PanelId = ruleDef.PanelId
model.Name = ruleDef.Name
model.Description = ruleDef.Description
model.Frequency = ruleDef.Frequency
......@@ -67,8 +69,11 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
model.State = ruleDef.State
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
if id, ok := v.(int64); ok {
model.Notifications = append(model.Notifications, int64(id))
jsonModel := simplejson.NewFromAny(v)
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": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
],
"notifications": [
{"id": 1134},
{"id": 22}
]
}
`
......@@ -91,6 +95,10 @@ func TestAlertRuleModel(t *testing.T) {
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 {
return &HandlerImpl{
log: log.New("alerting.executor"),
log: log.New("alerting.handler"),
alertJobTimeout: time.Second * 5,
}
}
......@@ -33,7 +33,7 @@ func (e *HandlerImpl) Execute(context *AlertResultContext) {
context.EndTime = time.Now()
e.log.Debug("Job Execution timeout", "alertId", context.Rule.Id)
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
}
func (a *AlertResultContext) GetDurationSeconds() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000000)
func (a *AlertResultContext) GetDurationMs() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
}
func NewAlertResultContext(rule *AlertRule) *AlertResultContext {
......
......@@ -23,6 +23,8 @@ func NewRootNotifier() *RootNotifier {
}
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)
if err != nil {
n.log.Error("Failed to read notifications", "error", err)
......@@ -70,20 +72,22 @@ type EmailNotifier struct {
}
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}
if err := bus.Dispatch(slugQuery); err != nil {
this.log.Error("Failed to load dashboard", "error", err)
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{
Data: map[string]interface{}{
"RuleName": context.Rule.Name,
"Severity": context.Rule.Severity,
"RuleLink": setting.ToAbsUrl("dashboard/db/" + dashboardSlug),
"RuleState": context.Rule.State,
"RuleName": context.Rule.Name,
"Severity": context.Rule.Severity,
"RuleLink": ruleLink,
},
To: this.Addresses,
Template: "alert_notification.html",
......@@ -91,7 +95,7 @@ func (this *EmailNotifier) Notify(context *AlertResultContext) {
err := bus.Dispatch(cmd)
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
serverPosition int
clusterSize int
log log.Logger
}
func NewRuleReader() *AlertRuleReader {
ruleReader := &AlertRuleReader{}
ruleReader := &AlertRuleReader{
log: log.New("alerting.ruleReader"),
}
go ruleReader.initReader()
return ruleReader
......@@ -40,17 +43,19 @@ func (arr *AlertRuleReader) initReader() {
func (arr *AlertRuleReader) Fetch() []*AlertRule {
cmd := &m.GetAllAlertsQuery{}
err := bus.Dispatch(cmd)
if err != nil {
log.Error(1, "Alerting: ruleReader.fetch(): Could not load alerts", err)
if err := bus.Dispatch(cmd); err != nil {
arr.log.Error("Could not load alerts", "error", err)
return []*AlertRule{}
}
res := make([]*AlertRule, len(cmd.Result))
for i, ruleDef := range cmd.Result {
model, _ := NewAlertRuleFromDBModel(ruleDef)
res[i] = model
res := make([]*AlertRule, 0)
for _, ruleDef := range cmd.Result {
if model, err := NewAlertRuleFromDBModel(ruleDef); err != nil {
arr.log.Error("Could not build alert model for rule", "ruleId", ruleDef.Id, "error", err)
} else {
res = append(res, model)
}
}
return res
......
......@@ -17,7 +17,8 @@ type ResultHandlerImpl struct {
func NewResultHandler() *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
//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 (
"bytes"
"fmt"
"strings"
"time"
"github.com/go-xorm/xorm"
......@@ -63,8 +64,10 @@ func getAlertNotifications(query *m.GetAlertNotificationsQuery, sess *xorm.Sessi
}
if len(query.Ids) > 0 {
sql.WriteString(` AND alert_notification.id IN (?)`)
params = append(params, query.Ids)
sql.WriteString(` AND alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + ")")
for _, v := range query.Ids {
params = append(params, v)
}
}
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());
this.syncState();
};
......
......@@ -95,10 +95,10 @@ export class PanelCtrl {
this.editModeInitiated = true;
this.events.emit('init-edit-mode', null);
var routeParams = this.$injector.get('$routeParams');
if (routeParams.editorTab) {
var urlTab = (this.$injector.get('$routeParams').tab || '').toLowerCase();
if (urlTab) {
this.editorTabs.forEach((tab, i) => {
if (tab.title === routeParams.editorTab) {
if (tab.title.toLowerCase() === urlTab) {
this.editorTabIndex = i;
}
});
......@@ -109,7 +109,7 @@ export class PanelCtrl {
this.editorTabIndex = newIndex;
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();
}
......
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import $ from 'jquery';
import angular from 'angular';
import {
QueryPartDef,
......@@ -28,7 +26,6 @@ var reducerAvgDef = new QueryPartDef({
export class AlertTabCtrl {
panel: any;
panelCtrl: any;
metricTargets;
testing: boolean;
testResult: any;
......@@ -50,37 +47,57 @@ export class AlertTabCtrl {
{text: 'Warning', value: 'warning'},
];
addNotificationSegment;
notifications;
alertNotifications;
/** @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.panel = this.panelCtrl.panel;
$scope.ctrl = this;
this.$scope.ctrl = this;
}
this.metricTargets = this.panel.targets.map(val => val);
this.addNotificationSegment = uiSegmentSrv.newPlusButton();
$onInit() {
this.addNotificationSegment = this.uiSegmentSrv.newPlusButton();
this.initModel();
// set panel alert edit mode
$scope.$on("$destroy", () => {
this.$scope.$on("$destroy", () => {
this.panelCtrl.editingAlert = false;
this.panelCtrl.render();
});
}
getNotifications() {
// build notification model
this.notifications = [];
this.alertNotifications = [];
return this.backendSrv.get('/api/alert-notifications').then(res => {
return res.map(item => {
return this.uiSegmentSrv.newSegment(item.name);
this.notifications = res;
_.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() {
this.alert.notifications.push({
name: this.addNotificationSegment.value
});
var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});
if (!model) {
return;
}
this.alertNotifications.push({name: model.name});
this.alert.notifications.push({id: model.id});
// reset plus button
this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
......@@ -89,6 +106,7 @@ export class AlertTabCtrl {
removeNotification(index) {
this.alert.notifications.splice(index, 1);
this.alertNotifications.splice(index, 1);
}
initModel() {
......
......@@ -132,7 +132,7 @@ class GraphCtrl extends MetricsPanelCtrl {
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
if (config.alertingEnabled) {
this.addEditorTab('Alerting', graphAlertEditor, 5);
this.addEditorTab('Alert', graphAlertEditor, 5);
}
this.logScales = {
......
......@@ -99,7 +99,7 @@
<h5 class="section-heading">Notifications</h5>
<div class="gf-form-inline">
<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}}
<i class="fa fa-remove pointer" ng-click="ctrl.removeNotification($index)"></i>
</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">
<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">
{{Subject .Subject "Grafana Alert: {{.Severity}} {{.RuleName}}"}}
{{Subject .Subject "Grafana Alert: [ {{.State}} ] {{.Name}}" }}
<br />
<br />
Alertstate: {{.State}}<br />
{{.AlertPageUrl}}<br />
{{.DashboardLink}}<br />
{{.Description}}<br />
Alert rule: {{.RuleName}}<br />
Alert state: {{.RuleState}}<br />
{{if eq .State "Ok"}}
Everything is Ok
{{end}}
<a href="{{.RuleLink}}" style="color: #E67612; text-decoration: none">Link to alert rule</a>
{{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" />
<br />
<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%">
......
......@@ -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">
<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">
<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>
</table>
</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">
<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">
<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>
</table>
</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">
<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">
<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>
</table>
</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