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
e06abb30
Commit
e06abb30
authored
Jun 06, 2019
by
Thibault Chataigner
Committed by
Carl Bergquist
Jun 06, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Alerting: Add tags to alert rules (#10989)
Ref #6552
parent
34f31455
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
201 additions
and
70 deletions
+201
-70
docs/sources/alerting/notifications.md
+27
-19
pkg/models/alert.go
+15
-0
pkg/models/alert_test.go
+23
-0
pkg/services/alerting/notifiers/alertmanager.go
+4
-1
pkg/services/alerting/rule.go
+2
-0
pkg/services/sqlstore/alert.go
+19
-0
pkg/services/sqlstore/annotation.go
+2
-22
pkg/services/sqlstore/annotation_test.go
+0
-28
pkg/services/sqlstore/migrations/alert_mig.go
+14
-0
pkg/services/sqlstore/tags.go
+23
-0
pkg/services/sqlstore/tags_test.go
+26
-0
public/app/features/alerting/AlertTabCtrl.ts
+14
-0
public/app/features/alerting/partials/alert_tab.html
+32
-0
No files found.
docs/sources/alerting/notifications.md
View file @
e06abb30
...
@@ -167,26 +167,26 @@ Notifications can be sent by setting up an incoming webhook in Google Hangouts c
...
@@ -167,26 +167,26 @@ Notifications can be sent by setting up an incoming webhook in Google Hangouts c
### All supported notifiers
### All supported notifiers
Name | Type | Supports images
Name | Type | Supports images
|Support alert rule tags
-----|------------ | ------
-----|------------ | ------
DingDing |
`dingding`
| yes, external only
DingDing |
`dingding`
| yes, external only
| no
Discord |
`discord`
| yes
Discord |
`discord`
| yes
| no
Email |
`email`
| yes
Email |
`email`
| yes
| no
Google Hangouts Chat |
`googlechat`
| yes, external only
Google Hangouts Chat |
`googlechat`
| yes, external only
| no
Hipchat |
`hipchat`
| yes, external only
Hipchat |
`hipchat`
| yes, external only
| no
Kafka |
`kafka`
| yes, external only
Kafka |
`kafka`
| yes, external only
| no
Line |
`line`
| yes, external only
Line |
`line`
| yes, external only
| no
Microsoft Teams |
`teams`
| yes, external only
Microsoft Teams |
`teams`
| yes, external only
| no
OpsGenie |
`opsgenie`
| yes, external only
OpsGenie |
`opsgenie`
| yes, external only
| no
Pagerduty |
`pagerduty`
| yes, external only
Pagerduty |
`pagerduty`
| yes, external only
| no
Prometheus Alertmanager |
`prometheus-alertmanager`
| yes, external only
Prometheus Alertmanager |
`prometheus-alertmanager`
| yes, external only
| yes
Pushover |
`pushover`
| yes
Pushover |
`pushover`
| yes
| no
Sensu |
`sensu`
| yes, external only
Sensu |
`sensu`
| yes, external only
| no
Slack |
`slack`
| yes
Slack |
`slack`
| yes
| no
Telegram |
`telegram`
| yes
Telegram |
`telegram`
| yes
| no
Threema |
`threema`
| yes, external only
Threema |
`threema`
| yes, external only
| no
VictorOps |
`victorops`
| yes, external only
VictorOps |
`victorops`
| yes, external only
| no
Webhook |
`webhook`
| yes, external only
Webhook |
`webhook`
| yes, external only
| no
# Enable images in notifications {#external-image-store}
# Enable images in notifications {#external-image-store}
...
@@ -197,6 +197,14 @@ Be aware that some notifiers requires public access to the image to be able to i
...
@@ -197,6 +197,14 @@ Be aware that some notifiers requires public access to the image to be able to i
Notification services which need public image access are marked as 'external only'.
Notification services which need public image access are marked as 'external only'.
# Use alert rule tags in notifications {#alert-rule-tags}
Grafana can include a list of tags (key/value) in the notification.
It's called alert rule tags to contrast with tags parsed from timeseries.
It currently supports only the Prometheus Alertmanager notifier.
This is an optional feature. You can get notifications without using alert rule tags.
# Configure the link back to Grafana from alert notifications
# Configure the link back to Grafana from alert notifications
All alert notifications contain a link back to the triggered alert in the Grafana instance.
All alert notifications contain a link back to the triggered alert in the Grafana instance.
...
...
pkg/models/alert.go
View file @
e06abb30
...
@@ -117,6 +117,21 @@ func (this *Alert) ContainsUpdates(other *Alert) bool {
...
@@ -117,6 +117,21 @@ func (this *Alert) ContainsUpdates(other *Alert) bool {
return
result
return
result
}
}
func
(
alert
*
Alert
)
GetTagsFromSettings
()
[]
*
Tag
{
tags
:=
[]
*
Tag
{}
if
alert
.
Settings
!=
nil
{
if
data
,
ok
:=
alert
.
Settings
.
CheckGet
(
"alertRuleTags"
);
ok
{
for
tagNameString
,
tagValue
:=
range
data
.
MustMap
()
{
// MustMap() already guarantees the return of a `map[string]interface{}`.
// Therefore we only need to verify that tagValue is a String.
tagValueString
:=
simplejson
.
NewFromAny
(
tagValue
)
.
MustString
()
tags
=
append
(
tags
,
&
Tag
{
Key
:
tagNameString
,
Value
:
tagValueString
})
}
}
}
return
tags
}
type
AlertingClusterInfo
struct
{
type
AlertingClusterInfo
struct
{
ServerId
string
ServerId
string
ClusterSize
int
ClusterSize
int
...
...
pkg/models/alert_test.go
View file @
e06abb30
...
@@ -35,5 +35,28 @@ func TestAlertingModelTest(t *testing.T) {
...
@@ -35,5 +35,28 @@ func TestAlertingModelTest(t *testing.T) {
rule1
.
Settings
=
json2
rule1
.
Settings
=
json2
So
(
rule1
.
ContainsUpdates
(
rule2
),
ShouldBeTrue
)
So
(
rule1
.
ContainsUpdates
(
rule2
),
ShouldBeTrue
)
})
})
Convey
(
"Should parse alertRule tags correctly"
,
func
()
{
json2
,
_
:=
simplejson
.
NewJson
([]
byte
(
`{
"field": "value",
"alertRuleTags": {
"foo": "bar",
"waldo": "fred",
"tagMap": { "mapValue": "value" }
}
}`
))
rule1
.
Settings
=
json2
expectedTags
:=
[]
*
Tag
{
{
Id
:
0
,
Key
:
"foo"
,
Value
:
"bar"
},
{
Id
:
0
,
Key
:
"waldo"
,
Value
:
"fred"
},
{
Id
:
0
,
Key
:
"tagMap"
,
Value
:
""
},
}
actualTags
:=
rule1
.
GetTagsFromSettings
()
So
(
len
(
actualTags
),
ShouldEqual
,
len
(
expectedTags
))
for
_
,
tag
:=
range
expectedTags
{
So
(
ContainsTag
(
actualTags
,
tag
),
ShouldBeTrue
)
}
})
})
})
}
}
pkg/services/alerting/notifiers/alertmanager.go
View file @
e06abb30
...
@@ -93,7 +93,7 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
...
@@ -93,7 +93,7 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
alertJSON
.
SetPath
([]
string
{
"annotations"
,
"image"
},
evalContext
.
ImagePublicURL
)
alertJSON
.
SetPath
([]
string
{
"annotations"
,
"image"
},
evalContext
.
ImagePublicURL
)
}
}
// Labels (from metrics tags + mandatory alertname).
// Labels (from metrics tags +
AlertRuleTags +
mandatory alertname).
tags
:=
make
(
map
[
string
]
string
)
tags
:=
make
(
map
[
string
]
string
)
if
match
!=
nil
{
if
match
!=
nil
{
if
len
(
match
.
Tags
)
==
0
{
if
len
(
match
.
Tags
)
==
0
{
...
@@ -104,6 +104,9 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
...
@@ -104,6 +104,9 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
}
}
}
}
}
}
for
_
,
tag
:=
range
evalContext
.
Rule
.
AlertRuleTags
{
tags
[
tag
.
Key
]
=
tag
.
Value
}
tags
[
"alertname"
]
=
evalContext
.
Rule
.
Name
tags
[
"alertname"
]
=
evalContext
.
Rule
.
Name
alertJSON
.
Set
(
"labels"
,
tags
)
alertJSON
.
Set
(
"labels"
,
tags
)
return
alertJSON
return
alertJSON
...
...
pkg/services/alerting/rule.go
View file @
e06abb30
...
@@ -35,6 +35,7 @@ type Rule struct {
...
@@ -35,6 +35,7 @@ type Rule struct {
State
models
.
AlertStateType
State
models
.
AlertStateType
Conditions
[]
Condition
Conditions
[]
Condition
Notifications
[]
string
Notifications
[]
string
AlertRuleTags
[]
*
models
.
Tag
StateChanges
int64
StateChanges
int64
}
}
...
@@ -145,6 +146,7 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
...
@@ -145,6 +146,7 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
model
.
Notifications
=
append
(
model
.
Notifications
,
uid
)
model
.
Notifications
=
append
(
model
.
Notifications
,
uid
)
}
}
}
}
model
.
AlertRuleTags
=
ruleDef
.
GetTagsFromSettings
()
for
index
,
condition
:=
range
ruleDef
.
Settings
.
Get
(
"conditions"
)
.
MustArray
()
{
for
index
,
condition
:=
range
ruleDef
.
Settings
.
Get
(
"conditions"
)
.
MustArray
()
{
conditionModel
:=
simplejson
.
NewFromAny
(
condition
)
conditionModel
:=
simplejson
.
NewFromAny
(
condition
)
...
...
pkg/services/sqlstore/alert.go
View file @
e06abb30
...
@@ -64,6 +64,10 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
...
@@ -64,6 +64,10 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
return
err
return
err
}
}
if
_
,
err
:=
sess
.
Exec
(
"DELETE FROM alert_rule_tag WHERE alert_id = ?"
,
alertId
);
err
!=
nil
{
return
err
}
return
nil
return
nil
}
}
...
@@ -215,6 +219,21 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
...
@@ -215,6 +219,21 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
sqlog
.
Debug
(
"Alert inserted"
,
"name"
,
alert
.
Name
,
"id"
,
alert
.
Id
)
sqlog
.
Debug
(
"Alert inserted"
,
"name"
,
alert
.
Name
,
"id"
,
alert
.
Id
)
}
}
tags
:=
alert
.
GetTagsFromSettings
()
if
_
,
err
:=
sess
.
Exec
(
"DELETE FROM alert_rule_tag WHERE alert_id = ?"
,
alert
.
Id
);
err
!=
nil
{
return
err
}
if
tags
!=
nil
{
tags
,
err
:=
EnsureTagsExist
(
sess
,
tags
)
if
err
!=
nil
{
return
err
}
for
_
,
tag
:=
range
tags
{
if
_
,
err
:=
sess
.
Exec
(
"INSERT INTO alert_rule_tag (alert_id, tag_id) VALUES(?,?)"
,
alert
.
Id
,
tag
.
Id
);
err
!=
nil
{
return
err
}
}
}
}
}
return
nil
return
nil
...
...
pkg/services/sqlstore/annotation.go
View file @
e06abb30
...
@@ -29,7 +29,7 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
...
@@ -29,7 +29,7 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
}
}
if
item
.
Tags
!=
nil
{
if
item
.
Tags
!=
nil
{
tags
,
err
:=
r
.
e
nsureTagsExist
(
sess
,
tags
)
tags
,
err
:=
E
nsureTagsExist
(
sess
,
tags
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -44,26 +44,6 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
...
@@ -44,26 +44,6 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
})
})
}
}
// Will insert if needed any new key/value pars and return ids
func
(
r
*
SqlAnnotationRepo
)
ensureTagsExist
(
sess
*
DBSession
,
tags
[]
*
models
.
Tag
)
([]
*
models
.
Tag
,
error
)
{
for
_
,
tag
:=
range
tags
{
var
existingTag
models
.
Tag
// check if it exists
if
exists
,
err
:=
sess
.
Table
(
"tag"
)
.
Where
(
dialect
.
Quote
(
"key"
)
+
"=? AND "
+
dialect
.
Quote
(
"value"
)
+
"=?"
,
tag
.
Key
,
tag
.
Value
)
.
Get
(
&
existingTag
);
err
!=
nil
{
return
nil
,
err
}
else
if
exists
{
tag
.
Id
=
existingTag
.
Id
}
else
{
if
_
,
err
:=
sess
.
Table
(
"tag"
)
.
Insert
(
tag
);
err
!=
nil
{
return
nil
,
err
}
}
}
return
tags
,
nil
}
func
(
r
*
SqlAnnotationRepo
)
Update
(
item
*
annotations
.
Item
)
error
{
func
(
r
*
SqlAnnotationRepo
)
Update
(
item
*
annotations
.
Item
)
error
{
return
inTransaction
(
func
(
sess
*
DBSession
)
error
{
return
inTransaction
(
func
(
sess
*
DBSession
)
error
{
var
(
var
(
...
@@ -94,7 +74,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
...
@@ -94,7 +74,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
}
}
if
item
.
Tags
!=
nil
{
if
item
.
Tags
!=
nil
{
tags
,
err
:=
r
.
e
nsureTagsExist
(
sess
,
models
.
ParseTagPairs
(
item
.
Tags
))
tags
,
err
:=
E
nsureTagsExist
(
sess
,
models
.
ParseTagPairs
(
item
.
Tags
))
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
...
pkg/services/sqlstore/annotation_test.go
View file @
e06abb30
...
@@ -5,37 +5,9 @@ import (
...
@@ -5,37 +5,9 @@ import (
.
"github.com/smartystreets/goconvey/convey"
.
"github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations"
)
)
func
TestSavingTags
(
t
*
testing
.
T
)
{
InitTestDB
(
t
)
Convey
(
"Testing annotation saving/loading"
,
t
,
func
()
{
repo
:=
SqlAnnotationRepo
{}
Convey
(
"Can save tags"
,
func
()
{
Reset
(
func
()
{
_
,
err
:=
x
.
Exec
(
"DELETE FROM annotation_tag WHERE 1=1"
)
So
(
err
,
ShouldBeNil
)
})
tagPairs
:=
[]
*
models
.
Tag
{
{
Key
:
"outage"
},
{
Key
:
"type"
,
Value
:
"outage"
},
{
Key
:
"server"
,
Value
:
"server-1"
},
{
Key
:
"error"
},
}
tags
,
err
:=
repo
.
ensureTagsExist
(
newSession
(),
tagPairs
)
So
(
err
,
ShouldBeNil
)
So
(
len
(
tags
),
ShouldEqual
,
4
)
})
})
}
func
TestAnnotations
(
t
*
testing
.
T
)
{
func
TestAnnotations
(
t
*
testing
.
T
)
{
InitTestDB
(
t
)
InitTestDB
(
t
)
...
...
pkg/services/sqlstore/migrations/alert_mig.go
View file @
e06abb30
...
@@ -45,6 +45,20 @@ func addAlertMigrations(mg *Migrator) {
...
@@ -45,6 +45,20 @@ func addAlertMigrations(mg *Migrator) {
mg
.
AddMigration
(
"add index alert state"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
1
]))
mg
.
AddMigration
(
"add index alert state"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
1
]))
mg
.
AddMigration
(
"add index alert dashboard_id"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
2
]))
mg
.
AddMigration
(
"add index alert dashboard_id"
,
NewAddIndexMigration
(
alertV1
,
alertV1
.
Indices
[
2
]))
alertRuleTagTable
:=
Table
{
Name
:
"alert_rule_tag"
,
Columns
:
[]
*
Column
{
{
Name
:
"alert_id"
,
Type
:
DB_BigInt
,
Nullable
:
false
},
{
Name
:
"tag_id"
,
Type
:
DB_BigInt
,
Nullable
:
false
},
},
Indices
:
[]
*
Index
{
{
Cols
:
[]
string
{
"alert_id"
,
"tag_id"
},
Type
:
UniqueIndex
},
},
}
mg
.
AddMigration
(
"Create alert_rule_tag table v1"
,
NewAddTableMigration
(
alertRuleTagTable
))
mg
.
AddMigration
(
"Add unique index alert_rule_tag.alert_id_tag_id"
,
NewAddIndexMigration
(
alertRuleTagTable
,
alertRuleTagTable
.
Indices
[
0
]))
alert_notification
:=
Table
{
alert_notification
:=
Table
{
Name
:
"alert_notification"
,
Name
:
"alert_notification"
,
Columns
:
[]
*
Column
{
Columns
:
[]
*
Column
{
...
...
pkg/services/sqlstore/tags.go
0 → 100644
View file @
e06abb30
package
sqlstore
import
"github.com/grafana/grafana/pkg/models"
// Will insert if needed any new key/value pars and return ids
func
EnsureTagsExist
(
sess
*
DBSession
,
tags
[]
*
models
.
Tag
)
([]
*
models
.
Tag
,
error
)
{
for
_
,
tag
:=
range
tags
{
var
existingTag
models
.
Tag
// check if it exists
if
exists
,
err
:=
sess
.
Table
(
"tag"
)
.
Where
(
"`key`=? AND `value`=?"
,
tag
.
Key
,
tag
.
Value
)
.
Get
(
&
existingTag
);
err
!=
nil
{
return
nil
,
err
}
else
if
exists
{
tag
.
Id
=
existingTag
.
Id
}
else
{
if
_
,
err
:=
sess
.
Table
(
"tag"
)
.
Insert
(
tag
);
err
!=
nil
{
return
nil
,
err
}
}
}
return
tags
,
nil
}
pkg/services/sqlstore/tags_test.go
0 → 100644
View file @
e06abb30
package
sqlstore
import
(
"testing"
.
"github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
)
func
TestSavingTags
(
t
*
testing
.
T
)
{
Convey
(
"Testing tags saving"
,
t
,
func
()
{
InitTestDB
(
t
)
tagPairs
:=
[]
*
models
.
Tag
{
{
Key
:
"outage"
},
{
Key
:
"type"
,
Value
:
"outage"
},
{
Key
:
"server"
,
Value
:
"server-1"
},
{
Key
:
"error"
},
}
tags
,
err
:=
EnsureTagsExist
(
newSession
(),
tagPairs
)
So
(
err
,
ShouldBeNil
)
So
(
len
(
tags
),
ShouldEqual
,
4
)
})
}
public/app/features/alerting/AlertTabCtrl.ts
View file @
e06abb30
...
@@ -28,6 +28,7 @@ export class AlertTabCtrl {
...
@@ -28,6 +28,7 @@ export class AlertTabCtrl {
error
:
string
;
error
:
string
;
appSubUrl
:
string
;
appSubUrl
:
string
;
alertHistory
:
any
;
alertHistory
:
any
;
newAlertRuleTag
:
any
;
/** @ngInject */
/** @ngInject */
constructor
(
constructor
(
...
@@ -158,6 +159,18 @@ export class AlertTabCtrl {
...
@@ -158,6 +159,18 @@ export class AlertTabCtrl {
_
.
remove
(
this
.
alertNotifications
,
(
n
:
any
)
=>
n
.
uid
===
an
.
uid
||
n
.
id
===
an
.
id
);
_
.
remove
(
this
.
alertNotifications
,
(
n
:
any
)
=>
n
.
uid
===
an
.
uid
||
n
.
id
===
an
.
id
);
}
}
addAlertRuleTag
()
{
if
(
this
.
newAlertRuleTag
.
name
)
{
this
.
alert
.
alertRuleTags
[
this
.
newAlertRuleTag
.
name
]
=
this
.
newAlertRuleTag
.
value
;
}
this
.
newAlertRuleTag
.
name
=
''
;
this
.
newAlertRuleTag
.
value
=
''
;
}
removeAlertRuleTag
(
tagName
)
{
delete
this
.
alert
.
alertRuleTags
[
tagName
];
}
initModel
()
{
initModel
()
{
const
alert
=
(
this
.
alert
=
this
.
panel
.
alert
);
const
alert
=
(
this
.
alert
=
this
.
panel
.
alert
);
if
(
!
alert
)
{
if
(
!
alert
)
{
...
@@ -175,6 +188,7 @@ export class AlertTabCtrl {
...
@@ -175,6 +188,7 @@ export class AlertTabCtrl {
alert
.
handler
=
alert
.
handler
||
1
;
alert
.
handler
=
alert
.
handler
||
1
;
alert
.
notifications
=
alert
.
notifications
||
[];
alert
.
notifications
=
alert
.
notifications
||
[];
alert
.
for
=
alert
.
for
||
'0m'
;
alert
.
for
=
alert
.
for
||
'0m'
;
alert
.
alertRuleTags
=
alert
.
alertRuleTags
||
{};
const
defaultName
=
this
.
panel
.
title
+
' alert'
;
const
defaultName
=
this
.
panel
.
title
+
' alert'
;
alert
.
name
=
alert
.
name
||
defaultName
;
alert
.
name
=
alert
.
name
||
defaultName
;
...
...
public/app/features/alerting/partials/alert_tab.html
View file @
e06abb30
...
@@ -149,6 +149,38 @@
...
@@ -149,6 +149,38 @@
<textarea
class=
"gf-form-input"
rows=
"10"
ng-model=
"ctrl.alert.message"
<textarea
class=
"gf-form-input"
rows=
"10"
ng-model=
"ctrl.alert.message"
placeholder=
"Notification message details..."
></textarea>
placeholder=
"Notification message details..."
></textarea>
</div>
</div>
<div
class=
"gf-form"
>
<span
class=
"gf-form-label width-8"
>
Tags
</span>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
ng-repeat=
"(name, value) in ctrl.alert.alertRuleTags"
>
<label
class=
"gf-form-label width-15"
>
{{ name }}
</label>
<input
class=
"gf-form-input width-15"
placeholder=
"Tag value..."
ng-model=
"ctrl.alert.alertRuleTags[name]"
type=
"text"
/>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.removeAlertRuleTag(name)"
>
<i
class=
"fa fa-trash"
></i>
</a>
</label>
</div>
<div
class=
"gf-form-group"
>
<div
class=
"gf-form-inline"
>
<div
class=
"gf-form"
>
<input
class=
"gf-form-input width-15"
placeholder=
"New tag name..."
ng-model=
"ctrl.newAlertRuleTag.name"
type=
"text"
>
<input
class=
"gf-form-input width-15"
placeholder=
"New tag value..."
ng-model=
"ctrl.newAlertRuleTag.value"
type=
"text"
>
</div>
</div>
<div
class=
"gf-form"
>
<label
class=
"gf-form-label"
>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"ctrl.addAlertRuleTag()"
>
<i
class=
"fa fa-plus"
></i>
Add Tag
</a>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
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