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 { ...@@ -43,13 +43,16 @@ func GetAlerts(c *middleware.Context) Response {
for _, alert := range query.Result { for _, alert := range query.Result {
dashboardIds = append(dashboardIds, alert.DashboardId) dashboardIds = append(dashboardIds, alert.DashboardId)
alertDTOs = append(alertDTOs, &dtos.AlertRule{ alertDTOs = append(alertDTOs, &dtos.AlertRule{
Id: alert.Id, Id: alert.Id,
DashboardId: alert.DashboardId, DashboardId: alert.DashboardId,
PanelId: alert.PanelId, PanelId: alert.PanelId,
Name: alert.Name, Name: alert.Name,
Message: alert.Message, Message: alert.Message,
State: alert.State, State: alert.State,
Severity: alert.Severity, Severity: alert.Severity,
EvalDate: alert.EvalDate,
NewStateDate: alert.NewStateDate,
ExecutionError: alert.ExecutionError,
}) })
} }
......
...@@ -8,15 +8,17 @@ import ( ...@@ -8,15 +8,17 @@ import (
) )
type AlertRule struct { type AlertRule struct {
Id int64 `json:"id"` Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"` DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"` PanelId int64 `json:"panelId"`
Name string `json:"name"` Name string `json:"name"`
Message string `json:"message"` Message string `json:"message"`
State m.AlertStateType `json:"state"` State m.AlertStateType `json:"state"`
Severity m.AlertSeverityType `json:"severity"` Severity m.AlertSeverityType `json:"severity"`
NewStateDate time.Time `json:"newStateDate"`
DashbboardUri string `json:"dashboardUri"` EvalDate time.Time `json:"evalDate"`
ExecutionError string `json:"executionError"`
DashbboardUri string `json:"dashboardUri"`
} }
type AlertNotification struct { type AlertNotification struct {
......
...@@ -93,14 +93,14 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { ...@@ -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) { if setting.AlertingEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
alertChildNavs := []*dtos.NavLink{ alertChildNavs := []*dtos.NavLink{
{Text: "Home", Url: setting.AppSubUrl + "/alerting"}, {Text: "Alert List", Url: setting.AppSubUrl + "/alerting/list"},
{Text: "Notifications", Url: setting.AppSubUrl + "/alerting/notifications"}, {Text: "Notifications", Url: setting.AppSubUrl + "/alerting/notifications"},
} }
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{ data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
Text: "Alerting", Text: "Alerting",
Icon: "icon-gf icon-gf-monitoring", Icon: "icon-gf icon-gf-monitoring",
Url: setting.AppSubUrl + "/alerting", Url: setting.AppSubUrl + "/alerting/list",
Children: alertChildNavs, Children: alertChildNavs,
}) })
} }
......
...@@ -32,6 +32,7 @@ func (s AlertSeverityType) IsValid() bool { ...@@ -32,6 +32,7 @@ func (s AlertSeverityType) IsValid() bool {
type Alert struct { type Alert struct {
Id int64 Id int64
Version int64
OrgId int64 OrgId int64
DashboardId int64 DashboardId int64
PanelId int64 PanelId int64
...@@ -45,11 +46,10 @@ type Alert struct { ...@@ -45,11 +46,10 @@ type Alert struct {
ExecutionError string ExecutionError string
Frequency int64 Frequency int64
LastEvalData *simplejson.Json EvalData *simplejson.Json
LastEvalTime time.Time EvalDate time.Time
NewStateDate time.Time
CreatedBy int64 StateChanges int
UpdatedBy int64
Created time.Time Created time.Time
Updated time.Time Updated time.Time
......
...@@ -161,8 +161,6 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xor ...@@ -161,8 +161,6 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xor
alert.Updated = time.Now() alert.Updated = time.Now()
alert.Created = time.Now() alert.Created = time.Now()
alert.State = m.AlertStatePending alert.State = m.AlertStatePending
alert.CreatedBy = cmd.UserId
alert.UpdatedBy = cmd.UserId
_, err := sess.Insert(alert) _, err := sess.Insert(alert)
if err != nil { if err != nil {
...@@ -222,8 +220,10 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error { ...@@ -222,8 +220,10 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
} }
alert.State = cmd.State 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 return nil
}) })
} }
...@@ -10,6 +10,7 @@ func addAlertMigrations(mg *Migrator) { ...@@ -10,6 +10,7 @@ func addAlertMigrations(mg *Migrator) {
Name: "alert", 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: "version", Type: DB_BigInt, Nullable: false},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: false}, {Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
{Name: "panel_id", Type: DB_BigInt, Nullable: false}, {Name: "panel_id", Type: DB_BigInt, Nullable: false},
{Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false},
...@@ -23,12 +24,12 @@ func addAlertMigrations(mg *Migrator) { ...@@ -23,12 +24,12 @@ func addAlertMigrations(mg *Migrator) {
{Name: "paused", Type: DB_Bool, Nullable: false}, {Name: "paused", Type: DB_Bool, Nullable: false},
{Name: "silenced", Type: DB_Bool, Nullable: false}, {Name: "silenced", Type: DB_Bool, Nullable: false},
{Name: "execution_error", Type: DB_Text, Nullable: false}, {Name: "execution_error", Type: DB_Text, Nullable: false},
{Name: "last_eval_data", Type: DB_Text, Nullable: false}, {Name: "eval_data", Type: DB_Text, Nullable: true},
{Name: "last_eval_time", Type: DB_DateTime, Nullable: false}, {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: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", 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{ Indices: []*Index{
{Cols: []string{"org_id", "id"}, Type: IndexType}, {Cols: []string{"org_id", "id"}, Type: IndexType},
......
...@@ -32,6 +32,7 @@ func addAnnotationMig(mg *Migrator) { ...@@ -32,6 +32,7 @@ func addAnnotationMig(mg *Migrator) {
// create indices // create indices
mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0])) 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 org_id & type", NewAddIndexMigration(table, table.Indices[1]))
mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2])) mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[2]))
} }
...@@ -194,7 +194,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) { ...@@ -194,7 +194,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl', controllerAs: 'ctrl',
templateUrl: 'public/app/features/styleguide/styleguide.html', templateUrl: 'public/app/features/styleguide/styleguide.html',
}) })
.when('/alerting', { .when('/alerting/list', {
templateUrl: 'public/app/features/alerting/partials/alert_list.html', templateUrl: 'public/app/features/alerting/partials/alert_list.html',
controller: 'AlertListCtrl', controller: 'AlertListCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
......
///<reference path="../../headers/common.d.ts" /> ///<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 { import {
QueryPartDef, QueryPartDef,
...@@ -50,14 +41,28 @@ function createReducerPart(model) { ...@@ -50,14 +41,28 @@ function createReducerPart(model) {
return new QueryPart(model, def); return new QueryPart(model, def);
} }
var severityLevels = [ var severityLevels = {
{text: 'Critical', value: 'critical'}, 'critical': {text: 'Critical', iconClass: 'icon-gf-critical alert-icon-critical'},
{text: 'Warning', value: 'warning'}, '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 { export default {
alertQueryDef: alertQueryDef, alertQueryDef: alertQueryDef,
getSeverityIconClass: getSeverityIconClass, getStateDisplayModel: getStateDisplayModel,
conditionTypes: conditionTypes, conditionTypes: conditionTypes,
evalFunctions: evalFunctions, evalFunctions: evalFunctions,
severityLevels: severityLevels, severityLevels: severityLevels,
......
...@@ -3,23 +3,20 @@ ...@@ -3,23 +3,20 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../../core/core_module'; import coreModule from '../../core/core_module';
import config from 'app/core/config'; import moment from 'moment';
import alertDef from './alert_def'; import alertDef from './alert_def';
export class AlertListCtrl { export class AlertListCtrl {
alerts: any; alerts: any;
filter = { filters = {
ok: false, state: 'OK'
warn: false,
critical: false,
acknowleged: false
}; };
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, private $route) { constructor(private backendSrv, private $route) {
_.each($route.current.params.state, state => { _.each($route.current.params.state, state => {
this.filter[state.toLowerCase()] = true; this.filters[state.toLowerCase()] = true;
}); });
this.loadAlerts(); this.loadAlerts();
...@@ -27,10 +24,6 @@ export class AlertListCtrl { ...@@ -27,10 +24,6 @@ export class AlertListCtrl {
updateFilter() { updateFilter() {
var stats = []; 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.current.params.state = stats;
this.$route.updateParams(); this.$route.updateParams();
} }
...@@ -38,17 +31,14 @@ export class AlertListCtrl { ...@@ -38,17 +31,14 @@ export class AlertListCtrl {
loadAlerts() { loadAlerts() {
var stats = []; var stats = [];
this.filter.ok && stats.push('OK');
this.filter.warn && stats.push('Warn');
this.filter.critical && stats.push('critical');
var params = { var params = {
state: stats state: stats
}; };
this.backendSrv.get('/api/alerts', params).then(result => { this.backendSrv.get('/api/alerts', params).then(result => {
this.alerts = _.map(result, alert => { 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; return alert;
}); });
}); });
......
import './alerts_ctrl'; import './alert_list_ctrl';
import './alert_log_ctrl'; import './alert_log_ctrl';
import './notifications_list_ctrl'; import './notifications_list_ctrl';
import './notification_edit_ctrl'; import './notification_edit_ctrl';
......
...@@ -6,38 +6,46 @@ ...@@ -6,38 +6,46 @@
<h1>Alerting</h1> <h1>Alerting</h1>
</div> </div>
<div class="gf-form-inline"> <div class="gf-form-group">
<gf-form-switch class="gf-form" label="OK" label-class="width-5" checked="ctrl.filter.ok" on-change="ctrl.updateFilter()"></gf-form-switch> <div class="gf-form-inline">
<gf-form-switch class="gf-form" label="Warn" label-class="width-5" checked="ctrl.filter.warn" on-change="ctrl.updateFilter()"></gf-form-switch> <div class="gf-form">
<gf-form-switch class="gf-form" label="Critical" label-class="width-5" checked="ctrl.filter.critical" on-change="ctrl.updateFilter()"></gf-form-switch> <label class="gf-form-label">Filter by state</label>
</div> <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> </div>
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">Severity</span> <span class="gf-form-label">Severity</span>
<div class="gf-form-select-wrapper width-13"> <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> </select>
</div> </div>
</div> </div>
......
...@@ -5,10 +5,6 @@ ...@@ -5,10 +5,6 @@
<div class="page-header"> <div class="page-header">
<h1>Plugins</h1> <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"> <div class="page-header-tabs">
<ul class="gf-tabs"> <ul class="gf-tabs">
<li class="gf-tabs-item"> <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