Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
0d9b98da
Commit
0d9b98da
authored
Jul 26, 2016
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(alerting): progress on email notifications
parent
6cb1dafb
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
118 additions
and
106 deletions
+118
-106
emails/templates/alert_notification.html
+7
-26
pkg/api/alerting.go
+1
-1
pkg/api/dtos/alerting.go
+1
-1
pkg/log/log.go
+3
-1
pkg/services/alerting/alert_rule.go
+7
-2
pkg/services/alerting/alert_rule_test.go
+10
-2
pkg/services/alerting/handler.go
+2
-2
pkg/services/alerting/models.go
+2
-2
pkg/services/alerting/notifier.go
+10
-6
pkg/services/alerting/reader.go
+13
-8
pkg/services/alerting/result_handler.go
+3
-3
pkg/services/sqlstore/alert_notification.go
+5
-2
public/app/features/dashboard/viewStateSrv.js
+5
-0
public/app/features/panel/panel_ctrl.ts
+4
-4
public/app/plugins/panel/graph/alert_tab_ctrl.ts
+33
-15
public/app/plugins/panel/graph/module.ts
+1
-1
public/app/plugins/panel/graph/partials/tab_alerting.html
+1
-1
public/emails/alert_notification.html
+7
-26
public/emails/invited_to_org.html
+1
-1
public/emails/new_user_invite.html
+1
-1
public/emails/signup_started.html
+1
-1
No files found.
emails/templates/alert_notification.html
View file @
0d9b98da
<!-- 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]]
pkg/api/alerting.go
View file @
0d9b98da
...
@@ -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
.
Tim
ing
=
fmt
.
Sprintf
(
"%1.3fs"
,
res
.
GetDurationSecond
s
())
dtoRes
.
Tim
eMs
=
fmt
.
Sprintf
(
"%1.3fms"
,
res
.
GetDurationM
s
())
return
Json
(
200
,
dtoRes
)
return
Json
(
200
,
dtoRes
)
}
}
...
...
pkg/api/dtos/alerting.go
View file @
0d9b98da
...
@@ -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"`
Tim
ing
string
`json:"timing
"`
Tim
eMs
string
`json:"timeMs
"`
Error
string
`json:"error,omitempty"`
Error
string
`json:"error,omitempty"`
Logs
[]
*
AlertTestResultLog
`json:"logs,omitempty"`
Logs
[]
*
AlertTestResultLog
`json:"logs,omitempty"`
}
}
...
...
pkg/log/log.go
View file @
0d9b98da
...
@@ -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
...
...
pkg/services/alerting/alert_rule.go
View file @
0d9b98da
...
@@ -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
)
}
}
}
}
...
...
pkg/services/alerting/alert_rule_test.go
View file @
0d9b98da
...
@@ -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
)
})
})
})
})
})
}
}
pkg/services/alerting/handler.go
View file @
0d9b98da
...
@@ -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.
executo
r"
),
log
:
log
.
New
(
"alerting.
handle
r"
),
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"
,
"tim
ing"
,
context
.
GetDurationSecond
s
(),
"alertId"
,
context
.
Rule
.
Id
,
"firing"
,
context
.
Firing
)
e
.
log
.
Debug
(
"Job Execution done"
,
"tim
eMs"
,
context
.
GetDurationM
s
(),
"alertId"
,
context
.
Rule
.
Id
,
"firing"
,
context
.
Firing
)
}
}
}
}
...
...
pkg/services/alerting/models.go
View file @
0d9b98da
...
@@ -42,8 +42,8 @@ type AlertResultContext struct {
...
@@ -42,8 +42,8 @@ type AlertResultContext struct {
log
log
.
Logger
log
log
.
Logger
}
}
func
(
a
*
AlertResultContext
)
GetDuration
Second
s
()
float64
{
func
(
a
*
AlertResultContext
)
GetDuration
M
s
()
float64
{
return
float64
(
a
.
EndTime
.
Nanosecond
()
-
a
.
StartTime
.
Nanosecond
())
/
float64
(
1000000
000
)
return
float64
(
a
.
EndTime
.
Nanosecond
()
-
a
.
StartTime
.
Nanosecond
())
/
float64
(
1000000
)
}
}
func
NewAlertResultContext
(
rule
*
AlertRule
)
*
AlertResultContext
{
func
NewAlertResultContext
(
rule
*
AlertRule
)
*
AlertResultContext
{
...
...
pkg/services/alerting/notifier.go
View file @
0d9b98da
...
@@ -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
)
}
}
}
}
...
...
pkg/services/alerting/reader.go
View file @
0d9b98da
...
@@ -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
...
...
pkg/services/alerting/result_handler.go
View file @
0d9b98da
...
@@ -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)
}
}
}
}
pkg/services/sqlstore/alert_notification.go
View file @
0d9b98da
...
@@ -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
)
...
...
public/app/features/dashboard/viewStateSrv.js
View file @
0d9b98da
...
@@ -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
();
};
};
...
...
public/app/features/panel/panel_ctrl.ts
View file @
0d9b98da
...
@@ -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
.
editor
Tab
)
{
if
(
url
Tab
)
{
this
.
editorTabs
.
forEach
((
tab
,
i
)
=>
{
this
.
editorTabs
.
forEach
((
tab
,
i
)
=>
{
if
(
tab
.
title
===
routeParams
.
editor
Tab
)
{
if
(
tab
.
title
.
toLowerCase
()
===
url
Tab
)
{
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
();
}
}
...
...
public/app/plugins/panel/graph/alert_tab_ctrl.ts
View file @
0d9b98da
///<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
()
{
...
...
public/app/plugins/panel/graph/module.ts
View file @
0d9b98da
...
@@ -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
(
'Alert
ing
'
,
graphAlertEditor
,
5
);
this
.
addEditorTab
(
'Alert'
,
graphAlertEditor
,
5
);
}
}
this
.
logScales
=
{
this
.
logScales
=
{
...
...
public/app/plugins/panel/graph/partials/tab_alerting.html
View file @
0d9b98da
...
@@ -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
.n
otifications"
>
<span
class=
"gf-form-label"
ng-repeat=
"nc in ctrl.alert
N
otifications"
>
{{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>
...
...
public/emails/alert_notification.html
View file @
0d9b98da
...
@@ -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%"
>
...
...
public/emails/invited_to_org.html
View file @
0d9b98da
...
@@ -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>
...
...
public/emails/new_user_invite.html
View file @
0d9b98da
...
@@ -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>
...
...
public/emails/signup_started.html
View file @
0d9b98da
...
@@ -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>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment