diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js
index b74b3a4e8b3..b8a8af24ab9 100644
--- a/public/app/features/dashboard/viewStateSrv.js
+++ b/public/app/features/dashboard/viewStateSrv.js
@@ -115,6 +115,11 @@ function (angular, _, $) {
}
}
+ // if no edit state cleanup tab parm
+ if (!this.state.edit) {
+ delete this.state.tab;
+ }
+
$location.search(this.serializeToUrl());
this.syncState();
};
diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts
index bcb1980f854..e58994974cd 100644
--- a/public/app/features/panel/panel_ctrl.ts
+++ b/public/app/features/panel/panel_ctrl.ts
@@ -95,10 +95,10 @@ export class PanelCtrl {
this.editModeInitiated = true;
this.events.emit('init-edit-mode', null);
- var routeParams = this.$injector.get('$routeParams');
- if (routeParams.editorTab) {
+ var urlTab = (this.$injector.get('$routeParams').tab || '').toLowerCase();
+ if (urlTab) {
this.editorTabs.forEach((tab, i) => {
- if (tab.title === routeParams.editorTab) {
+ if (tab.title.toLowerCase() === urlTab) {
this.editorTabIndex = i;
}
});
@@ -109,7 +109,7 @@ export class PanelCtrl {
this.editorTabIndex = newIndex;
var route = this.$injector.get('$route');
- route.current.params.editorTab = this.editorTabs[newIndex].title;
+ route.current.params.tab = this.editorTabs[newIndex].title.toLowerCase();
route.updateParams();
}
diff --git a/public/app/plugins/panel/graph/alert_tab_ctrl.ts b/public/app/plugins/panel/graph/alert_tab_ctrl.ts
index 2578f17ffca..a8cac86a6ad 100644
--- a/public/app/plugins/panel/graph/alert_tab_ctrl.ts
+++ b/public/app/plugins/panel/graph/alert_tab_ctrl.ts
@@ -1,8 +1,6 @@
///
import _ from 'lodash';
-import $ from 'jquery';
-import angular from 'angular';
import {
QueryPartDef,
@@ -19,72 +17,107 @@ var alertQueryDef = new QueryPartDef({
defaultParams: ['#A', '5m', 'now', 'avg']
});
+var reducerAvgDef = new QueryPartDef({
+ type: 'avg',
+ params: [],
+ defaultParams: []
+});
+
export class AlertTabCtrl {
panel: any;
panelCtrl: any;
- metricTargets;
+ testing: boolean;
+ testResult: any;
+
handlers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
- transforms = [
- {
- text: 'Aggregation',
- type: 'aggregation',
- },
- {
- text: 'Linear Forecast',
- type: 'forecast',
- },
+ conditionTypes = [
+ {text: 'Query', value: 'query'},
+ {text: 'Other alert', value: 'other_alert'},
+ {text: 'Time of day', value: 'time_of_day'},
+ {text: 'Day of week', value: 'day_of_week'},
];
- aggregators = ['avg', 'sum', 'min', 'max', 'last'];
alert: any;
- thresholds: any;
- query: any;
- queryParams: any;
- transformDef: any;
- levelOpList = [
+ conditionModels: any;
+ evalFunctions = [
{text: '>', value: '>'},
{text: '<', value: '<'},
- {text: '=', value: '='},
];
+ severityLevels = [
+ {text: 'Critical', value: 'critical'},
+ {text: 'Warning', value: 'warning'},
+ ];
+ addNotificationSegment;
+ notifications;
+ alertNotifications;
/** @ngInject */
- constructor($scope, private $timeout) {
+ constructor(private $scope, private $timeout, private backendSrv, private dashboardSrv, private uiSegmentSrv) {
this.panelCtrl = $scope.ctrl;
this.panel = this.panelCtrl.panel;
- $scope.ctrl = this;
+ this.$scope.ctrl = this;
+ }
+
+ $onInit() {
+ this.addNotificationSegment = this.uiSegmentSrv.newPlusButton();
- this.metricTargets = this.panel.targets.map(val => val);
this.initModel();
// set panel alert edit mode
- $scope.$on("$destroy", () => {
+ this.$scope.$on("$destroy", () => {
this.panelCtrl.editingAlert = false;
this.panelCtrl.render();
});
+
+ // build notification model
+ this.notifications = [];
+ this.alertNotifications = [];
+
+ return this.backendSrv.get('/api/alert-notifications').then(res => {
+ this.notifications = res;
+
+ _.each(this.alert.notifications, item => {
+ var model = _.findWhere(this.notifications, {id: item.id});
+ if (model) {
+ this.alertNotifications.push(model);
+ }
+ });
+ });
}
- getThresholdWithDefaults(threshold) {
- threshold = threshold || {};
- threshold.op = threshold.op || '>';
- threshold.value = threshold.value || undefined;
- return threshold;
+ getNotifications() {
+ return Promise.resolve(this.notifications.map(item => {
+ return this.uiSegmentSrv.newSegment(item.name);
+ }));
+ }
+
+ notificationAdded() {
+ var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});
+ if (!model) {
+ return;
+ }
+
+ this.alertNotifications.push({name: model.name});
+ this.alert.notifications.push({id: model.id});
+
+ // reset plus button
+ this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value;
+ this.addNotificationSegment.html = this.uiSegmentSrv.newPlusButton().html;
+ }
+
+ removeNotification(index) {
+ this.alert.notifications.splice(index, 1);
+ this.alertNotifications.splice(index, 1);
}
initModel() {
var alert = this.alert = this.panel.alert = this.panel.alert || {};
- // set threshold defaults
- alert.warn = this.getThresholdWithDefaults(alert.warn);
- alert.crit = this.getThresholdWithDefaults(alert.crit);
-
- alert.query = alert.query || {};
- alert.query.refId = alert.query.refId || 'A';
- alert.query.from = alert.query.from || '5m';
- alert.query.to = alert.query.to || 'now';
-
- alert.transform = alert.transform || {};
- alert.transform.type = alert.transform.type || 'aggregation';
- alert.transform.method = alert.transform.method || 'avg';
+ alert.conditions = alert.conditions || [];
+ if (alert.conditions.length === 0) {
+ alert.conditions.push(this.buildDefaultCondition());
+ }
+ alert.severity = alert.severity || 'critical';
alert.frequency = alert.frequency || '60s';
alert.handler = alert.handler || 1;
alert.notifications = alert.notifications || [];
@@ -93,50 +126,87 @@ export class AlertTabCtrl {
alert.name = alert.name || defaultName;
alert.description = alert.description || defaultName;
- // great temp working model
- this.queryParams = {
- params: [alert.query.refId, alert.query.from, alert.query.to]
- };
+ this.conditionModels = _.reduce(alert.conditions, (memo, value) => {
+ memo.push(this.buildConditionModel(value));
+ return memo;
+ }, []);
- // init the query part components model
- this.query = new QueryPart(this.queryParams, alertQueryDef);
- this.transformDef = _.findWhere(this.transforms, {type: alert.transform.type});
-
- this.panelCtrl.editingAlert = true;
+ ///this.panelCtrl.editingAlert = true;
+ this.syncThresholds();
this.panelCtrl.render();
}
- queryUpdated() {
- this.alert.query = {
- refId: this.query.params[0],
- from: this.query.params[1],
- to: this.query.params[2],
+ syncThresholds() {
+ var threshold: any = {};
+ if (this.panel.thresholds && this.panel.thresholds.length > 0) {
+ threshold = this.panel.thresholds[0];
+ } else {
+ this.panel.thresholds = [threshold];
+ }
+
+ var updated = false;
+ for (var condition of this.conditionModels) {
+ if (condition.type === 'query') {
+ var value = condition.evaluator.params[0];
+ if (!_.isNumber(value)) {
+ continue;
+ }
+
+ if (value !== threshold.from) {
+ threshold.from = value;
+ updated = true;
+ }
+
+ if (condition.evaluator.type === '<' && threshold.to !== -Infinity) {
+ threshold.to = -Infinity;
+ updated = true;
+ } else if (condition.evaluator.type === '>' && threshold.to !== Infinity) {
+ threshold.to = Infinity;
+ updated = true;
+ }
+ }
+ }
+
+ return updated;
+ }
+
+ buildDefaultCondition() {
+ return {
+ type: 'query',
+ query: {params: ['A', '5m', 'now']},
+ reducer: {type: 'avg', params: []},
+ evaluator: {type: '>', params: [null]},
};
}
- transformChanged() {
- // clear model
- this.alert.transform = {type: this.alert.transform.type};
- this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
+ buildConditionModel(source) {
+ var cm: any = {source: source, type: source.type};
- switch (this.alert.transform.type) {
- case 'aggregation': {
- this.alert.transform.method = 'avg';
- break;
- }
- case "forecast": {
- this.alert.transform.timespan = '7d';
- break;
- }
- }
+ cm.queryPart = new QueryPart(source.query, alertQueryDef);
+ cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
+ cm.evaluator = source.evaluator;
+
+ return cm;
+ }
+
+ queryPartUpdated(conditionModel) {
+ }
+
+ addCondition(type) {
+ var condition = this.buildDefaultCondition();
+ // add to persited model
+ this.alert.conditions.push(condition);
+ // add to view model
+ this.conditionModels.push(this.buildConditionModel(condition));
+ }
+
+ removeCondition(index) {
+ this.alert.conditions.splice(index, 1);
+ this.conditionModels.splice(index, 1);
}
delete() {
this.alert.enabled = false;
- this.alert.warn.value = undefined;
- this.alert.crit.value = undefined;
-
- // reset model but keep thresholds instance
this.initModel();
}
@@ -145,8 +215,24 @@ export class AlertTabCtrl {
this.initModel();
}
- thresholdsUpdated() {
- this.panelCtrl.render();
+ thresholdUpdated() {
+ if (this.syncThresholds()) {
+ this.panelCtrl.render();
+ }
+ }
+
+ test() {
+ this.testing = true;
+
+ var payload = {
+ dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),
+ panelId: this.panelCtrl.panel.id,
+ };
+
+ return this.backendSrv.post('/api/alerts/test', payload).then(res => {
+ this.testResult = res;
+ this.testing = false;
+ });
}
}
diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js
index eb657b55335..13172b06019 100755
--- a/public/app/plugins/panel/graph/graph.js
+++ b/public/app/plugins/panel/graph/graph.js
@@ -184,7 +184,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
// give space to alert editing
if (ctrl.editingAlert) {
if (!thresholdControls) {
- elem.css('margin-right', '220px');
+ elem.css('margin-right', '110px');
thresholdControls = new ThresholdControls(ctrl);
}
} else if (thresholdControls) {
@@ -327,74 +327,28 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
}
function addGridThresholds(options, panel) {
- if (!panel.alert) {
+ if (!panel.thresholds || panel.thresholds.length === 0) {
return;
}
- var crit = panel.alert.crit;
- var warn = panel.alert.warn;
- var critEdge = Infinity;
-
- if (_.isNumber(crit.value)) {
- if (crit.op === '<') {
- critEdge = -Infinity;
+ for (var i = 0; i < panel.thresholds.length; i++) {
+ var threshold = panel.thresholds[i];
+ if (!_.isNumber(threshold.from)) {
+ continue;
}
// fill
options.grid.markings.push({
- yaxis: {from: crit.value, to: critEdge},
+ yaxis: {from: threshold.from, to: threshold.to},
color: 'rgba(234, 112, 112, 0.10)',
});
// line
options.grid.markings.push({
- yaxis: {from: crit.value, to: crit.value},
+ yaxis: {from: threshold.from, to: threshold.from},
color: '#ed2e18'
});
}
-
- if (_.isNumber(warn.value)) {
- //var warnEdge = crit.value || Infinity;
- var warnEdge;
- if (crit.value) {
- warnEdge = crit.value;
- } else {
- warnEdge = warn.op === '<' ? -Infinity : Infinity;
- }
-
- // fill
- options.grid.markings.push({
- yaxis: {from: warn.value, to: warnEdge},
- color: 'rgba(216, 200, 27, 0.10)',
- });
-
- // line
- options.grid.markings.push({
- yaxis: {from: warn.value, to: warn.value},
- color: '#F79520'
- });
- }
-
- // if (_.isNumber(panel.grid.threshold1)) {
- // var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null);
- // options.grid.markings.push({
- // yaxis: { from: panel.grid.threshold1, to: limit1 },
- // color: panel.grid.threshold1Color
- // });
- //
- // if (_.isNumber(panel.grid.threshold2)) {
- // var limit2;
- // if (panel.grid.thresholdLine) {
- // limit2 = panel.grid.threshold2;
- // } else {
- // limit2 = panel.grid.threshold1 > panel.grid.threshold2 ? -Infinity : +Infinity;
- // }
- // options.grid.markings.push({
- // yaxis: { from: panel.grid.threshold2, to: limit2 },
- // color: panel.grid.threshold2Color
- // });
- // }
- // }
}
function addAnnotations(options) {
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts
index 75aa05ee57c..6e00bef084a 100644
--- a/public/app/plugins/panel/graph/module.ts
+++ b/public/app/plugins/panel/graph/module.ts
@@ -105,6 +105,7 @@ class GraphCtrl extends MetricsPanelCtrl {
// other style overrides
seriesOverrides: [],
alerting: {},
+ thresholds: [],
};
/** @ngInject */
@@ -132,7 +133,7 @@ class GraphCtrl extends MetricsPanelCtrl {
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
if (config.alertingEnabled) {
- this.addEditorTab('Alerting', graphAlertEditor, 5);
+ this.addEditorTab('Alert', graphAlertEditor, 5);
}
this.logScales = {
diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html
index 4b76648a845..463e2cb4154 100644
--- a/public/app/plugins/panel/graph/partials/tab_alerting.html
+++ b/public/app/plugins/panel/graph/partials/tab_alerting.html
@@ -1,147 +1,140 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+ Evaluating rule
+
+
+
+
+
+
+
diff --git a/public/app/plugins/panel/graph/thresholds.ts b/public/app/plugins/panel/graph/thresholds.ts
index ee413fd990f..9ec7a5efb53 100644
--- a/public/app/plugins/panel/graph/thresholds.ts
+++ b/public/app/plugins/panel/graph/thresholds.ts
@@ -8,10 +8,10 @@ export class ThresholdControls {
plot: any;
placeholder: any;
height: any;
- alert: any;
+ thresholds: any;
constructor(private panelCtrl) {
- this.alert = this.panelCtrl.panel.alert;
+ this.thresholds = this.panelCtrl.panel.thresholds;
}
getHandleInnerHtml(type, op, value) {
@@ -120,8 +120,9 @@ export class ThresholdControls {
this.placeholder = plot.getPlaceholder();
this.height = plot.height();
- this.renderHandle('crit', this.alert.crit, 10);
- this.renderHandle('warn', this.alert.warn, this.height-30);
+ if (this.thresholds.length > 0) {
+ this.renderHandle('crit', this.thresholds[0], 10);
+ }
}
}
diff --git a/public/emails/alert_notification.html b/public/emails/alert_notification.html
index 4491c71a5cc..21dded9c783 100644
--- a/public/emails/alert_notification.html
+++ b/public/emails/alert_notification.html
@@ -113,37 +113,18 @@ color: #FFFFFF !important;
-
+ {{Subject .Subject "Grafana Alert: {{.Severity}} {{.RuleName}}"}}
-{{Subject .Subject "Grafana Alert: [ {{.State}} ] {{.Name}}" }}
+
+
-Alertstate: {{.State}}
-{{.AlertPageUrl}}
-{{.DashboardLink}}
-{{.Description}}
+Alert rule: {{.RuleName}}
+Alert state: {{.RuleState}}
-{{if eq .State "Ok"}}
- Everything is Ok
-{{end}}
+Link to alert rule
-{{if ne .State "Ok" }}
-
+
-
-
- | Serie |
- State |
- Actual value |
-
- {{ range $ta := .TriggeredAlerts}}
-
- | {{$ta.Name}} |
- {{$ta.State}} |
- {{$ta.ActualValue}} |
-
- {{end}}
-
-{{end}}
|