Commit f934081b by Torkel Ödegaard

feat(alerting): making progress on alerting list, #5784

parent 9f29c541
......@@ -43,13 +43,16 @@ func GetAlerts(c *middleware.Context) Response {
for _, alert := range query.Result {
dashboardIds = append(dashboardIds, alert.DashboardId)
alertDTOs = append(alertDTOs, &dtos.AlertRule{
Id: alert.Id,
DashboardId: alert.DashboardId,
PanelId: alert.PanelId,
Name: alert.Name,
Message: alert.Message,
State: alert.State,
Severity: alert.Severity,
Id: alert.Id,
DashboardId: alert.DashboardId,
PanelId: alert.PanelId,
Name: alert.Name,
Message: alert.Message,
State: alert.State,
Severity: alert.Severity,
EvalDate: alert.EvalDate,
NewStateDate: alert.NewStateDate,
ExecutionError: alert.ExecutionError,
})
}
......
......@@ -8,15 +8,17 @@ import (
)
type AlertRule struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Name string `json:"name"`
Message string `json:"message"`
State m.AlertStateType `json:"state"`
Severity m.AlertSeverityType `json:"severity"`
DashbboardUri string `json:"dashboardUri"`
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Name string `json:"name"`
Message string `json:"message"`
State m.AlertStateType `json:"state"`
Severity m.AlertSeverityType `json:"severity"`
NewStateDate time.Time `json:"newStateDate"`
EvalDate time.Time `json:"evalDate"`
ExecutionError string `json:"executionError"`
DashbboardUri string `json:"dashboardUri"`
}
type AlertNotification struct {
......
......@@ -93,14 +93,14 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
if setting.AlertingEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
alertChildNavs := []*dtos.NavLink{
{Text: "Home", Url: setting.AppSubUrl + "/alerting"},
{Text: "Alert List", Url: setting.AppSubUrl + "/alerting/list"},
{Text: "Notifications", Url: setting.AppSubUrl + "/alerting/notifications"},
}
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Alerting",
Icon: "icon-gf icon-gf-monitoring",
Url: setting.AppSubUrl + "/alerting",
Url: setting.AppSubUrl + "/alerting/list",
Children: alertChildNavs,
})
}
......
......@@ -32,6 +32,7 @@ func (s AlertSeverityType) IsValid() bool {
type Alert struct {
Id int64
Version int64
OrgId int64
DashboardId int64
PanelId int64
......@@ -45,11 +46,10 @@ type Alert struct {
ExecutionError string
Frequency int64
LastEvalData *simplejson.Json
LastEvalTime time.Time
CreatedBy int64
UpdatedBy int64
EvalData *simplejson.Json
EvalDate time.Time
NewStateDate time.Time
StateChanges int
Created time.Time
Updated time.Time
......
......@@ -161,8 +161,6 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xor
alert.Updated = time.Now()
alert.Created = time.Now()
alert.State = m.AlertStatePending
alert.CreatedBy = cmd.UserId
alert.UpdatedBy = cmd.UserId
_, err := sess.Insert(alert)
if err != nil {
......@@ -222,8 +220,10 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
}
alert.State = cmd.State
sess.Id(alert.Id).Update(&alert)
alert.StateChanges += 1
alert.NewStateDate = time.Now()
sess.Id(alert.Id).Update(&alert)
return nil
})
}
......@@ -10,6 +10,7 @@ func addAlertMigrations(mg *Migrator) {
Name: "alert",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "version", Type: DB_BigInt, Nullable: false},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
{Name: "panel_id", Type: DB_BigInt, Nullable: false},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
......@@ -23,12 +24,12 @@ func addAlertMigrations(mg *Migrator) {
{Name: "paused", Type: DB_Bool, Nullable: false},
{Name: "silenced", Type: DB_Bool, Nullable: false},
{Name: "execution_error", Type: DB_Text, Nullable: false},
{Name: "last_eval_data", Type: DB_Text, Nullable: false},
{Name: "last_eval_time", Type: DB_DateTime, Nullable: false},
{Name: "eval_data", Type: DB_Text, Nullable: true},
{Name: "eval_date", Type: DB_DateTime, Nullable: true},
{Name: "new_state_date", Type: DB_DateTime, Nullable: false},
{Name: "state_changes", Type: DB_Int, Nullable: false},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
{Name: "updated_by", Type: DB_BigInt, Nullable: false},
{Name: "created_by", Type: DB_BigInt, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id", "id"}, Type: IndexType},
......
......@@ -32,6 +32,7 @@ func addAnnotationMig(mg *Migrator) {
// create indices
mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0]))
mg.AddMigration("add index annotation org_id & type", NewAddIndexMigration(table, table.Indices[1]))
mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2]))
}
......@@ -194,7 +194,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl',
templateUrl: 'public/app/features/styleguide/styleguide.html',
})
.when('/alerting', {
.when('/alerting/list', {
templateUrl: 'public/app/features/alerting/partials/alert_list.html',
controller: 'AlertListCtrl',
controllerAs: 'ctrl',
......
///<reference path="../../headers/common.d.ts" />
var alertSeverityIconMap = {
"ok": "icon-gf-online alert-icon-online",
"warning": "icon-gf-warn alert-icon-warn",
"critical": "icon-gf-critical alert-icon-critical",
};
function getSeverityIconClass(alertState) {
return alertSeverityIconMap[alertState];
}
import {
QueryPartDef,
......@@ -50,14 +41,28 @@ function createReducerPart(model) {
return new QueryPart(model, def);
}
var severityLevels = [
{text: 'Critical', value: 'critical'},
{text: 'Warning', value: 'warning'},
];
var severityLevels = {
'critical': {text: 'Critical', iconClass: 'icon-gf-critical alert-icon-critical'},
'warning': {text: 'Warning', iconClass: 'icon-gf-warn alert-icon-warn'},
};
function getStateDisplayModel(state, severity) {
var model = {
text: 'OK',
iconClass: 'icon-gf-online alert-icon-online'
};
if (state === 'firing') {
model.text = severityLevels[severity].text;
model.iconClass = severityLevels[severity].iconClass;
}
return model;
}
export default {
alertQueryDef: alertQueryDef,
getSeverityIconClass: getSeverityIconClass,
getStateDisplayModel: getStateDisplayModel,
conditionTypes: conditionTypes,
evalFunctions: evalFunctions,
severityLevels: severityLevels,
......
......@@ -3,23 +3,20 @@
import angular from 'angular';
import _ from 'lodash';
import coreModule from '../../core/core_module';
import config from 'app/core/config';
import moment from 'moment';
import alertDef from './alert_def';
export class AlertListCtrl {
alerts: any;
filter = {
ok: false,
warn: false,
critical: false,
acknowleged: false
filters = {
state: 'OK'
};
/** @ngInject */
constructor(private backendSrv, private $route) {
_.each($route.current.params.state, state => {
this.filter[state.toLowerCase()] = true;
this.filters[state.toLowerCase()] = true;
});
this.loadAlerts();
......@@ -27,10 +24,6 @@ export class AlertListCtrl {
updateFilter() {
var stats = [];
this.filter.ok && stats.push('OK');
this.filter.warn && stats.push('Warn');
this.filter.critical && stats.push('critical');
this.$route.current.params.state = stats;
this.$route.updateParams();
}
......@@ -38,17 +31,14 @@ export class AlertListCtrl {
loadAlerts() {
var stats = [];
this.filter.ok && stats.push('OK');
this.filter.warn && stats.push('Warn');
this.filter.critical && stats.push('critical');
var params = {
state: stats
};
this.backendSrv.get('/api/alerts', params).then(result => {
this.alerts = _.map(result, alert => {
alert.severityClass = alertDef.getSeverityIconClass(alert.severity);
alert.stateModel = alertDef.getStateDisplayModel(alert.state, alert.severity);
alert.newStateDateAgo = moment(alert.newStateDate).fromNow().replace(" ago", "");
return alert;
});
});
......
import './alerts_ctrl';
import './alert_list_ctrl';
import './alert_log_ctrl';
import './notifications_list_ctrl';
import './notification_edit_ctrl';
......
......@@ -6,38 +6,46 @@
<h1>Alerting</h1>
</div>
<div class="gf-form-inline">
<gf-form-switch class="gf-form" label="OK" label-class="width-5" checked="ctrl.filter.ok" on-change="ctrl.updateFilter()"></gf-form-switch>
<gf-form-switch class="gf-form" label="Warn" label-class="width-5" checked="ctrl.filter.warn" on-change="ctrl.updateFilter()"></gf-form-switch>
<gf-form-switch class="gf-form" label="Critical" label-class="width-5" checked="ctrl.filter.critical" on-change="ctrl.updateFilter()"></gf-form-switch>
</div>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Filter by state</label>
<div class="gf-form-select-wrapper width-13">
<select class="gf-form-input" ng-model="ctrl.filters.state" ng-options="f for f in ['OK', 'Warning', 'Critical']" ng-change="ctrl.severityChanged()">
</select>
</div>
</div>
</div>
</div>
<section class="card-section card-list-layout-list">
<ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="alert in ctrl.alerts">
<a class="card-item" href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
<div class="card-item-header">
<div class="card-item-type">ACTIVE</div>
<div class="card-item-notice" ng-show="alert.executionError">
<span>Execution Error</span>
</div>
</div>
<div class="card-item-body">
<div class="card-item-details">
<div class="card-item-name">{{alert.name}}</div>
<div class="card-item-sub-name">
<div class="alert-list-state-line">
<i class="icon-gf {{alert.stateModel.iconClass}}"></i>
{{alert.stateModel.text}}
for
{{alert.newStateDateAgo}}
</div>
</div>
</div>
</div>
</a>
</li>
</ol>
</section>
<table class="grafana-options-table">
<thead>
<th style="min-width: 200px"><strong>Name</strong></th>
<th style="width: 1%">State</th>
<th style="width: 1%">Severity</th>
<th style="width: 1%"></th>
</thead>
<tr ng-repeat="alert in ctrl.alerts">
<td>
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&editorTab=Alerting">
{{alert.name}}
</a>
</td>
<td class="text-center">
{{alert.state}}
</td>
<td class="text-center">
{{alert.severity}}
</td>
<td class="text-center">
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&editorTab=Alerting" class="btn btn-inverse btn-small">
<i class="fa fa-edit"></i>
edit
</a>
</td>
</tr>
</table>
</div>
......@@ -34,7 +34,7 @@
<div class="gf-form">
<span class="gf-form-label">Severity</span>
<div class="gf-form-select-wrapper width-13">
<select class="gf-form-input" ng-model="ctrl.alert.severity" ng-options="f.value as f.text for f in ctrl.severityLevels" ng-change="ctrl.severityChanged()">
<select class="gf-form-input" ng-model="ctrl.alert.severity" ng-options="key as value.text for (key, value) in ctrl.severityLevels" ng-change="ctrl.severityChanged()">
</select>
</div>
</div>
......
......@@ -5,10 +5,6 @@
<div class="page-header">
<h1>Plugins</h1>
<!-- <a class="btn btn&#45;inverse" href="https://grafana.net/plugins?utm_source=grafana_plugin_list" target="_blank"> -->
<!-- Explore plugins on Grafana.net -->
<!-- </a> -->
<div class="page-header-tabs">
<ul class="gf-tabs">
<li class="gf-tabs-item">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment