Browse Source

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

Torkel Ödegaard 9 years ago
parent
commit
f934081bcb

+ 10 - 7
pkg/api/alerting.go

@@ -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,
 		})
 	}
 

+ 11 - 9
pkg/api/dtos/alerting.go

@@ -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 {

+ 2 - 2
pkg/api/index.go

@@ -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,
 		})
 	}

+ 5 - 5
pkg/models/alert.go

@@ -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

+ 3 - 3
pkg/services/sqlstore/alert.go

@@ -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
 	})
 }

+ 5 - 4
pkg/services/sqlstore/migrations/alert_mig.go

@@ -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},

+ 1 - 0
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -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]))
 }

+ 1 - 1
public/app/core/routes/routes.ts

@@ -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',

+ 19 - 14
public/app/features/alerting/alert_def.ts

@@ -1,14 +1,5 @@
 ///<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,

+ 6 - 16
public/app/features/alerting/alerts_ctrl.ts → public/app/features/alerting/alert_list_ctrl.ts

@@ -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;
       });
     });

+ 1 - 1
public/app/features/alerting/all.ts

@@ -1,4 +1,4 @@
-import './alerts_ctrl';
+import './alert_list_ctrl';
 import './alert_log_ctrl';
 import './notifications_list_ctrl';
 import './notification_edit_ctrl';

+ 40 - 32
public/app/features/alerting/partials/alert_list.html

@@ -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>
 

+ 1 - 1
public/app/features/alerting/partials/alert_tab.html

@@ -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>

+ 0 - 4
public/app/features/plugins/partials/plugin_list.html

@@ -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">