mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): show alertin state in panel header, closes #6136
This commit is contained in:
@@ -48,19 +48,18 @@ export class AlertTabCtrl {
|
||||
$onInit() {
|
||||
this.addNotificationSegment = this.uiSegmentSrv.newPlusButton();
|
||||
|
||||
this.initModel();
|
||||
this.validateModel();
|
||||
// subscribe to graph threshold handle changes
|
||||
var thresholdChangedEventHandler = this.graphThresholdChanged.bind(this);
|
||||
this.panelCtrl.events.on('threshold-changed', thresholdChangedEventHandler);
|
||||
|
||||
// set panel alert edit mode
|
||||
// set panel alert edit mode
|
||||
this.$scope.$on("$destroy", () => {
|
||||
this.panelCtrl.events.off("threshold-changed", thresholdChangedEventHandler);
|
||||
this.panelCtrl.editingThresholds = false;
|
||||
this.panelCtrl.render();
|
||||
});
|
||||
|
||||
// subscribe to graph threshold handle changes
|
||||
this.panelCtrl.events.on('threshold-changed', this.graphThresholdChanged.bind(this));
|
||||
|
||||
// build notification model
|
||||
// build notification model
|
||||
this.notifications = [];
|
||||
this.alertNotifications = [];
|
||||
this.alertHistory = [];
|
||||
@@ -68,21 +67,8 @@ export class AlertTabCtrl {
|
||||
return this.backendSrv.get('/api/alert-notifications').then(res => {
|
||||
this.notifications = res;
|
||||
|
||||
_.each(this.alert.notifications, item => {
|
||||
var model = _.find(this.notifications, {id: item.id});
|
||||
if (model) {
|
||||
model.iconClass = this.getNotificationIcon(model.type);
|
||||
this.alertNotifications.push(model);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(this.notifications, item => {
|
||||
if (item.isDefault) {
|
||||
item.iconClass = this.getNotificationIcon(item.type);
|
||||
item.bgColor = "#00678b";
|
||||
this.alertNotifications.push(item);
|
||||
}
|
||||
});
|
||||
this.initModel();
|
||||
this.validateModel();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,9 +129,8 @@ export class AlertTabCtrl {
|
||||
}
|
||||
|
||||
initModel() {
|
||||
var alert = this.alert = this.panel.alert = this.panel.alert || {enabled: false};
|
||||
|
||||
if (!this.alert.enabled) {
|
||||
var alert = this.alert = this.panel.alert;
|
||||
if (!alert) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,6 +154,22 @@ export class AlertTabCtrl {
|
||||
|
||||
ThresholdMapper.alertToGraphThresholds(this.panel);
|
||||
|
||||
for (let addedNotification of alert.notifications) {
|
||||
var model = _.find(this.notifications, {id: addedNotification.id});
|
||||
if (model) {
|
||||
model.iconClass = this.getNotificationIcon(model.type);
|
||||
this.alertNotifications.push(model);
|
||||
}
|
||||
}
|
||||
|
||||
for (let notification of this.notifications) {
|
||||
if (notification.isDefault) {
|
||||
notification.iconClass = this.getNotificationIcon(notification.type);
|
||||
notification.bgColor = "#00678b";
|
||||
this.alertNotifications.push(notification);
|
||||
}
|
||||
}
|
||||
|
||||
this.panelCtrl.editingThresholds = true;
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
@@ -193,7 +194,7 @@ export class AlertTabCtrl {
|
||||
}
|
||||
|
||||
validateModel() {
|
||||
if (!this.alert.enabled) {
|
||||
if (!this.alert) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -310,17 +311,17 @@ export class AlertTabCtrl {
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
this.alert = this.panel.alert = {enabled: false};
|
||||
delete this.panel.alert;
|
||||
this.alert = null;
|
||||
this.panel.thresholds = [];
|
||||
this.conditionModels = [];
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.alert.enabled = true;
|
||||
this.panel.alert = {};
|
||||
this.initModel();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="edit-tab-with-sidemenu" ng-if="ctrl.alert.enabled">
|
||||
<div class="edit-tab-with-sidemenu" ng-if="ctrl.alert">
|
||||
<aside class="edit-sidemenu-aside">
|
||||
<ul class="edit-sidemenu">
|
||||
<li ng-class="{active: ctrl.subTabIndex === 0}">
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="!ctrl.alert.enabled">
|
||||
<div class="gf-form-group" ng-if="!ctrl.alert">
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.enable()">
|
||||
<i class="icon-gf icon-gf-alert"></i>
|
||||
|
||||
@@ -9,6 +9,7 @@ import coreModule from 'app/core/core_module';
|
||||
|
||||
export class AnnotationsSrv {
|
||||
globalAnnotationsPromise: any;
|
||||
alertStatesPromise: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope,
|
||||
@@ -22,14 +23,27 @@ export class AnnotationsSrv {
|
||||
|
||||
clearCache() {
|
||||
this.globalAnnotationsPromise = null;
|
||||
this.alertStatesPromise = null;
|
||||
}
|
||||
|
||||
getAnnotations(options) {
|
||||
return this.$q.all([
|
||||
this.getGlobalAnnotations(options),
|
||||
this.getPanelAnnotations(options)
|
||||
]).then(allResults => {
|
||||
return _.flattenDeep(allResults);
|
||||
this.getPanelAnnotations(options),
|
||||
this.getAlertStates(options)
|
||||
]).then(results => {
|
||||
|
||||
// combine the annotations and flatten results
|
||||
var annotations = _.flattenDeep([results[0], results[1]]);
|
||||
|
||||
// look for alert state for this panel
|
||||
var alertState = _.find(results[2], {panelId: options.panel.id});
|
||||
|
||||
return {
|
||||
annotations: annotations,
|
||||
alertState: alertState,
|
||||
};
|
||||
|
||||
}).catch(err => {
|
||||
this.$rootScope.appEvent('alert-error', ['Annotations failed', (err.message || err)]);
|
||||
});
|
||||
@@ -39,7 +53,7 @@ export class AnnotationsSrv {
|
||||
var panel = options.panel;
|
||||
var dashboard = options.dashboard;
|
||||
|
||||
if (panel && panel.alert && panel.alert.enabled) {
|
||||
if (panel && panel.alert) {
|
||||
return this.backendSrv.get('/api/annotations', {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
@@ -54,6 +68,28 @@ export class AnnotationsSrv {
|
||||
return this.$q.when([]);
|
||||
}
|
||||
|
||||
getAlertStates(options) {
|
||||
if (!options.dashboard.id) {
|
||||
return this.$q.when([]);
|
||||
}
|
||||
|
||||
// ignore if no alerts
|
||||
if (options.panel && !options.panel.alert) {
|
||||
return this.$q.when([]);
|
||||
}
|
||||
|
||||
if (options.range.raw.to !== 'now') {
|
||||
return this.$q.when([]);
|
||||
}
|
||||
|
||||
if (this.alertStatesPromise) {
|
||||
return this.alertStatesPromise;
|
||||
}
|
||||
|
||||
this.alertStatesPromise = this.backendSrv.get('/api/alerts/states-for-dashboard', {dashboardId: options.dashboard.id});
|
||||
return this.alertStatesPromise;
|
||||
}
|
||||
|
||||
getGlobalAnnotations(options) {
|
||||
var dashboard = options.dashboard;
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ export class DashNavCtrl {
|
||||
var confirmText = "";
|
||||
var text2 = $scope.dashboard.title;
|
||||
var alerts = $scope.dashboard.rows.reduce((memo, row) => {
|
||||
memo += row.panels.filter(panel => panel.alert && panel.alert.enabled).length;
|
||||
memo += row.panels.filter(panel => panel.alert).length;
|
||||
return memo;
|
||||
}, 0);
|
||||
|
||||
|
||||
@@ -131,7 +131,9 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
var intervalOverride = this.panel.interval;
|
||||
|
||||
// if no panel interval check datasource
|
||||
if (!intervalOverride && this.datasource && this.datasource.interval) {
|
||||
if (intervalOverride) {
|
||||
intervalOverride = this.templateSrv.replace(intervalOverride, this.panel.scopedVars);
|
||||
} else if (this.datasource && this.datasource.interval) {
|
||||
intervalOverride = this.datasource.interval;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import $ from 'jquery';
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
var panelTemplate = `
|
||||
<div class="panel-container" ng-class="{'panel-transparent': ctrl.panel.transparent}">
|
||||
<div class="panel-container">
|
||||
<div class="panel-header">
|
||||
<span class="alert-error panel-error small pointer" ng-if="ctrl.error" ng-click="ctrl.openInspector()">
|
||||
<span data-placement="top" bs-tooltip="ctrl.error">
|
||||
@@ -65,6 +65,26 @@ module.directive('grafanaPanel', function() {
|
||||
link: function(scope, elem) {
|
||||
var panelContainer = elem.find('.panel-container');
|
||||
var ctrl = scope.ctrl;
|
||||
|
||||
// the reason for handling these classes this way is for performance
|
||||
// limit the watchers on panels etc
|
||||
|
||||
ctrl.events.on('render', () => {
|
||||
panelContainer.toggleClass('panel-transparent', ctrl.panel.transparent === true);
|
||||
panelContainer.toggleClass('panel-has-alert', ctrl.panel.alert !== undefined);
|
||||
|
||||
if (panelContainer.hasClass('panel-has-alert')) {
|
||||
panelContainer.removeClass('panel-alert-state--ok panel-alert-state--alerting');
|
||||
}
|
||||
|
||||
// set special class for ok, or alerting states
|
||||
if (ctrl.alertState) {
|
||||
if (ctrl.alertState.state === 'ok' || ctrl.alertState.state === 'alerting') {
|
||||
panelContainer.addClass('panel-alert-state--' + ctrl.alertState.state);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watchGroup(['ctrl.fullscreen', 'ctrl.containerHeight'], function() {
|
||||
panelContainer.css({minHeight: ctrl.containerHeight});
|
||||
elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false);
|
||||
|
||||
@@ -12,6 +12,7 @@ function (angular, $, _, Tether) {
|
||||
.directive('panelMenu', function($compile, linkSrv) {
|
||||
var linkTemplate =
|
||||
'<span class="panel-title drag-handle pointer">' +
|
||||
'<span class="icon-gf panel-alert-icon"></span>' +
|
||||
'<span class="panel-title-text drag-handle">{{ctrl.panel.title | interpolateTemplateVars:this}}</span>' +
|
||||
'<span class="panel-links-btn"><i class="fa fa-external-link"></i></span>' +
|
||||
'<span class="panel-time-info" ng-show="ctrl.timeInfo"><i class="fa fa-clock-o"></i> {{ctrl.timeInfo}}</span>' +
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
<span class="gf-form-label width-6">Span</span>
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
|
||||
</div>
|
||||
<div class="gf-form max-width-26">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-8">Height</span>
|
||||
<input type="text" class="gf-form-input max-width-6" ng-model='ctrl.panel.height' placeholder="100px"></input>
|
||||
<editor-checkbox text="Transparent" model="ctrl.panel.transparent"></editor-checkbox>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label="Transparent" checked="ctrl.panel.transparent" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
|
||||
@@ -62,7 +62,7 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
annotations = data.annotations || annotations;
|
||||
annotations = ctrl.annotations;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
hiddenSeries: any = {};
|
||||
seriesList: any = [];
|
||||
dataList: any = [];
|
||||
annotations: any = [];
|
||||
alertState: any;
|
||||
|
||||
annotationsPromise: any;
|
||||
datapointsCount: number;
|
||||
datapointsOutside: boolean;
|
||||
@@ -167,11 +170,11 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
|
||||
onDataError(err) {
|
||||
this.seriesList = [];
|
||||
this.annotations = [];
|
||||
this.render([]);
|
||||
}
|
||||
|
||||
onDataReceived(dataList) {
|
||||
|
||||
this.dataList = dataList;
|
||||
this.seriesList = this.processor.getSeriesList({dataList: dataList, range: this.range});
|
||||
|
||||
@@ -186,9 +189,10 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
this.annotationsPromise.then(annotations => {
|
||||
this.annotationsPromise.then(result => {
|
||||
this.loading = false;
|
||||
this.seriesList.annotations = annotations;
|
||||
this.alertState = result.alertState;
|
||||
this.annotations = result.annotations;
|
||||
this.render(this.seriesList);
|
||||
}, () => {
|
||||
this.loading = false;
|
||||
|
||||
@@ -13,7 +13,7 @@ export class ThresholdFormCtrl {
|
||||
constructor($scope) {
|
||||
this.panel = this.panelCtrl.panel;
|
||||
|
||||
if (this.panel.alert && this.panel.alert.enabled) {
|
||||
if (this.panel.alert) {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@
|
||||
ng-change="editor.render()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label-class="width-4"
|
||||
label="Scroll"
|
||||
checked="editor.panel.scroll"
|
||||
change="editor.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-4" label="Scroll" checked="editor.panel.scroll" on-change="editor.render()"></gf-form-switch>
|
||||
<div class="gf-form max-width-17">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
|
||||
@@ -38,3 +38,33 @@
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-has-alert {
|
||||
.panel-alert-icon:before {
|
||||
content: "\e611";
|
||||
position: relative;
|
||||
top: 1px;
|
||||
left: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-alert-state {
|
||||
&--alerting {
|
||||
box-shadow: 0 0 10px $critical;
|
||||
|
||||
.panel-alert-icon:before {
|
||||
color: $critical;
|
||||
content: "\e610";
|
||||
}
|
||||
}
|
||||
|
||||
&--ok {
|
||||
//box-shadow: 0 0 5px rgba(0,200,0,10.8);
|
||||
.panel-alert-icon:before {
|
||||
color: $online;
|
||||
content: "\e610";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user