feat(alerting): more model changes

pull/5622/head
Torkel Ödegaard 10 years ago
parent a362984c57
commit 382f396247
  1. 10
      pkg/services/alerting/commands.go
  2. 46
      pkg/services/alerting/extractor.go
  3. 8
      pkg/services/alerting/extractor_test.go
  4. 19
      pkg/services/sqlstore/alert.go
  5. 4
      pkg/services/sqlstore/alert_rule_changes.go
  6. 8
      pkg/services/sqlstore/migrations/alert_mig.go
  7. 2
      pkg/services/sqlstore/migrator/migrator.go
  8. 72
      public/app/plugins/panel/graph/alert_tab_ctrl.ts
  9. 26
      public/app/plugins/panel/graph/partials/tab_alerting.html

@ -18,20 +18,20 @@ func init() {
} }
func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error { func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error {
saveRulesCmd := m.SaveAlertsCommand{ saveAlerts := m.SaveAlertsCommand{
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
UserId: cmd.UserId, UserId: cmd.UserId,
} }
extractor := NewAlertRuleExtractor(cmd.Dashboard, cmd.OrgId) extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
rules, err := extractor.GetRuleModels() alerts, err := extractor.GetRuleModels()
if err != nil { if err != nil {
return err return err
} }
saveRulesCmd.Alerts = rules saveAlerts.Alerts = alerts
if bus.Dispatch(&saveRulesCmd); err != nil { if bus.Dispatch(&saveAlerts); err != nil {
return err return err
} }

@ -9,21 +9,21 @@ import (
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
type AlertRuleExtractor struct { type DashAlertExtractor struct {
Dash *m.Dashboard Dash *m.Dashboard
OrgId int64 OrgId int64
log log.Logger log log.Logger
} }
func NewAlertRuleExtractor(dash *m.Dashboard, orgId int64) *AlertRuleExtractor { func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor {
return &AlertRuleExtractor{ return &DashAlertExtractor{
Dash: dash, Dash: dash,
OrgId: orgId, OrgId: orgId,
log: log.New("alerting.extractor"), log: log.New("alerting.extractor"),
} }
} }
func (e *AlertRuleExtractor) lookupDatasourceId(dsName string) (int64, error) { func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (int64, error) {
if dsName == "" { if dsName == "" {
query := &m.GetDataSourcesQuery{OrgId: e.OrgId} query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
if err := bus.Dispatch(query); err != nil { if err := bus.Dispatch(query); err != nil {
@ -47,36 +47,36 @@ func (e *AlertRuleExtractor) lookupDatasourceId(dsName string) (int64, error) {
return 0, errors.New("Could not find datasource id for " + dsName) return 0, errors.New("Could not find datasource id for " + dsName)
} }
func (e *AlertRuleExtractor) GetRuleModels() (m.AlertRules, error) { func (e *DashAlertExtractor) GetRuleModels() ([]*m.Alert, error) {
rules := make(m.AlertRules, 0) alerts := make([]*m.Alert, 0)
for _, rowObj := range e.Dash.Data.Get("rows").MustArray() { for _, rowObj := range e.Dash.Data.Get("rows").MustArray() {
row := simplejson.NewFromAny(rowObj) row := simplejson.NewFromAny(rowObj)
for _, panelObj := range row.Get("panels").MustArray() { for _, panelObj := range row.Get("panels").MustArray() {
panel := simplejson.NewFromAny(panelObj) panel := simplejson.NewFromAny(panelObj)
jsonRule := panel.Get("alerting") jsonAlert := panel.Get("alert")
// check if marked for deletion // check if marked for deletion
deleted := jsonRule.Get("deleted").MustBool() deleted := jsonAlert.Get("deleted").MustBool()
if deleted { if deleted {
e.log.Info("Deleted alert rule found") e.log.Info("Deleted alert rule found")
continue continue
} }
ruleModel := &m.Alert{ alert := &m.Alert{
DashboardId: e.Dash.Id, DashboardId: e.Dash.Id,
OrgId: e.OrgId, OrgId: e.OrgId,
PanelId: panel.Get("id").MustInt64(), PanelId: panel.Get("id").MustInt64(),
Id: jsonRule.Get("id").MustInt64(), Id: jsonAlert.Get("id").MustInt64(),
Name: jsonRule.Get("name").MustString(), Name: jsonAlert.Get("name").MustString(),
Scheduler: jsonRule.Get("scheduler").MustInt64(), Scheduler: jsonAlert.Get("scheduler").MustInt64(),
Enabled: jsonRule.Get("enabled").MustBool(), Enabled: jsonAlert.Get("enabled").MustBool(),
Description: jsonRule.Get("description").MustString(), Description: jsonAlert.Get("description").MustString(),
} }
valueQuery := jsonRule.Get("query") valueQuery := jsonAlert.Get("query")
valueQueryRef := valueQuery.Get("refId").MustString() valueQueryRef := valueQuery.Get("refId").MustString()
for _, targetsObj := range panel.Get("targets").MustArray() { for _, targetsObj := range panel.Get("targets").MustArray() {
target := simplejson.NewFromAny(targetsObj) target := simplejson.NewFromAny(targetsObj)
@ -97,24 +97,24 @@ func (e *AlertRuleExtractor) GetRuleModels() (m.AlertRules, error) {
targetQuery := target.Get("target").MustString() targetQuery := target.Get("target").MustString()
if targetQuery != "" { if targetQuery != "" {
jsonRule.SetPath([]string{"query", "query"}, targetQuery) jsonAlert.SetPath([]string{"query", "query"}, targetQuery)
} }
} }
} }
ruleModel.Expression = jsonRule alert.Expression = jsonAlert
// validate // validate
_, err := NewAlertRuleFromDBModel(ruleModel) _, err := NewAlertRuleFromDBModel(alert)
if err == nil && ruleModel.ValidToSave() { if err == nil && alert.ValidToSave() {
rules = append(rules, ruleModel) alerts = append(alerts, alert)
} else { } else {
e.log.Error("Failed to extract alert rules from dashboard", "error", err) e.log.Error("Failed to extract alerts from dashboard", "error", err)
return nil, errors.New("Failed to extract alert rules from dashboard") return nil, errors.New("Failed to extract alerts from dashboard")
} }
} }
} }
return rules, nil return alerts, nil
} }

@ -36,7 +36,7 @@ func TestAlertRuleExtraction(t *testing.T) {
} }
], ],
"datasource": null, "datasource": null,
"alerting": { "alert": {
"name": "name1", "name": "name1",
"description": "desc1", "description": "desc1",
"scheduler": 1, "scheduler": 1,
@ -71,7 +71,7 @@ func TestAlertRuleExtraction(t *testing.T) {
} }
], ],
"datasource": "graphite2", "datasource": "graphite2",
"alerting": { "alert": {
"name": "name2", "name": "name2",
"description": "desc2", "description": "desc2",
"scheduler": 0, "scheduler": 0,
@ -150,7 +150,7 @@ func TestAlertRuleExtraction(t *testing.T) {
"title": "Broken influxdb panel", "title": "Broken influxdb panel",
"transform": "table", "transform": "table",
"type": "table", "type": "table",
"alerting": { "alert": {
"deleted": true "deleted": true
} }
} }
@ -164,7 +164,7 @@ func TestAlertRuleExtraction(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJson) dash := m.NewDashboardFromJson(dashJson)
extractor := NewAlertRuleExtractor(dash, 1) extractor := NewDashAlertExtractor(dash, 1)
// mock data // mock data
defaultDs := &m.DataSource{Id: 12, OrgId: 2, Name: "I am default", IsDefault: true} defaultDs := &m.DataSource{Id: 12, OrgId: 2, Name: "I am default", IsDefault: true}

@ -79,7 +79,7 @@ func GetAlertById(query *m.GetAlertByIdQuery) error {
func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error { func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
var alerts []*m.Alert var alerts []*m.Alert
err := x.Sql("select * from alert_rule").Find(&alerts) err := x.Sql("select * from alert").Find(&alerts)
if err != nil { if err != nil {
return err return err
} }
@ -90,7 +90,7 @@ func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
func DeleteAlertById(cmd *m.DeleteAlertCommand) error { func DeleteAlertById(cmd *m.DeleteAlertCommand) error {
return inTransaction(func(sess *xorm.Session) error { return inTransaction(func(sess *xorm.Session) error {
if _, err := sess.Exec("DELETE FROM alert_rule WHERE id = ?", cmd.AlertId); err != nil { if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", cmd.AlertId); err != nil {
return err return err
} }
@ -103,7 +103,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
params := make([]interface{}, 0) params := make([]interface{}, 0)
sql.WriteString(`SELECT * sql.WriteString(`SELECT *
from alert_rule from alert
`) `)
sql.WriteString(`WHERE org_id = ?`) sql.WriteString(`WHERE org_id = ?`)
@ -141,15 +141,17 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
} }
func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error { func DeleteAlertDefinition(dashboardId int64, sess *xorm.Session) error {
alerts := make(m.Alerts, 0) alerts := make([]*m.Alert, 0)
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts) sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
for _, alert := range alerts { for _, alert := range alerts {
_, err := sess.Exec("DELETE FROM alert_rule WHERE id = ? ", alert.Id) _, err := sess.Exec("DELETE FROM alert WHERE id = ? ", alert.Id)
if err != nil { if err != nil {
return err return err
} }
sqlog.Debug("Alert deleted (due to dashboard deletion)", "name", alert.Name, "id", alert.Id)
if err := SaveAlertChange("DELETED", alert, sess); err != nil { if err := SaveAlertChange("DELETED", alert, sess); err != nil {
return err return err
} }
@ -194,6 +196,7 @@ func upsertAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Session) erro
return err return err
} }
sqlog.Debug("Alert updated", "name", alert.Name, "id", alert.Id)
SaveAlertChange("UPDATED", alert, sess) SaveAlertChange("UPDATED", alert, sess)
} }
@ -205,6 +208,8 @@ func upsertAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Session) erro
if err != nil { if err != nil {
return err return err
} }
sqlog.Debug("Alert inserted", "name", alert.Name, "id", alert.Id)
SaveAlertChange("CREATED", alert, sess) SaveAlertChange("CREATED", alert, sess)
} }
} }
@ -223,11 +228,13 @@ func deleteMissingAlerts(alerts []*m.Alert, posted []*m.Alert, sess *xorm.Sessio
} }
if missing { if missing {
_, err := sess.Exec("DELETE FROM alert_rule WHERE id = ?", missingAlert.Id) _, err := sess.Exec("DELETE FROM alert WHERE id = ?", missingAlert.Id)
if err != nil { if err != nil {
return err return err
} }
sqlog.Debug("Alert deleted", "name", missingAlert.Name, "id", missingAlert.Id)
err = SaveAlertChange("DELETED", missingAlert, sess) err = SaveAlertChange("DELETED", missingAlert, sess)
if err != nil { if err != nil {
return err return err

@ -39,7 +39,7 @@ func GetAlertRuleChanges(query *m.GetAlertChangesQuery) error {
params = append(params, query.Limit) params = append(params, query.Limit)
} }
alertChanges := make([]*m.AlertRuleChange, 0) alertChanges := make([]*m.AlertChange, 0)
if err := x.Sql(sql.String(), params...).Find(&alertChanges); err != nil { if err := x.Sql(sql.String(), params...).Find(&alertChanges); err != nil {
return err return err
} }
@ -49,7 +49,7 @@ func GetAlertRuleChanges(query *m.GetAlertChangesQuery) error {
} }
func SaveAlertChange(change string, alert *m.Alert, sess *xorm.Session) error { func SaveAlertChange(change string, alert *m.Alert, sess *xorm.Session) error {
_, err := sess.Insert(&m.AlertRuleChange{ _, err := sess.Insert(&m.AlertChange{
OrgId: alert.OrgId, OrgId: alert.OrgId,
Type: change, Type: change,
Created: time.Now(), Created: time.Now(),

@ -7,7 +7,7 @@ import (
func addAlertMigrations(mg *Migrator) { func addAlertMigrations(mg *Migrator) {
alertV1 := Table{ alertV1 := Table{
Name: "alert_rule", Name: "alert",
Columns: []*Column{ Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: false}, {Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
@ -26,10 +26,10 @@ func addAlertMigrations(mg *Migrator) {
} }
// create table // create table
mg.AddMigration("create alert_rule table v2", NewAddTableMigration(alertV1)) mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1))
alert_changes := Table{ alert_changes := Table{
Name: "alert_rule_change", Name: "alert_change",
Columns: []*Column{ Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "alert_id", Type: DB_BigInt, Nullable: false}, {Name: "alert_id", Type: DB_BigInt, Nullable: false},
@ -39,7 +39,7 @@ func addAlertMigrations(mg *Migrator) {
}, },
} }
mg.AddMigration("create alert_rules_updates table v1", NewAddTableMigration(alert_changes)) mg.AddMigration("create alert_change table v1", NewAddTableMigration(alert_changes))
alert_state_log := Table{ alert_state_log := Table{
Name: "alert_state", Name: "alert_state",

@ -107,7 +107,7 @@ func (mg *Migrator) Start() error {
} }
func (mg *Migrator) exec(m Migration) error { func (mg *Migrator) exec(m Migration) error {
log.Info("Executing migration", "id", m.Id()) mg.Logger.Info("Executing migration", "id", m.Id())
err := mg.inTransaction(func(sess *xorm.Session) error { err := mg.inTransaction(func(sess *xorm.Session) error {

@ -22,7 +22,6 @@ var alertQueryDef = new QueryPartDef({
export class AlertTabCtrl { export class AlertTabCtrl {
panel: any; panel: any;
panelCtrl: any; panelCtrl: any;
alerting: any;
metricTargets = [{ refId: '- select query -' } ]; metricTargets = [{ refId: '- select query -' } ];
schedulers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}]; schedulers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
transforms = [ transforms = [
@ -36,7 +35,7 @@ export class AlertTabCtrl {
}, },
]; ];
aggregators = ['avg', 'sum', 'min', 'max', 'last']; aggregators = ['avg', 'sum', 'min', 'max', 'last'];
rule: any; alert: any;
query: any; query: any;
queryParams: any; queryParams: any;
transformDef: any; transformDef: any;
@ -71,33 +70,37 @@ export class AlertTabCtrl {
$scope.ctrl = this; $scope.ctrl = this;
this.metricTargets = this.panel.targets.map(val => val); this.metricTargets = this.panel.targets.map(val => val);
this.rule = this.panel.alerting = this.panel.alerting || {};
this.initAlertModel();
}
initAlertModel() {
this.alert = this.panel.alert = this.panel.alert || {};
// set defaults // set defaults
_.defaults(this.rule, this.defaultValues); _.defaults(this.alert, this.defaultValues);
var defaultName = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert'); var defaultName = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert');
this.rule.name = this.rule.name || defaultName; this.alert.name = this.alert.name || defaultName;
this.rule.description = this.rule.description || defaultName; this.alert.description = this.alert.description || defaultName;
this.rule.queryRef = this.panel.alerting.queryRef || this.metricTargets[0].refId;
// great temp working model // great temp working model
this.queryParams = { this.queryParams = {
params: [ params: [
this.rule.query.refId, this.alert.query.refId,
this.rule.query.from, this.alert.query.from,
this.rule.query.to this.alert.query.to
] ]
}; };
// init the query part components model // init the query part components model
this.query = new QueryPart(this.queryParams, alertQueryDef); this.query = new QueryPart(this.queryParams, alertQueryDef);
this.convertThresholdsToAlertThresholds(); this.convertThresholdsToAlertThresholds();
this.transformDef = _.findWhere(this.transforms, {type: this.rule.transform.type}); this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
} }
queryUpdated() { queryUpdated() {
this.rule.query = { this.alert.query = {
refId: this.query.params[0], refId: this.query.params[0],
from: this.query.params[1], from: this.query.params[1],
to: this.query.params[2], to: this.query.params[2],
@ -106,16 +109,16 @@ export class AlertTabCtrl {
transformChanged() { transformChanged() {
// clear model // clear model
this.rule.transform = {type: this.rule.transform.type}; this.alert.transform = {type: this.alert.transform.type};
this.transformDef = _.findWhere(this.transforms, {type: this.rule.transform.type}); this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
switch (this.rule.transform.type) { switch (this.alert.transform.type) {
case 'aggregation': { case 'aggregation': {
this.rule.transform.method = 'avg'; this.alert.transform.method = 'avg';
break; break;
} }
case "forecast": { case "forecast": {
this.rule.transform.timespan = '7d'; this.alert.transform.timespan = '7d';
break; break;
} }
} }
@ -124,45 +127,34 @@ export class AlertTabCtrl {
convertThresholdsToAlertThresholds() { convertThresholdsToAlertThresholds() {
if (this.panel.grid if (this.panel.grid
&& this.panel.grid.threshold1 && this.panel.grid.threshold1
&& this.rule.warnLevel === undefined && this.alert.warnLevel === undefined
) { ) {
this.rule.warning.op = '>'; this.alert.warning.op = '>';
this.rule.warning.level = this.panel.grid.threshold1; this.alert.warning.level = this.panel.grid.threshold1;
} }
if (this.panel.grid if (this.panel.grid
&& this.panel.grid.threshold2 && this.panel.grid.threshold2
&& this.rule.critical.level === undefined && this.alert.critical.level === undefined
) { ) {
this.rule.critical.op = '>'; this.alert.critical.op = '>';
this.rule.critical.level = this.panel.grid.threshold2; this.alert.critical.level = this.panel.grid.threshold2;
} }
} }
delete() { delete() {
this.rule = this.panel.alerting = this.defaultValues; this.alert = this.panel.alert = {};
this.rule.deleted = true; this.alert.deleted = true;
this.initAlertModel();
} }
enable() { enable() {
delete this.rule.deleted; delete this.alert.deleted;
this.rule.enabled = true; this.alert.enabled = true;
} }
disable() { disable() {
this.rule.enabled = false; this.alert.enabled = false;
}
thresholdsUpdated() {
if (this.panel.alerting.warnLevel) {
this.panel.grid.threshold1 = parseInt(this.panel.alerting.warnLevel);
}
if (this.panel.alerting.critLevel) {
this.panel.grid.threshold2 = parseInt(this.panel.alerting.critLevel);
}
this.panelCtrl.render();
} }
} }

@ -13,7 +13,7 @@
<span class="gf-form-label">Transform using</span> <span class="gf-form-label">Transform using</span>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input" <select class="gf-form-input"
ng-model="ctrl.rule.transform.type" ng-model="ctrl.alert.transform.type"
ng-options="f.type as f.text for f in ctrl.transforms" ng-options="f.type as f.text for f in ctrl.transforms"
ng-change="ctrl.transformChanged()" ng-change="ctrl.transformChanged()"
> >
@ -24,14 +24,14 @@
<span class="gf-form-label">Method</span> <span class="gf-form-label">Method</span>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input" <select class="gf-form-input"
ng-model="ctrl.rule.transform.method" ng-model="ctrl.alert.transform.method"
ng-options="f for f in ctrl.aggregators"> ng-options="f for f in ctrl.aggregators">
</select> </select>
</div> </div>
</div> </div>
<div class="gf-form" ng-if="ctrl.transformDef.type === 'forecast'"> <div class="gf-form" ng-if="ctrl.transformDef.type === 'forecast'">
<span class="gf-form-label">Timespan</span> <span class="gf-form-label">Timespan</span>
<input class="gf-form-input max-width-5" type="text" ng-model="ctrl.rule.transform.timespan" ng-change="ctrl.ruleUpdated()"></input> <input class="gf-form-input max-width-5" type="text" ng-model="ctrl.alert.transform.timespan" ng-change="ctrl.ruleUpdated()"></input>
</div> </div>
</div> </div>
</div> </div>
@ -44,16 +44,16 @@
<i class="icon-gf icon-gf-warn alert-icon-warn"></i> <i class="icon-gf icon-gf-warn alert-icon-warn"></i>
Warn if Warn if
</span> </span>
<metric-segment-model property="ctrl.rule.warning.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model> <metric-segment-model property="ctrl.alert.warning.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.warnLevel" ng-change="ctrl.thresholdsUpdated()"></input> <input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.warnLevel" ng-change="ctrl.thresholdsUpdated()"></input>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label"> <span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-critical"></i> <i class="icon-gf icon-gf-warn alert-icon-critical"></i>
Critcal if Critcal if
</span> </span>
<metric-segment-model property="ctrl.rule.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model> <metric-segment-model property="ctrl.alert.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.critLevel" ng-change="ctrl.thresholdsUpdated()"></input> <input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.critLevel" ng-change="ctrl.thresholdsUpdated()"></input>
</div> </div>
</div> </div>
</div> </div>
@ -67,14 +67,14 @@
<span class="gf-form-label">Scheduler</span> <span class="gf-form-label">Scheduler</span>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input" <select class="gf-form-input"
ng-model="ctrl.rule.scheduler" ng-model="ctrl.alert.scheduler"
ng-options="f.value as f.text for f in ctrl.schedulers"> ng-options="f.value as f.text for f in ctrl.schedulers">
</select> </select>
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">Evaluate every</span> <span class="gf-form-label">Evaluate every</span>
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.rule.frequency"></input> <input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
</div> </div>
</div> </div>
</div> </div>
@ -83,7 +83,7 @@
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">Groups</span> <span class="gf-form-label">Groups</span>
<bootstrap-tagsinput ng-model="ctrl.rule.notify" tagclass="label label-tag" placeholder="add tags"> <bootstrap-tagsinput ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput> </bootstrap-tagsinput>
</div> </div>
</div> </div>
@ -109,8 +109,8 @@
<div class="editor-row"> <div class="editor-row">
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.rule.enabled">Delete</button> <button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.alert.enabled">Delete</button>
<button class="btn btn-success" ng-click="ctrl.enable()" ng-hide="ctrl.rule.enabled">Enable</button> <button class="btn btn-success" ng-click="ctrl.enable()" ng-hide="ctrl.alert.enabled">Enable</button>
<button class="btn btn-secondary" ng-click="ctrl.disable()" ng-show="ctrl.rule.enabled">Disable</button> <button class="btn btn-secondary" ng-click="ctrl.disable()" ng-show="ctrl.alert.enabled">Disable</button>
</div> </div>
</div> </div>

Loading…
Cancel
Save