The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/panel/graph/threshold_manager.ts

261 lines
7.4 KiB

import 'vendor/flot/jquery.flot';
import $ from 'jquery';
import { isNumber } from 'lodash';
import { getColorForTheme } from '@grafana/data';
import { CoreEvents } from 'app/types';
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
import { config } from 'app/core/config';
export class ThresholdManager {
plot: any;
placeholder: any;
height: any;
thresholds: any;
needsCleanup = false;
hasSecondYAxis: any;
constructor(private panelCtrl: PanelCtrl) {}
getHandleHtml(handleIndex: any, model: { colorMode: string }, valueStr: any) {
let stateClass = model.colorMode;
if (model.colorMode === 'custom') {
stateClass = 'critical';
}
return `
<div class="alert-handle-wrapper alert-handle-wrapper--T${handleIndex}">
<div class="alert-handle-line alert-handle-line--${stateClass}">
</div>
<div class="alert-handle" data-handle-index="${handleIndex}">
<i class="icon-gf icon-gf-${stateClass} alert-state-${stateClass}"></i>
<span class="alert-handle-value">${valueStr}<i class="alert-handle-grip"></i></span>
</div>
</div>`;
}
initDragging(evt: any) {
const handleElem = $(evt.currentTarget).parents('.alert-handle-wrapper');
const handleIndex = $(evt.currentTarget).data('handleIndex');
let lastY: number | null = null;
let posTop: number;
const plot = this.plot;
const panelCtrl = this.panelCtrl;
const model = this.thresholds[handleIndex];
function dragging(evt: any) {
if (lastY === null) {
lastY = evt.clientY;
} else {
const diff = evt.clientY - lastY;
posTop = posTop + diff;
lastY = evt.clientY;
handleElem.css({ top: posTop + diff });
}
}
function stopped() {
// calculate graph level
let graphValue = plot.c2p({ left: 0, top: posTop }).y;
graphValue = parseInt(graphValue.toFixed(0), 10);
model.value = graphValue;
handleElem.off('mousemove', dragging);
document.removeEventListener('mouseup', stopped);
// trigger digest and render
panelCtrl.$scope.$apply(() => {
panelCtrl.render();
panelCtrl.events.emit(CoreEvents.thresholdChanged, {
threshold: model,
handleIndex: handleIndex,
});
});
}
lastY = null;
posTop = handleElem.position().top;
handleElem.on('mousemove', dragging);
document.addEventListener('mouseup', stopped);
}
cleanUp() {
this.placeholder.find('.alert-handle-wrapper').remove();
this.needsCleanup = false;
}
renderHandle(handleIndex: number, defaultHandleTopPos: number) {
const model = this.thresholds[handleIndex];
// alerting defines
if (!model.visible && (this.panelCtrl as any).alert) {
return;
}
const value = model.value;
let valueStr = value;
let handleTopPos = 0;
// handle no value
if (!isNumber(value)) {
valueStr = '';
handleTopPos = defaultHandleTopPos;
} else {
const valueCanvasPos = this.plot.p2c({ x: 0, y: value });
handleTopPos = Math.round(Math.min(Math.max(valueCanvasPos.top, 0), this.height) - 6);
}
const handleElem = $(this.getHandleHtml(handleIndex, model, valueStr));
this.placeholder.append(handleElem);
handleElem.toggleClass('alert-handle-wrapper--no-value', valueStr === '');
handleElem.css({ top: handleTopPos });
}
shouldDrawHandles() {
// @ts-ignore
return !this.hasSecondYAxis && this.panelCtrl.editingThresholds && this.panelCtrl.panel.thresholds.length > 0;
}
prepare(elem: JQuery, data: any[]) {
this.hasSecondYAxis = false;
for (let i = 0; i < data.length; i++) {
if (data[i].yaxis > 1) {
this.hasSecondYAxis = true;
break;
}
}
if (this.shouldDrawHandles()) {
const thresholdMargin = this.panelCtrl.panel.thresholds.length > 1 ? '220px' : '110px';
elem.css('margin-right', thresholdMargin);
} else if (this.needsCleanup) {
elem.css('margin-right', '0');
}
}
draw(plot: any) {
this.thresholds = this.panelCtrl.panel.thresholds;
this.plot = plot;
this.placeholder = plot.getPlaceholder();
if (this.needsCleanup) {
this.cleanUp();
}
if (!this.shouldDrawHandles()) {
return;
}
this.height = plot.height();
if (this.thresholds.length > 0) {
this.renderHandle(0, 10);
}
if (this.thresholds.length > 1) {
this.renderHandle(1, this.height - 30);
}
this.placeholder.off('mousedown', '.alert-handle');
this.placeholder.on('mousedown', '.alert-handle', this.initDragging.bind(this));
this.needsCleanup = true;
}
addFlotOptions(options: any, panel: any) {
if (!panel.thresholds || panel.thresholds.length === 0) {
return;
}
let gtLimit = Infinity;
let ltLimit = -Infinity;
let i, threshold, other;
for (i = 0; i < panel.thresholds.length; i++) {
threshold = panel.thresholds[i];
if (!isNumber(threshold.value)) {
continue;
}
let limit;
switch (threshold.op) {
case 'gt': {
limit = gtLimit;
// if next threshold is less then op and greater value, then use that as limit
if (panel.thresholds.length > i + 1) {
other = panel.thresholds[i + 1];
if (other.value > threshold.value) {
limit = other.value;
ltLimit = limit;
}
}
break;
}
case 'lt': {
limit = ltLimit;
// if next threshold is less then op and greater value, then use that as limit
if (panel.thresholds.length > i + 1) {
other = panel.thresholds[i + 1];
if (other.value < threshold.value) {
limit = other.value;
gtLimit = limit;
}
}
break;
}
}
let fillColor, lineColor;
switch (threshold.colorMode) {
case 'critical': {
fillColor = 'rgba(234, 112, 112, 0.12)';
lineColor = 'rgba(237, 46, 24, 0.60)';
break;
}
case 'warning': {
fillColor = 'rgba(235, 138, 14, 0.12)';
lineColor = 'rgba(247, 149, 32, 0.60)';
break;
}
case 'ok': {
fillColor = 'rgba(11, 237, 50, 0.090)';
lineColor = 'rgba(6,163,69, 0.60)';
break;
}
case 'custom': {
fillColor = threshold.fillColor;
lineColor = threshold.lineColor;
break;
}
}
// fill
if (threshold.fill) {
if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
options.grid.markings.push({
y2axis: { from: threshold.value, to: limit },
color: getColorForTheme(fillColor, config.theme),
});
} else {
options.grid.markings.push({
yaxis: { from: threshold.value, to: limit },
color: getColorForTheme(fillColor, config.theme),
});
}
}
if (threshold.line) {
if (threshold.yaxis === 'right' && this.hasSecondYAxis) {
options.grid.markings.push({
y2axis: { from: threshold.value, to: threshold.value },
color: getColorForTheme(lineColor, config.theme),
});
} else {
options.grid.markings.push({
yaxis: { from: threshold.value, to: threshold.value },
color: getColorForTheme(lineColor, config.theme),
});
}
}
}
}
}