瀏覽代碼

feat(alerting): progress on email notifications

Torkel Ödegaard 9 年之前
父節點
當前提交
0d9b98da6d

+ 7 - 26
emails/templates/alert_notification.html

@@ -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 />
-[[.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]]

+ 1 - 1
pkg/api/alerting.go

@@ -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)
 }

+ 1 - 1
pkg/api/dtos/alerting.go

@@ -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"`
 }

+ 3 - 1
pkg/log/log.go

@@ -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

+ 7 - 2
pkg/services/alerting/alert_rule.go

@@ -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)
 		}
 	}
 

+ 10 - 2
pkg/services/alerting/alert_rule_test.go

@@ -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)
+			})
 		})
 	})
 }

+ 2 - 2
pkg/services/alerting/handler.go

@@ -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)
 	}
 
 }

+ 2 - 2
pkg/services/alerting/models.go

@@ -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 {

+ 10 - 6
pkg/services/alerting/notifier.go

@@ -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)
 	}
 }
 

+ 13 - 8
pkg/services/alerting/reader.go

@@ -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

+ 3 - 3
pkg/services/alerting/result_handler.go

@@ -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)
 	}
 }

+ 5 - 2
pkg/services/sqlstore/alert_notification.go

@@ -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)

+ 5 - 0
public/app/features/dashboard/viewStateSrv.js

@@ -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();
     };

+ 4 - 4
public/app/features/panel/panel_ctrl.ts

@@ -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();
   }
 

+ 33 - 15
public/app/plugins/panel/graph/alert_tab_ctrl.ts

@@ -1,8 +1,6 @@
  ///<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() {

+ 1 - 1
public/app/plugins/panel/graph/module.ts

@@ -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 = {

+ 1 - 1
public/app/plugins/panel/graph/partials/tab_alerting.html

@@ -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>

+ 7 - 26
public/emails/alert_notification.html

@@ -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%">

+ 1 - 1
public/emails/invited_to_org.html

@@ -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>

+ 1 - 1
public/emails/new_user_invite.html

@@ -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>

+ 1 - 1
public/emails/signup_started.html

@@ -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>