mirror of
https://github.com/timberjoegithub/grafana-statusmap.git
synced 2026-07-21 23:42:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a2e4a394e | ||
|
|
4bd3ecbcf2 | ||
|
|
4b7d111cd6 | ||
|
|
80725f6cd5 | ||
|
|
3e37a0c3cc | ||
|
|
a4d48eb591 | ||
|
|
b96d205787 | ||
|
|
bd30626721 | ||
|
|
f02831fcbe | ||
|
|
7c2f9a9114 | ||
|
|
276d3cce92 | ||
|
|
46177c71e8 | ||
|
|
c7e7cd33bc | ||
|
|
6d4c9bb0ef | ||
|
|
7f8264a893 | ||
|
|
ef4b0e1228 | ||
|
|
99ba3cbdd6 | ||
|
|
4af775ee1f | ||
|
|
7817a3928a | ||
|
|
080f358e1b | ||
|
|
8c7279561d | ||
|
|
4e281e53d5 | ||
|
|
2479569d71 | ||
|
|
a911d64a27 | ||
|
|
d592dd8ca2 | ||
|
|
d7338a7409 | ||
|
|
48597eda59 | ||
|
|
129a37bda6 | ||
|
|
f4d2ede57b | ||
|
|
80548d3972 | ||
|
|
6b6219c1a9 | ||
|
|
b33248c664 | ||
|
|
28d4f51876 | ||
|
|
9463913189 | ||
|
|
a3f1495680 | ||
|
|
18574c3fa1 |
@@ -2,3 +2,4 @@ node_modules
|
||||
.jshintrc
|
||||
.idea
|
||||
.sass-cache
|
||||
.tscache
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## v0.3.1
|
||||
|
||||
- Fix to work in Grafana 7.1.0
|
||||
|
||||
## v0.3.0
|
||||
|
||||
- Tooltip freeze on click
|
||||
- Show URLs in tooltip
|
||||
- Pagination controls
|
||||
- Fix tooltip and null buckets
|
||||
- Fix display for 6.7+, 7.0+
|
||||
|
||||
## v0.2.0
|
||||
|
||||
- Migrate code base to typescript
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
- InfluxDB
|
||||
- Mysql
|
||||
* Tested with Grafana:
|
||||
- 5.1.3
|
||||
- 5.4.3
|
||||
- 6.0.0
|
||||
- 6.3, 6.6, 6.7
|
||||
- 7.0, 7.1
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -161,7 +161,7 @@ __Multiple values__ checkbox specifies how they should be displayed:
|
||||
|
||||

|
||||
|
||||
__Null values__ can be treated as empty buckets or displayed with the color of `0` value.
|
||||
__Display nulls__ can be treated as empty buckets or displayed with the color of `0` value.
|
||||
|
||||

|
||||
|
||||
@@ -172,15 +172,39 @@ __Rounding__ may be used to round edges.
|
||||
|
||||

|
||||
|
||||
__Values index__ set to positive number to display only values from specified timeseries.
|
||||
|
||||
#### Display
|
||||
|
||||

|
||||
|
||||
__Show legend__ checkbox toggles legend at the bottom of the panel.
|
||||
|
||||
__Rows sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
|
||||
|
||||
#### Pagination
|
||||
|
||||

|
||||
|
||||
__Enable pagination__ toggles pagination controls on graph.
|
||||
|
||||
__Rows per page__ a number of rows to display on graph.
|
||||
|
||||
#### Tooltip
|
||||
|
||||

|
||||
|
||||
__Show tooltip__ toggles tooltip display on mouse over buckets.
|
||||
|
||||
__Y axis sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
|
||||
__Freeze on click__ toggles tooltip "freezing" on click. Frozen tooltip can be used to compare data with floating tooltip or to follow URLs.
|
||||
|
||||
__Show items__ toggles display of additional items in tooltip.
|
||||
|
||||
__Items__ is a list of definitions to display URLs in tooltip.
|
||||
|
||||
Each URL has a template, icon, label and formating options: lowercase and date format for variables.
|
||||
|
||||

|
||||
|
||||
## Development
|
||||
|
||||
@@ -191,7 +215,7 @@ the directory containing Statusmap plugin):
|
||||
docker run --rm -it -v $PWD:/var/lib/grafana/plugins/flant-statusmap-panel \
|
||||
-p 3000:3000 --name grafana.docker \
|
||||
--env=GF_USERS_DEFAULT_THEME=light \
|
||||
grafana/grafana:5.1.3
|
||||
grafana/grafana:7.1.3
|
||||
```
|
||||
|
||||
This will expose local plugin from your machine to Grafana container. Now run `grunt` to compile
|
||||
|
||||
Vendored
+12
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## v0.3.1
|
||||
|
||||
- Fix to work in Grafana 7.1.0
|
||||
|
||||
## v0.3.0
|
||||
|
||||
- Tooltip freeze on click
|
||||
- Show URLs in tooltip
|
||||
- Pagination controls
|
||||
- Fix tooltip and null buckets
|
||||
- Fix display for 6.7+, 7.0+
|
||||
|
||||
## v0.2.0
|
||||
|
||||
- Migrate code base to typescript
|
||||
|
||||
Vendored
+29
-5
@@ -26,9 +26,9 @@
|
||||
- InfluxDB
|
||||
- Mysql
|
||||
* Tested with Grafana:
|
||||
- 5.1.3
|
||||
- 5.4.3
|
||||
- 6.0.0
|
||||
- 6.3, 6.6, 6.7
|
||||
- 7.0, 7.1
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -161,7 +161,7 @@ __Multiple values__ checkbox specifies how they should be displayed:
|
||||
|
||||

|
||||
|
||||
__Null values__ can be treated as empty buckets or displayed with the color of `0` value.
|
||||
__Display nulls__ can be treated as empty buckets or displayed with the color of `0` value.
|
||||
|
||||

|
||||
|
||||
@@ -172,15 +172,39 @@ __Rounding__ may be used to round edges.
|
||||
|
||||

|
||||
|
||||
__Values index__ set to positive number to display only values from specified timeseries.
|
||||
|
||||
#### Display
|
||||
|
||||

|
||||
|
||||
__Show legend__ checkbox toggles legend at the bottom of the panel.
|
||||
|
||||
__Rows sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
|
||||
|
||||
#### Pagination
|
||||
|
||||

|
||||
|
||||
__Enable pagination__ toggles pagination controls on graph.
|
||||
|
||||
__Rows per page__ a number of rows to display on graph.
|
||||
|
||||
#### Tooltip
|
||||
|
||||

|
||||
|
||||
__Show tooltip__ toggles tooltip display on mouse over buckets.
|
||||
|
||||
__Y axis sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
|
||||
__Freeze on click__ toggles tooltip "freezing" on click. Frozen tooltip can be used to compare data with floating tooltip or to follow URLs.
|
||||
|
||||
__Show items__ toggles display of additional items in tooltip.
|
||||
|
||||
__Items__ is a list of definitions to display URLs in tooltip.
|
||||
|
||||
Each URL has a template, icon, label and formating options: lowercase and date format for variables.
|
||||
|
||||

|
||||
|
||||
## Development
|
||||
|
||||
@@ -191,7 +215,7 @@ the directory containing Statusmap plugin):
|
||||
docker run --rm -it -v $PWD:/var/lib/grafana/plugins/flant-statusmap-panel \
|
||||
-p 3000:3000 --name grafana.docker \
|
||||
--env=GF_USERS_DEFAULT_THEME=light \
|
||||
grafana/grafana:5.1.3
|
||||
grafana/grafana:7.1.3
|
||||
```
|
||||
|
||||
This will expose local plugin from your machine to Grafana container. Now run `grunt` to compile
|
||||
|
||||
Vendored
+35
-19
@@ -1,9 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "app/core/core", "app/core/utils/ticks", "app/core/core_module"], function (_export, _context) {
|
||||
System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "app/core/core", "app/core/utils/ticks", "app/core/core_module", "./libs/grafana/events/index"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var _, $, d3, d3ScaleChromatic, contextSrv, tickStep, coreModule, LEGEND_STEP_WIDTH;
|
||||
var _, $, d3, d3ScaleChromatic, contextSrv, tickStep, coreModule, PanelEvents, LEGEND_STEP_WIDTH;
|
||||
|
||||
function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) {
|
||||
var legendElem = $(elem).find('svg');
|
||||
@@ -21,7 +21,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange).enter().append("rect") // translate from range space into pixels
|
||||
// and shift all rectangles to the right by 10
|
||||
.attr("x", function (d) {
|
||||
return d * widthFactor + 10;
|
||||
return (d - rangeFrom) * widthFactor + 10;
|
||||
}).attr("y", 0) // rectangles are slightly overlaped to prevent gaps
|
||||
.attr("width", LEGEND_STEP_WIDTH + 1).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) {
|
||||
return colorScale(d);
|
||||
@@ -53,7 +53,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
|
||||
}
|
||||
|
||||
function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
|
||||
function drawDiscreteColorLegend(elem, colorOptions, discreteExtraSeries) {
|
||||
var legendElem = $(elem).find('svg');
|
||||
var legend = d3.select(legendElem.get(0));
|
||||
clearLegend(elem);
|
||||
@@ -65,7 +65,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
|
||||
var valuesNumber = thresholds.length; // graph width as a fallback
|
||||
|
||||
var $heatmap = $(elem).parent().parent().parent().find('.status-heatmap-panel');
|
||||
var $heatmap = $(elem).parent().parent().parent().find('.statusmap-panel');
|
||||
var graphWidthAttr = $heatmap.find('svg').attr("width");
|
||||
var graphWidth = parseInt(graphWidthAttr); // calculate max width of tooltip and use it as width for each item
|
||||
|
||||
@@ -87,7 +87,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
return d * itemWidth;
|
||||
}).attr("y", 0).attr("width", itemWidth + 1) // Overlap rectangles to prevent gaps
|
||||
.attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) {
|
||||
return discreteHelper.getDiscreteColor(d);
|
||||
return discreteExtraSeries.getDiscreteColor(d);
|
||||
});
|
||||
drawDiscreteLegendValues(elem, colorOptions, legendWidth);
|
||||
}
|
||||
@@ -100,8 +100,8 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
return;
|
||||
}
|
||||
|
||||
var legendValueScale = d3.scaleLinear().domain([0, rangeTo]).range([0, legendWidth]);
|
||||
var ticks = buildLegendTicks(0, rangeTo, maxValue, minValue);
|
||||
var legendValueScale = d3.scaleLinear().domain([rangeFrom, rangeTo]).range([0, legendWidth]);
|
||||
var ticks = buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue);
|
||||
var xAxis = d3.axisBottom(legendValueScale).tickValues(ticks).tickSize(2);
|
||||
var colorRect = legendElem.find(":first-child");
|
||||
var posY = getSvgElemHeight(legendElem) + 2;
|
||||
@@ -120,8 +120,8 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
}
|
||||
|
||||
var valuesNumber = thresholds.length;
|
||||
var rangeStep = Math.floor(legendWidth / valuesNumber);
|
||||
var valuesRange = d3.range(0, legendWidth, rangeStep);
|
||||
var rangeStep = Math.floor(legendWidth / valuesNumber); //let valuesRange = d3.range(0, legendWidth, rangeStep);
|
||||
|
||||
var legendValueScale = d3.scaleLinear().domain([0, valuesNumber]).range([0, legendWidth]);
|
||||
var thresholdValues = [];
|
||||
var thresholdTooltips = [];
|
||||
@@ -133,7 +133,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
|
||||
var xAxis = d3.axisBottom(legendValueScale).tickValues(d3.range(0, valuesNumber, 1)) //thresholdValues)
|
||||
.tickSize(2).tickFormat(function (t) {
|
||||
var i = Math.floor(t);
|
||||
var i = Math.floor(t.valueOf());
|
||||
var v = thresholdTooltips[i];
|
||||
|
||||
if (v != undefined) {
|
||||
@@ -252,7 +252,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
var ticks = [];
|
||||
|
||||
for (var i = 0; i < ticksNum; i++) {
|
||||
var current = tickStepSize * i; // Add user-defined min and max if it had been set
|
||||
var current = tickStepSize * i + rangeFrom; // Add user-defined min and max if it had been set
|
||||
|
||||
if (isValueCloseTo(minValue, current, tickStepSize)) {
|
||||
ticks.push(minValue);
|
||||
@@ -268,7 +268,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
ticks.push(maxValue);
|
||||
}
|
||||
|
||||
ticks.push(tickStepSize * i);
|
||||
ticks.push(current);
|
||||
}
|
||||
|
||||
if (!isValueCloseTo(maxValue, rangeTo, tickStepSize)) {
|
||||
@@ -300,6 +300,8 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
tickStep = _appCoreUtilsTicks.tickStep;
|
||||
}, function (_appCoreCore_module) {
|
||||
coreModule = _appCoreCore_module.default;
|
||||
}, function (_libsGrafanaEventsIndex) {
|
||||
PanelEvents = _libsGrafanaEventsIndex.PanelEvents;
|
||||
}],
|
||||
execute: function () {
|
||||
LEGEND_STEP_WIDTH = 2;
|
||||
@@ -315,7 +317,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
var ctrl = scope.ctrl;
|
||||
var panel = scope.ctrl.panel;
|
||||
render();
|
||||
ctrl.events.on('render', function () {
|
||||
ctrl.events.on(PanelEvents.render, function () {
|
||||
render();
|
||||
});
|
||||
|
||||
@@ -350,7 +352,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
var ctrl = scope.ctrl;
|
||||
var panel = scope.ctrl.panel;
|
||||
render();
|
||||
ctrl.events.on('render', function () {
|
||||
ctrl.events.on(PanelEvents.render, function () {
|
||||
render();
|
||||
});
|
||||
|
||||
@@ -361,12 +363,26 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) {
|
||||
var rangeFrom = ctrl.cardsData.minValue;
|
||||
var rangeTo = ctrl.cardsData.maxValue;
|
||||
if (ctrl.bucketMatrix) {
|
||||
var rangeFrom = ctrl.bucketMatrix.minValue;
|
||||
var rangeTo = ctrl.bucketMatrix.maxValue;
|
||||
var maxValue = panel.color.max || rangeTo;
|
||||
var minValue = panel.color.min || rangeFrom;
|
||||
|
||||
if (ctrl.bucketMatrix.noDatapoints) {
|
||||
if (!panel.color.max) {
|
||||
rangeTo = maxValue = 100;
|
||||
} else {
|
||||
rangeTo = 100;
|
||||
}
|
||||
|
||||
if (!panel.color.min) {
|
||||
rangeFrom = minValue = 0;
|
||||
} else {
|
||||
rangeFrom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.color.mode === 'spectrum') {
|
||||
var colorScheme = _.find(ctrl.colorSchemes, {
|
||||
value: panel.color.colorScheme
|
||||
@@ -378,7 +394,7 @@ System.register(["lodash", "jquery", "d3", "./libs/d3-scale-chromatic/index", "a
|
||||
drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue);
|
||||
} else if (panel.color.mode === 'discrete') {
|
||||
var _colorOptions = panel.color;
|
||||
drawDiscreteColorLegend(elem, _colorOptions, ctrl.discreteHelper);
|
||||
drawDiscreteColorLegend(elem, _colorOptions, ctrl.discreteExtraSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+75
-16
@@ -16,7 +16,7 @@ System.register([], function (_export, _context) {
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
// Helper methods to handle discrete color mode
|
||||
// Extra Series methods to handle discrete color mode
|
||||
_export("ColorModeDiscrete", ColorModeDiscrete =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
@@ -55,6 +55,24 @@ System.register([], function (_export, _context) {
|
||||
return tooltips;
|
||||
}
|
||||
}, {
|
||||
key: "convertValueToTooltips",
|
||||
value: function convertValueToTooltips(values) {
|
||||
var thresholds = this.panel.color.thresholds;
|
||||
var tooltips = [];
|
||||
|
||||
for (var i = 0; i < thresholds.length; i++) {
|
||||
//for (let j = 0; j < values.length; j++) {
|
||||
if (values == thresholds[i].value) {
|
||||
tooltips.push({
|
||||
"tooltip": thresholds[i].tooltip ? thresholds[i].tooltip : values,
|
||||
"color": thresholds[i].color
|
||||
}); //}
|
||||
}
|
||||
}
|
||||
|
||||
return tooltips;
|
||||
}
|
||||
}, {
|
||||
key: "getNotMatchedValues",
|
||||
value: function getNotMatchedValues(values) {
|
||||
var notMatched = [];
|
||||
@@ -92,6 +110,23 @@ System.register([], function (_export, _context) {
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}, {
|
||||
key: "getBucketColorSingle",
|
||||
value: function getBucketColorSingle(value) {
|
||||
//let thresholds = this.panel.color.thresholds;
|
||||
if (value == null) {
|
||||
// treat as null value
|
||||
return 'rgba(0,0,0,1)'; //return this.getMatchedThreshold(null).color;
|
||||
}
|
||||
|
||||
var threshold = this.getMatchedThreshold(value);
|
||||
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
return 'rgba(0,0,0,1)';
|
||||
} else {
|
||||
return threshold.color;
|
||||
}
|
||||
} // returns color from first matched thresold in order from 0 to thresholds.length
|
||||
|
||||
}, {
|
||||
@@ -137,29 +172,53 @@ System.register([], function (_export, _context) {
|
||||
return 'rgba(0,0,0,1)';
|
||||
}
|
||||
}, {
|
||||
key: "updateCardsValuesHasColorInfo",
|
||||
value: function updateCardsValuesHasColorInfo() {
|
||||
if (!this.panelCtrl.cardsData) {
|
||||
key: "updateCardsValuesHasColorInfoSingle",
|
||||
value: function updateCardsValuesHasColorInfoSingle() {
|
||||
var _this = this;
|
||||
|
||||
if (!this.panelCtrl.bucketMatrix) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panelCtrl.cardsData.noColorDefined = false;
|
||||
var cards = this.panelCtrl.cardsData.cards;
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = false;
|
||||
this.panelCtrl.bucketMatrix.targets.map(function (target) {
|
||||
_this.panelCtrl.bucketMatrix.buckets[target].map(function (bucket) {
|
||||
bucket.noColorDefined = false;
|
||||
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
cards[i].noColorDefined = false;
|
||||
var values = cards[i].values;
|
||||
|
||||
for (var j = 0; j < values.length; j++) {
|
||||
var threshold = this.getMatchedThreshold(values[j]);
|
||||
var threshold = _this.getMatchedThreshold(bucket.value);
|
||||
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
cards[i].noColorDefined = true;
|
||||
this.panelCtrl.cardsData.noColorDefined = true;
|
||||
break;
|
||||
bucket.noColorDefined = true;
|
||||
_this.panelCtrl.bucketMatrix.noColorDefined = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "updateCardsValuesHasColorInfo",
|
||||
value: function updateCardsValuesHasColorInfo() {
|
||||
var _this2 = this;
|
||||
|
||||
if (!this.panelCtrl.bucketMatrix) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = false;
|
||||
this.panelCtrl.bucketMatrix.targets.map(function (target) {
|
||||
_this2.panelCtrl.bucketMatrix.buckets[target].map(function (bucket) {
|
||||
bucket.noColorDefined = false;
|
||||
|
||||
for (var j = 0; j < bucket.values.length; j++) {
|
||||
var threshold = _this2.getMatchedThreshold(bucket.values[j]);
|
||||
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
bucket.noColorDefined = true;
|
||||
_this2.panelCtrl.bucketMatrix.noColorDefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "getMatchedThreshold",
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+44
-7
@@ -7,30 +7,43 @@
|
||||
color: #d8d9da;
|
||||
padding: 1px; }
|
||||
|
||||
.status-heatmap-panel {
|
||||
.statusmap-panel {
|
||||
position: relative; }
|
||||
.status-heatmap-panel .axis .tick text {
|
||||
.statusmap-panel .axis .tick text {
|
||||
fill: #d8d9da;
|
||||
color: #d8d9da;
|
||||
font-size: 11px; }
|
||||
.status-heatmap-panel .axis .tick line {
|
||||
.statusmap-panel .axis .tick line {
|
||||
opacity: 0.4;
|
||||
stroke: #8e8e8e; }
|
||||
.status-heatmap-panel svg {
|
||||
.statusmap-panel svg {
|
||||
pointer-events: none; }
|
||||
.status-heatmap-panel svg rect {
|
||||
.statusmap-panel svg rect {
|
||||
pointer-events: visiblePainted; }
|
||||
|
||||
.statusmap-tooltip {
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
font-weight: 200;
|
||||
border-radius: 5px;
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
line-height: 14px;
|
||||
z-index: 9999;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
background-color: #141414;
|
||||
color: #d8d9da; }
|
||||
.statusmap-tooltip .discrete-item {
|
||||
color: #52545c;
|
||||
color: #ffffff;
|
||||
padding: 1px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 0.2em #FFF, 0 0 0.2em #FFF, 0 0 0.2em #FFF; }
|
||||
text-shadow: 0 0 0.2em #212124, 0 0 0.2em #212124, 0 0 0.2em #212124; }
|
||||
|
||||
.statusmap-tooltip-frozen {
|
||||
position: relative;
|
||||
z-index: 1; }
|
||||
|
||||
.statusmap-histogram rect {
|
||||
fill: #8e8e8e; }
|
||||
@@ -44,6 +57,30 @@
|
||||
fill: rgba(102, 102, 102, 0.4);
|
||||
stroke: rgba(102, 102, 102, 0.8); }
|
||||
|
||||
.width-c-40 {
|
||||
width: 40rem !important; }
|
||||
|
||||
.width-c-50 {
|
||||
width: 50rem !important; }
|
||||
|
||||
.width-c-60 {
|
||||
width: 60rem !important; }
|
||||
|
||||
.width-c-70 {
|
||||
width: 70rem !important; }
|
||||
|
||||
.statusmap-pagination {
|
||||
margin-top: 5px; }
|
||||
.statusmap-pagination label {
|
||||
margin-right: 0px; }
|
||||
.statusmap-pagination input {
|
||||
margin-right: 0px; }
|
||||
.statusmap-pagination .last-in-group {
|
||||
margin-right: 4px; }
|
||||
|
||||
.pagination-buttons {
|
||||
text-align: right; }
|
||||
|
||||
.status-heatmap-legend-wrapper {
|
||||
margin: 0 10px; }
|
||||
.status-heatmap-legend-wrapper svg {
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EE5CI,IAAI;EF6CjB,gBAAgB,EC1CC,OAAO;ED2CxB,KAAK,EC7CM,OAAO;ED+ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,ECvDY,OAAO;;AD2DvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC7FG,OAAO;ID8Fd,KAAK,EC9FE,OAAO;ID+Fd,SAAS,EE/FE,IAAI;EFkGjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO;EDsGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECxGM,OAAO",
|
||||
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,gBAAiB;EACf,QAAQ,EAAE,QAAQ;EAGhB,iCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,iCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,oBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,yBAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EAGjB,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,GAAG;EAChB,aAAa,EAAE,GAAG;EAClB,SAAS,EAAE,KAAK;EAChB,UAAU,EAAE,KAAK;EACjB,QAAQ,EAAE,MAAM;EAChB,WAAW,EAAE,IAAI;EACjB,OAAO,EAAE,IAAI;EAGb,WAAW,EAAE,MAAM;EACnB,SAAS,EEzDI,IAAI;EF0DjB,gBAAgB,ECvDC,OAAO;EDwDxB,KAAK,EC1DM,OAAO;ED4DlB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,uDAAuD;;AAIxE,yBAA0B;EACxB,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,CAAC;;AAGZ,yBAA0B;EACxB,IAAI,ECzEY,OAAO;;AD6EvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAGlC,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AAEA,qBAAsB;EAEpB,UAAU,EAAE,GAAG;EACf,2BAAM;IACJ,YAAY,EAAC,GAAG;EAElB,2BAAM;IACJ,YAAY,EAAE,GAAG;EAEnB,oCAAe;IACb,YAAY,EAAE,GAAG;;AAIrB,mBAAmB;EAAC,UAAU,EAAE,KAAK;;AAErC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC3IG,OAAO;ID4Id,KAAK,EC5IE,OAAO;ID6Id,SAAS,EE7IE,IAAI;EFgJjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECjJM,OAAO;EDoJrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECtJM,OAAO",
|
||||
"sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.dark.scss","../../src/css/_variables.scss"],
|
||||
"names": [],
|
||||
"file": "statusmap.dark.css"
|
||||
|
||||
Vendored
+44
-7
@@ -7,30 +7,43 @@
|
||||
color: #52545c;
|
||||
padding: 1px; }
|
||||
|
||||
.status-heatmap-panel {
|
||||
.statusmap-panel {
|
||||
position: relative; }
|
||||
.status-heatmap-panel .axis .tick text {
|
||||
.statusmap-panel .axis .tick text {
|
||||
fill: #52545c;
|
||||
color: #52545c;
|
||||
font-size: 11px; }
|
||||
.status-heatmap-panel .axis .tick line {
|
||||
.statusmap-panel .axis .tick line {
|
||||
opacity: 0.4;
|
||||
stroke: #767980; }
|
||||
.status-heatmap-panel svg {
|
||||
.statusmap-panel svg {
|
||||
pointer-events: none; }
|
||||
.status-heatmap-panel svg rect {
|
||||
.statusmap-panel svg rect {
|
||||
pointer-events: visiblePainted; }
|
||||
|
||||
.statusmap-tooltip {
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
font-weight: 200;
|
||||
border-radius: 5px;
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
line-height: 14px;
|
||||
z-index: 9999;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
background-color: #dde4ed;
|
||||
color: #52545c; }
|
||||
.statusmap-tooltip .discrete-item {
|
||||
color: #52545c;
|
||||
color: #ffffff;
|
||||
padding: 1px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 0.2em #FFF, 0 0 0.2em #FFF, 0 0 0.2em #FFF; }
|
||||
text-shadow: 0 0 0.2em #212124, 0 0 0.2em #212124, 0 0 0.2em #212124; }
|
||||
|
||||
.statusmap-tooltip-frozen {
|
||||
position: relative;
|
||||
z-index: 1; }
|
||||
|
||||
.statusmap-histogram rect {
|
||||
fill: #767980; }
|
||||
@@ -44,6 +57,30 @@
|
||||
fill: rgba(102, 102, 102, 0.4);
|
||||
stroke: rgba(102, 102, 102, 0.8); }
|
||||
|
||||
.width-c-40 {
|
||||
width: 40rem !important; }
|
||||
|
||||
.width-c-50 {
|
||||
width: 50rem !important; }
|
||||
|
||||
.width-c-60 {
|
||||
width: 60rem !important; }
|
||||
|
||||
.width-c-70 {
|
||||
width: 70rem !important; }
|
||||
|
||||
.statusmap-pagination {
|
||||
margin-top: 5px; }
|
||||
.statusmap-pagination label {
|
||||
margin-right: 0px; }
|
||||
.statusmap-pagination input {
|
||||
margin-right: 0px; }
|
||||
.statusmap-pagination .last-in-group {
|
||||
margin-right: 4px; }
|
||||
|
||||
.pagination-buttons {
|
||||
text-align: right; }
|
||||
|
||||
.status-heatmap-legend-wrapper {
|
||||
margin: 0 10px; }
|
||||
.status-heatmap-legend-wrapper svg {
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EE5CI,IAAI;EF6CjB,gBAAgB,EC1CC,OAAO;ED2CxB,KAAK,EC7CM,OAAO;ED+ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,ECvDY,OAAO;;AD2DvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC7FG,OAAO;ID8Fd,KAAK,EC9FE,OAAO;ID+Fd,SAAS,EE/FE,IAAI;EFkGjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO;EDsGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECxGM,OAAO",
|
||||
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,gBAAiB;EACf,QAAQ,EAAE,QAAQ;EAGhB,iCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,iCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,oBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,yBAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EAGjB,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,IAAI;EACb,WAAW,EAAE,GAAG;EAChB,aAAa,EAAE,GAAG;EAClB,SAAS,EAAE,KAAK;EAChB,UAAU,EAAE,KAAK;EACjB,QAAQ,EAAE,MAAM;EAChB,WAAW,EAAE,IAAI;EACjB,OAAO,EAAE,IAAI;EAGb,WAAW,EAAE,MAAM;EACnB,SAAS,EEzDI,IAAI;EF0DjB,gBAAgB,ECvDC,OAAO;EDwDxB,KAAK,EC1DM,OAAO;ED4DlB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,uDAAuD;;AAIxE,yBAA0B;EACxB,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,CAAC;;AAGZ,yBAA0B;EACxB,IAAI,ECzEY,OAAO;;AD6EvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAGlC,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AACA,WAAY;EACV,KAAK,EAAE,gBACT;;AAEA,qBAAsB;EAEpB,UAAU,EAAE,GAAG;EACf,2BAAM;IACJ,YAAY,EAAC,GAAG;EAElB,2BAAM;IACJ,YAAY,EAAE,GAAG;EAEnB,oCAAe;IACb,YAAY,EAAE,GAAG;;AAIrB,mBAAmB;EAAC,UAAU,EAAE,KAAK;;AAErC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC3IG,OAAO;ID4Id,KAAK,EC5IE,OAAO;ID6Id,SAAS,EE7IE,IAAI;EFgJjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECjJM,OAAO;EDoJrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECtJM,OAAO",
|
||||
"sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.light.scss","../../src/css/_variables.scss"],
|
||||
"names": [],
|
||||
"file": "statusmap.light.css"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=appEvents.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[],"file":"appEvents.js"}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var graphHover, graphHoverClear;
|
||||
|
||||
function fallbackToStringEvents() {
|
||||
_export("graphHover", graphHover = 'graph-hover');
|
||||
|
||||
_export("graphHoverClear", graphHoverClear = 'graph-hover-clear');
|
||||
}
|
||||
|
||||
_export("fallbackToStringEvents", fallbackToStringEvents);
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
_export("graphHover", graphHover = {
|
||||
name: 'graph-hover'
|
||||
});
|
||||
|
||||
_export("graphHoverClear", graphHoverClear = {
|
||||
name: 'graph-hover-clear'
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=events.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../src/libs/grafana/events/events.ts"],"names":["fallbackToStringEvents","graphHover","graphHoverClear","name"],"mappings":";;;;;;;AAYO,WAASA,sBAAT,GAAkC;AACrC,0BAAAC,UAAU,GAAG,aAAb;;AACA,+BAAAC,eAAe,GAAG,mBAAlB;AACH;;oCAHeF,sB;;;;;4BAHLC,U,GAAkD;AAACE,QAAAA,IAAI,EAAE;AAAP,O;;iCAClDD,e,GAAyC;AAACC,QAAAA,IAAI,EAAE;AAAP,O","sourcesContent":["import {AppEvent} from './appEvents';\n\nexport interface GraphHoverPayload {\n pos: any;\n panel: {\n id: number;\n };\n}\n\nexport var graphHover:(AppEvent<GraphHoverPayload>|string) = {name: 'graph-hover'};\nexport var graphHoverClear:(AppEvent<any>|string) = {name: 'graph-hover-clear'};\n\nexport function fallbackToStringEvents() {\n graphHover = 'graph-hover';\n graphHoverClear = 'graph-hover-clear';\n}\n"],"file":"events.js"}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["./events", "./panelEvents"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var CoreEvents, PanelEvents;
|
||||
return {
|
||||
setters: [function (_events) {
|
||||
CoreEvents = _events;
|
||||
}, function (_panelEvents) {
|
||||
PanelEvents = _panelEvents;
|
||||
}],
|
||||
execute: function () {
|
||||
_export("CoreEvents", CoreEvents);
|
||||
|
||||
_export("PanelEvents", PanelEvents);
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../src/libs/grafana/events/index.ts"],"names":["CoreEvents","PanelEvents"],"mappings":";;;;;;;;AAAYA,MAAAA,U;;AACAC,MAAAA,W;;;4BACHD,U;;6BAAYC,W","sourcesContent":["import * as CoreEvents from './events';\nimport * as PanelEvents from './panelEvents';\nexport { CoreEvents, PanelEvents }"],"file":"index.js"}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var refresh, render, dataError, dataReceived, dataSnapshotLoad, editModeInitialized;
|
||||
|
||||
function fallbackToStringEvents() {
|
||||
_export("refresh", refresh = 'refresh');
|
||||
|
||||
_export("render", render = 'render');
|
||||
|
||||
_export("dataError", dataError = 'data-error');
|
||||
|
||||
_export("dataReceived", dataReceived = 'data-received');
|
||||
|
||||
_export("dataSnapshotLoad", dataSnapshotLoad = 'data-snapshot-load');
|
||||
|
||||
_export("editModeInitialized", editModeInitialized = 'init-edit-mode');
|
||||
}
|
||||
|
||||
_export("fallbackToStringEvents", fallbackToStringEvents);
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
_export("refresh", refresh = {
|
||||
name: 'refresh'
|
||||
});
|
||||
|
||||
_export("render", render = {
|
||||
name: 'render'
|
||||
});
|
||||
|
||||
_export("dataError", dataError = {
|
||||
name: 'data-error'
|
||||
});
|
||||
|
||||
_export("dataReceived", dataReceived = {
|
||||
name: 'data-received'
|
||||
});
|
||||
|
||||
_export("dataSnapshotLoad", dataSnapshotLoad = {
|
||||
name: 'data-snapshot-load'
|
||||
});
|
||||
|
||||
_export("editModeInitialized", editModeInitialized = {
|
||||
name: 'init-edit-mode'
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=panelEvents.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../src/libs/grafana/events/panelEvents.ts"],"names":["fallbackToStringEvents","refresh","render","dataError","dataReceived","dataSnapshotLoad","editModeInitialized","name"],"mappings":";;;;;;;AAqBO,WAASA,sBAAT,GAAkC;AACrC,uBAAAC,OAAO,GAAG,SAAV;;AACA,sBAAAC,MAAM,GAAG,QAAT;;AACA,yBAAAC,SAAS,GAAG,YAAZ;;AACA,4BAAAC,YAAY,GAAG,eAAf;;AACA,gCAAAC,gBAAgB,GAAG,oBAAnB;;AACA,mCAAAC,mBAAmB,GAAG,gBAAtB;AACH;;oCAPeN,sB;;;;;yBAPLC,O,GAAuC;AAACM,QAAAA,IAAI,EAAE;AAAP,O;;wBACvCL,M,GAAgC;AAACK,QAAAA,IAAI,EAAE;AAAP,O;;2BAChCJ,S,GAA8C;AAACI,QAAAA,IAAI,EAAE;AAAP,O;;8BAC9CH,Y,GAAwC;AAACG,QAAAA,IAAI,EAAE;AAAP,O;;kCACxCF,gB,GAA4C;AAACE,QAAAA,IAAI,EAAE;AAAP,O;;qCAC5CD,mB,GAAmD;AAACC,QAAAA,IAAI,EAAE;AAAP,O","sourcesContent":["import { AppEvent } from './appEvents';\n\nexport interface DataQueryError {\n data?: {\n message?: string;\n error?: string;\n };\n message?: string;\n status?: string;\n statusText?: string;\n refId?: string;\n cancelled?: boolean;\n}\n\nexport var refresh:(AppEvent<undefined>|string) = {name: 'refresh'};\nexport var render:(AppEvent<any>|string) = {name: 'render'};\nexport var dataError:(AppEvent<DataQueryError>|string) = {name: 'data-error'};\nexport var dataReceived:(AppEvent<any[]>|string) = {name: 'data-received'};\nexport var dataSnapshotLoad:(AppEvent<any[]>|string) = {name: 'data-snapshot-load'};\nexport var editModeInitialized:(AppEvent<undefined>|string) = {name: 'init-edit-mode'};\n\nexport function fallbackToStringEvents() {\n refresh = 'refresh';\n render = 'render';\n dataError = 'data-error';\n dataReceived = 'data-received';\n dataSnapshotLoad = 'data-snapshot-load';\n editModeInitialized = 'init-edit-mode';\n}\n"],"file":"panelEvents.js"}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
1. @grafana/data is not supported in grafana 5.x and 6.2. The workaround is
|
||||
the module `./libs/grafana/events` that include copy-pasted events
|
||||
from @grafana/data and app/core from grafana sources.
|
||||
|
||||
2. Newly Grafana versions flood Console with
|
||||
"Usage strings as events is deprecated ..." messages.
|
||||
This module has a function to detect if Grafana’s Emitter support AppEvent style event ids.
|
||||
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
// Old Grafana releases use strings as event ids and
|
||||
// new event ids in form of object {name: "event-id"} are not
|
||||
// supported properly: first defined listener will receive
|
||||
// all emitted events despite of the value in 'name' field.
|
||||
//
|
||||
// This method detects this behaviour and return true
|
||||
// only for new Grafana versions.
|
||||
function hasAppEventCompatibleEmitter(emitter) {
|
||||
var receiveEvents = 0;
|
||||
var eventId = {
|
||||
name: "non-existed-event-id"
|
||||
};
|
||||
var eventId2 = {
|
||||
name: "non-existed-event-id-2"
|
||||
};
|
||||
emitter.on(eventId, function () {
|
||||
receiveEvents++;
|
||||
});
|
||||
emitter.emit(eventId);
|
||||
emitter.emit(eventId2);
|
||||
emitter.removeAllListeners(eventId); // New Grafana versions should receive one event.
|
||||
|
||||
return receiveEvents == 1;
|
||||
}
|
||||
|
||||
_export("hasAppEventCompatibleEmitter", hasAppEventCompatibleEmitter);
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=funcs.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/libs/polygrafill/funcs.ts"],"names":["hasAppEventCompatibleEmitter","emitter","receiveEvents","eventId","name","eventId2","on","emit","removeAllListeners"],"mappings":";;;;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,WAASA,4BAAT,CAAsCC,OAAtC,EAAgE;AACnE,QAAIC,aAAa,GAAG,CAApB;AACA,QAAIC,OAAY,GAAG;AAACC,MAAAA,IAAI,EAAE;AAAP,KAAnB;AACA,QAAIC,QAAa,GAAG;AAACD,MAAAA,IAAI,EAAE;AAAP,KAApB;AACAH,IAAAA,OAAO,CAACK,EAAR,CAAWH,OAAX,EAAoB,YAAU;AAC1BD,MAAAA,aAAa;AAChB,KAFD;AAGAD,IAAAA,OAAO,CAACM,IAAR,CAAaJ,OAAb;AACAF,IAAAA,OAAO,CAACM,IAAR,CAAaF,QAAb;AACAJ,IAAAA,OAAO,CAACO,kBAAR,CAA2BL,OAA3B,EATmE,CAWnE;;AACA,WAAOD,aAAa,IAAI,CAAxB;AACH;;0CAbeF,4B","sourcesContent":["import { Emitter } from 'app/core/utils/emitter';\n\n// Old Grafana releases use strings as event ids and\n// new event ids in form of object {name: \"event-id\"} are not\n// supported properly: first defined listener will receive\n// all emitted events despite of the value in 'name' field.\n//\n// This method detects this behaviour and return true\n// only for new Grafana versions.\nexport function hasAppEventCompatibleEmitter(emitter: Emitter):boolean {\n let receiveEvents = 0;\n let eventId: any = {name: \"non-existed-event-id\"};\n let eventId2: any = {name: \"non-existed-event-id-2\"};\n emitter.on(eventId, function(){\n receiveEvents++;\n });\n emitter.emit(eventId);\n emitter.emit(eventId2);\n emitter.removeAllListeners(eventId);\n\n // New Grafana versions should receive one event.\n return receiveEvents == 1;\n}\n"],"file":"funcs.js"}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["./funcs"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var Polygrafill;
|
||||
return {
|
||||
setters: [function (_funcs) {
|
||||
Polygrafill = _funcs;
|
||||
}],
|
||||
execute: function () {
|
||||
_export("Polygrafill", Polygrafill);
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../src/libs/polygrafill/index.ts"],"names":["Polygrafill"],"mappings":";;;;;;;;AAAYA,MAAAA,W;;;6BACHA,W","sourcesContent":["import * as Polygrafill from './funcs';\nexport { Polygrafill };"],"file":"index.js"}
|
||||
Vendored
+26
-4
@@ -1,15 +1,37 @@
|
||||
<div class="status-heatmap-wrapper">
|
||||
<div class="status-heatmap-canvas-wrapper">
|
||||
|
||||
<div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined">
|
||||
<div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined || ctrl.noDatapoints">
|
||||
<span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noDatapoints" bs-tooltip="'{{ctrl.dataWarnings.noDatapoints.tip}}'">{{ctrl.dataWarnings.noDatapoints.title}}</span>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
<div class="statusmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-legend-wrapper" ng-if="ctrl.panel.legend.show">
|
||||
<status-heatmap-legend></status-heatmap-legend>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="statusmap-pagination gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-disabled="!pager.hasPrev()" ng-click="ctrl.onPrevPage()">
|
||||
<i class="fa fa-arrow-left"></i> Previous
|
||||
</button>
|
||||
<label class="gf-form-label">{{pager.currentPage+1}}/{{pager.pages()}}</label>
|
||||
<button class="btn btn-inverse last-in-group" ng-disabled="!pager.hasNext()" ng-click="ctrl.onNextPage()">
|
||||
Next <i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-3 last-in-group" placeholder="12" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show per page.'" ng-model="ctrl.pageSizeViewer" ng-change="ctrl.onChangePageSize()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
<label class="gf-form-label">Show {{pager.pageStartRow()}} to {{pager.pageEndRow()}} of {{pager.totalRows()}} rows</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
Vendored
+419
-235
@@ -1,9 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/sdk", "./statusmap_data", "./rendering", "./options_editor", "./color_mode_discrete"], function (_export, _context) {
|
||||
System.register(["lodash", "./color_legend", "./options_editor", "./tooltip_editor", "./panel_config_migration", "app/core/utils/kbn", "app/plugins/sdk", "./libs/grafana/events/index", "./statusmap_data", "./rendering", "./libs/polygrafill/index", "./color_mode_discrete"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var _, kbn, loadPluginCss, MetricsPanelCtrl, Card, rendering, statusHeatmapOptionsEditor, ColorModeDiscrete, CANVAS, SVG, VALUE_INDEX, TIME_INDEX, renderer, colorSchemes, colorModes, opacityScales, StatusHeatmapCtrl;
|
||||
var _, optionsEditorCtrl, tooltipEditorCtrl, migratePanelConfig, kbn, loadPluginCss, MetricsPanelCtrl, CoreEvents, PanelEvents, Bucket, BucketMatrix, BucketMatrixPager, rendering, Polygrafill, ColorModeDiscrete, VALUE_INDEX, TIME_INDEX, colorSchemes, colorModes, opacityScales, renderComplete, StatusHeatmapCtrl;
|
||||
|
||||
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
@@ -32,26 +32,34 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
return {
|
||||
setters: [function (_lodash) {
|
||||
_ = _lodash.default;
|
||||
}, function (_color_legend) {}, function (_appCoreUtilsKbn) {
|
||||
}, function (_color_legend) {}, function (_options_editor) {
|
||||
optionsEditorCtrl = _options_editor.optionsEditorCtrl;
|
||||
}, function (_tooltip_editor) {
|
||||
tooltipEditorCtrl = _tooltip_editor.tooltipEditorCtrl;
|
||||
}, function (_panel_config_migration) {
|
||||
migratePanelConfig = _panel_config_migration.migratePanelConfig;
|
||||
}, function (_appCoreUtilsKbn) {
|
||||
kbn = _appCoreUtilsKbn.default;
|
||||
}, function (_appPluginsSdk) {
|
||||
loadPluginCss = _appPluginsSdk.loadPluginCss;
|
||||
MetricsPanelCtrl = _appPluginsSdk.MetricsPanelCtrl;
|
||||
}, function (_libsGrafanaEventsIndex) {
|
||||
CoreEvents = _libsGrafanaEventsIndex.CoreEvents;
|
||||
PanelEvents = _libsGrafanaEventsIndex.PanelEvents;
|
||||
}, function (_statusmap_data) {
|
||||
Card = _statusmap_data.Card;
|
||||
Bucket = _statusmap_data.Bucket;
|
||||
BucketMatrix = _statusmap_data.BucketMatrix;
|
||||
BucketMatrixPager = _statusmap_data.BucketMatrixPager;
|
||||
}, function (_rendering) {
|
||||
rendering = _rendering.default;
|
||||
}, function (_options_editor) {
|
||||
statusHeatmapOptionsEditor = _options_editor.statusHeatmapOptionsEditor;
|
||||
}, function (_libsPolygrafillIndex) {
|
||||
Polygrafill = _libsPolygrafillIndex.Polygrafill;
|
||||
}, function (_color_mode_discrete) {
|
||||
ColorModeDiscrete = _color_mode_discrete.ColorModeDiscrete;
|
||||
}],
|
||||
execute: function () {
|
||||
CANVAS = 'CANVAS';
|
||||
SVG = 'SVG';
|
||||
VALUE_INDEX = 0;
|
||||
TIME_INDEX = 1;
|
||||
renderer = CANVAS;
|
||||
colorSchemes = [// Diverging
|
||||
{
|
||||
name: 'Spectral',
|
||||
@@ -147,6 +155,11 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
light: 'plugins/flant-statusmap-panel/css/statusmap.light.css'
|
||||
});
|
||||
|
||||
_export("renderComplete", renderComplete = {
|
||||
name: 'statusmap-render-complete'
|
||||
}); // eventFactory('statusmap-render-complete');
|
||||
|
||||
|
||||
_export("PanelCtrl", _export("StatusHeatmapCtrl", StatusHeatmapCtrl =
|
||||
/*#__PURE__*/
|
||||
function (_MetricsPanelCtrl) {
|
||||
@@ -154,6 +167,8 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
|
||||
_inherits(StatusHeatmapCtrl, _MetricsPanelCtrl);
|
||||
|
||||
// TODO remove this transient variable: use ng-model-options="{ getterSetter: true }"
|
||||
|
||||
/** @ngInject */
|
||||
function StatusHeatmapCtrl($scope, $injector, annotationsSrv) {
|
||||
var _this;
|
||||
@@ -163,6 +178,16 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
_this = _possibleConstructorReturn(this, _getPrototypeOf(StatusHeatmapCtrl).call(this, $scope, $injector));
|
||||
_this.annotationsSrv = annotationsSrv;
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "data", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "bucketMatrix", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "bucketMatrixPager", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "graph", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "discreteHelper", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "opacityScales", []);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "colorModes", []);
|
||||
@@ -171,24 +196,22 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "unitFormats", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "data", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "cardsData", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "graph", void 0);
|
||||
_defineProperty(_assertThisInitialized(_this), "dataWarnings", {});
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "multipleValues", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "noColorDefined", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "discreteHelper", void 0);
|
||||
_defineProperty(_assertThisInitialized(_this), "noDatapoints", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "dataWarnings", void 0);
|
||||
_defineProperty(_assertThisInitialized(_this), "discreteExtraSeries", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "annotations", []);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "annotationsPromise", void 0);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "pageSizeViewer", 15);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "panelDefaults", {
|
||||
// datasource name, null = default datasource
|
||||
datasource: null,
|
||||
@@ -212,35 +235,56 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
cardRound: null
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
showWeekends: true,
|
||||
minBucketWidthToShowWeekends: 4,
|
||||
showCrosshair: true,
|
||||
labelFormat: '%a %m/%d'
|
||||
show: true
|
||||
},
|
||||
yAxis: {
|
||||
show: true,
|
||||
showCrosshair: false
|
||||
minWidth: -1,
|
||||
maxWidth: -1
|
||||
},
|
||||
tooltip: {
|
||||
show: true
|
||||
show: true,
|
||||
freezeOnClick: true,
|
||||
showItems: false,
|
||||
items: [] // see tooltip_editor.ts
|
||||
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
data: {
|
||||
unitFormat: 'short',
|
||||
decimals: null
|
||||
},
|
||||
// how null points should be handled
|
||||
nullPointMode: 'as empty',
|
||||
yAxisSort: 'metrics',
|
||||
highlightCards: true,
|
||||
useMax: true
|
||||
useMax: true,
|
||||
seriesFilterIndex: -1,
|
||||
// Pagination options
|
||||
usingPagination: false,
|
||||
pageSize: 15
|
||||
});
|
||||
|
||||
if (!Polygrafill.hasAppEventCompatibleEmitter(_this.events)) {
|
||||
CoreEvents.fallbackToStringEvents();
|
||||
PanelEvents.fallbackToStringEvents();
|
||||
|
||||
_export("renderComplete", renderComplete = renderComplete.name);
|
||||
}
|
||||
|
||||
migratePanelConfig(_this.panel);
|
||||
|
||||
_.defaultsDeep(_this.panel, _this.panelDefaults);
|
||||
|
||||
_this.bucketMatrix = new BucketMatrix(); // Create pager for bucketMatrix and restore page size.
|
||||
|
||||
_this.bucketMatrixPager = new BucketMatrixPager();
|
||||
|
||||
_this.bucketMatrixPager.setEnable(_this.panel.usingPagination);
|
||||
|
||||
_this.bucketMatrixPager.setDefaultPageSize(_this.panel.pageSize);
|
||||
|
||||
_this.bucketMatrixPager.setPageSize(_this.panel.pageSize);
|
||||
|
||||
$scope.pager = _this.bucketMatrixPager;
|
||||
_this.opacityScales = opacityScales;
|
||||
_this.colorModes = colorModes;
|
||||
_this.colorSchemes = colorSchemes; // default graph width for discrete card width calculation
|
||||
@@ -250,34 +294,40 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
};
|
||||
_this.multipleValues = false;
|
||||
_this.noColorDefined = false;
|
||||
_this.discreteHelper = new ColorModeDiscrete($scope);
|
||||
_this.discreteExtraSeries = new ColorModeDiscrete($scope);
|
||||
_this.dataWarnings = {
|
||||
"noColorDefined": {
|
||||
noColorDefined: {
|
||||
title: 'Data has value with undefined color',
|
||||
tip: 'Check metric values, color values or define a new color'
|
||||
},
|
||||
"multipleValues": {
|
||||
multipleValues: {
|
||||
title: 'Data has multiple values for one target',
|
||||
tip: 'Change targets definitions or set "use max value"'
|
||||
},
|
||||
noDatapoints: {
|
||||
title: 'No data points',
|
||||
tip: 'No datapoints returned from data query'
|
||||
}
|
||||
};
|
||||
_this.annotations = [];
|
||||
_this.annotationsSrv = annotationsSrv;
|
||||
|
||||
_this.events.on('render', _this.onRender.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(PanelEvents.render, _this.onRender.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.events.on('data-received', _this.onDataReceived.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(PanelEvents.dataReceived, _this.onDataReceived.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.events.on('data-error', _this.onDataError.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(PanelEvents.dataError, _this.onDataError.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.events.on('data-snapshot-load', _this.onDataReceived.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(PanelEvents.dataSnapshotLoad, _this.onDataReceived.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.events.on('init-edit-mode', _this.onInitEditMode.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(PanelEvents.editModeInitialized, _this.onInitEditMode.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.events.on('refresh', _this.postRefresh.bind(_assertThisInitialized(_this))); // custom event from rendering.js
|
||||
_this.events.on(PanelEvents.refresh, _this.postRefresh.bind(_assertThisInitialized(_this))); // custom event from rendering.js
|
||||
|
||||
|
||||
_this.events.on('render-complete', _this.onRenderComplete.bind(_assertThisInitialized(_this)));
|
||||
_this.events.on(renderComplete, _this.onRenderComplete.bind(_assertThisInitialized(_this)));
|
||||
|
||||
_this.onCardColorChange = _this.onCardColorChange.bind(_assertThisInitialized(_this));
|
||||
return _this;
|
||||
}
|
||||
|
||||
@@ -287,18 +337,73 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
this.graph.chartWidth = data.chartWidth;
|
||||
this.renderingCompleted();
|
||||
}
|
||||
}, {
|
||||
key: "changeDefaultPaginationSize",
|
||||
value: function changeDefaultPaginationSize(defaultPageSize) {
|
||||
this.bucketMatrixPager.setDefaultPageSize(defaultPageSize);
|
||||
this.bucketMatrixPager.setPageSize(defaultPageSize);
|
||||
this.pageSizeViewer = defaultPageSize;
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
}, {
|
||||
key: "onChangePageSize",
|
||||
value: function onChangePageSize() {
|
||||
if (this.pageSizeViewer <= 0) {
|
||||
this.pageSizeViewer = this.bucketMatrixPager.defaultPageSize;
|
||||
}
|
||||
|
||||
this.bucketMatrixPager.setPageSize(this.pageSizeViewer);
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
}, {
|
||||
key: "onPrevPage",
|
||||
value: function onPrevPage() {
|
||||
this.bucketMatrixPager.switchToPrev();
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onNextPage",
|
||||
value: function onNextPage() {
|
||||
this.bucketMatrixPager.switchToNext();
|
||||
this.render();
|
||||
} // getChartWidth returns an approximation of chart canvas width or
|
||||
// a saved value calculated during a render.
|
||||
|
||||
}, {
|
||||
key: "getChartWidth",
|
||||
value: function getChartWidth() {
|
||||
if (this.graph.chartWidth > 0) {
|
||||
return this.graph.chartWidth;
|
||||
}
|
||||
|
||||
var wndWidth = $(window).width(); // gripPos.w is a width in grid's measurements. Grid size in Grafana is 24.
|
||||
|
||||
var panelWidthFactor = this.panel.gridPos.w / 24;
|
||||
var panelWidth = Math.ceil(wndWidth * panelWidthFactor); // approximate chartWidth because y axis ticks not rendered yet on first data receive.
|
||||
var panelWidth = Math.ceil(wndWidth * panelWidthFactor); // approximate width of the chart draw canvas:
|
||||
// - y axis ticks are not rendered yet on first data receive,
|
||||
// so choose 200 as a decent value for y legend width
|
||||
// - chartWidth can not be lower than the half of the panel width.
|
||||
|
||||
var chartWidth = _.max([panelWidth - 200, panelWidth / 2]);
|
||||
|
||||
return chartWidth;
|
||||
} // override calculateInterval for discrete color mode
|
||||
} // Quick workaround for 6.7 and 7.0+. There is no call to
|
||||
// calculateInterval in updateTimeRange in those versions.
|
||||
// TODO ts type has no argument for this method.
|
||||
|
||||
}, {
|
||||
key: "updateTimeRange",
|
||||
value: function updateTimeRange(datasource) {
|
||||
var ret = _get(_getPrototypeOf(StatusHeatmapCtrl.prototype), "updateTimeRange", this).call(this, datasource);
|
||||
|
||||
this.calculateInterval();
|
||||
return ret;
|
||||
} // calculateInterval is called on 'refresh' to calculate an interval
|
||||
// for datasource.
|
||||
// It is override of calculateInterval from MetricsPanelCtrl.
|
||||
|
||||
}, {
|
||||
key: "calculateInterval",
|
||||
@@ -310,7 +415,7 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
var intervalMs;
|
||||
var rangeMs = this.range.to.valueOf() - this.range.from.valueOf(); // this is minimal interval! kbn.round_interval will lower it.
|
||||
|
||||
intervalMs = this.discreteHelper.roundIntervalCeil(rangeMs / maxCardsCount); // Calculate low limit of interval
|
||||
intervalMs = this.discreteExtraSeries.roundIntervalCeil(rangeMs / maxCardsCount); // Calculate low limit of interval
|
||||
|
||||
var lowLimitMs = 1; // 1 millisecond default low limit
|
||||
|
||||
@@ -336,7 +441,9 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
|
||||
var interval = kbn.secondsToHms(intervalMs / 1000);
|
||||
this.intervalMs = intervalMs;
|
||||
this.interval = interval;
|
||||
this.interval = interval; // Get final buckets count after interval is adjusted
|
||||
// TODO is it needed?
|
||||
//this.xBucketsCount = Math.floor(rangeMs / intervalMs);
|
||||
}
|
||||
}, {
|
||||
key: "issueQueries",
|
||||
@@ -371,7 +478,7 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
key: "issueQueriesWithInterval",
|
||||
value: function issueQueriesWithInterval(datasource, interval) {
|
||||
var origInterval = this.panel.interval;
|
||||
this.panel.interval = this.interval;
|
||||
this.panel.interval = interval;
|
||||
|
||||
var res = _get(_getPrototypeOf(StatusHeatmapCtrl.prototype), "issueQueries", this).call(this, datasource);
|
||||
|
||||
@@ -383,58 +490,89 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
value: function onDataReceived(dataList) {
|
||||
var _this3 = this;
|
||||
|
||||
this.data = dataList;
|
||||
this.cardsData = this.convertToCards(this.data);
|
||||
console.log("OnDataReceived");
|
||||
this.annotationsPromise.then(function (result) {
|
||||
_this3.loading = false; //this.alertState = result.alertState;
|
||||
this.data = dataList; // Quick workaround for 7.0+. There is no call to
|
||||
// calculateInterval when enter Edit mode.
|
||||
|
||||
if (result.annotations && result.annotations.length > 0) {
|
||||
_this3.annotations = result.annotations;
|
||||
} else {
|
||||
if (!this.intervalMs) {
|
||||
this.calculateInterval();
|
||||
}
|
||||
|
||||
var newBucketMatrix = this.convertDataToBuckets(dataList, this.range.from.valueOf(), this.range.to.valueOf(), this.intervalMs, true);
|
||||
this.bucketMatrix = newBucketMatrix;
|
||||
this.bucketMatrixPager.bucketMatrix = newBucketMatrix;
|
||||
|
||||
if (newBucketMatrix.targets.length !== this.bucketMatrix.targets.length) {
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
}
|
||||
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
|
||||
if (this.annotationsPromise) {
|
||||
this.annotationsPromise.then(function (result) {
|
||||
_this3.loading = false; //this.alertState = result.alertState;
|
||||
|
||||
if (result.annotations && result.annotations.length > 0) {
|
||||
_this3.annotations = result.annotations;
|
||||
} else {
|
||||
_this3.annotations = [];
|
||||
}
|
||||
|
||||
_this3.render();
|
||||
}, function () {
|
||||
_this3.loading = false;
|
||||
_this3.annotations = [];
|
||||
}
|
||||
|
||||
console.log("annotationsPromise result " + _this3.annotations.length + " annotations");
|
||||
|
||||
_this3.render();
|
||||
}, function () {
|
||||
_this3.loading = false;
|
||||
_this3.annotations = [];
|
||||
console.log("annotationsPromise onrejected");
|
||||
|
||||
_this3.render();
|
||||
}); //this.render();
|
||||
_this3.render();
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.annotations = [];
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "onInitEditMode",
|
||||
value: function onInitEditMode() {
|
||||
this.addEditorTab('Options', statusHeatmapOptionsEditor, 2);
|
||||
this.addEditorTab('Options', optionsEditorCtrl, 2);
|
||||
this.addEditorTab('Tooltip', tooltipEditorCtrl, 3);
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
} // onRender will be called before StatusmapRenderer.onRender.
|
||||
// Decide if warning should be displayed over cards.
|
||||
|
||||
}, {
|
||||
key: "onRender",
|
||||
value: function onRender() {
|
||||
if (!this.range || !this.data) {
|
||||
this.noDatapoints = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.multipleValues = false;
|
||||
|
||||
if (!this.panel.useMax) {
|
||||
if (this.cardsData) {
|
||||
this.multipleValues = this.cardsData.multipleValues;
|
||||
if (this.bucketMatrix) {
|
||||
this.multipleValues = this.bucketMatrix.multipleValues;
|
||||
}
|
||||
}
|
||||
|
||||
this.noColorDefined = false;
|
||||
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
this.discreteHelper.updateCardsValuesHasColorInfo();
|
||||
|
||||
if (this.cardsData) {
|
||||
this.noColorDefined = this.cardsData.noColorDefined;
|
||||
if (this.panel.seriesFilterIndex == -1) {
|
||||
this.discreteExtraSeries.updateCardsValuesHasColorInfo();
|
||||
} else {
|
||||
this.discreteExtraSeries.updateCardsValuesHasColorInfoSingle();
|
||||
}
|
||||
|
||||
if (this.bucketMatrix) {
|
||||
this.noColorDefined = this.bucketMatrix.noColorDefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.noDatapoints = false;
|
||||
|
||||
if (this.bucketMatrix) {
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
@@ -455,182 +593,228 @@ System.register(["lodash", "./color_legend", "app/core/utils/kbn", "app/plugins/
|
||||
value: function postRefresh() {
|
||||
this.noColorDefined = false;
|
||||
}
|
||||
}, {
|
||||
key: "onEditorAddThreshold",
|
||||
value: function onEditorAddThreshold() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: this.panel.defaultColor
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onEditorRemoveThreshold",
|
||||
value: function onEditorRemoveThreshold(index) {
|
||||
this.panel.color.thresholds.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onEditorRemoveThresholds",
|
||||
value: function onEditorRemoveThresholds() {
|
||||
this.panel.color.thresholds = [];
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onEditorAddThreeLights",
|
||||
value: function onEditorAddThreeLights() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: "red",
|
||||
value: 2,
|
||||
tooltip: "error"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "yellow",
|
||||
value: 1,
|
||||
tooltip: "warning"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "green",
|
||||
value: 0,
|
||||
tooltip: "ok"
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
/* https://ethanschoonover.com/solarized/ */
|
||||
|
||||
}, {
|
||||
key: "onEditorAddSolarized",
|
||||
value: function onEditorAddSolarized() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#b58900",
|
||||
value: 0,
|
||||
tooltip: "yellow"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#cb4b16",
|
||||
value: 1,
|
||||
tooltip: "orange"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#dc322f",
|
||||
value: 2,
|
||||
tooltip: "red"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#d33682",
|
||||
value: 3,
|
||||
tooltip: "magenta"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#6c71c4",
|
||||
value: 4,
|
||||
tooltip: "violet"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#268bd2",
|
||||
value: 5,
|
||||
tooltip: "blue"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#2aa198",
|
||||
value: 6,
|
||||
tooltip: "cyan"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#859900",
|
||||
value: 7,
|
||||
tooltip: "green"
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "link",
|
||||
value: function link(scope, elem, attrs, ctrl) {
|
||||
rendering(scope, elem, attrs, ctrl);
|
||||
} // group values into buckets by target
|
||||
} // Compatible with Grafana 7.0 overrides feature.
|
||||
|
||||
}, {
|
||||
key: "convertToCards",
|
||||
value: function convertToCards(data) {
|
||||
var cardsData = {
|
||||
cards: [],
|
||||
xBucketSize: 0,
|
||||
yBucketSize: 0,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
multipleValues: false,
|
||||
noColorDefined: false,
|
||||
targets: [],
|
||||
// array of available unique targets
|
||||
targetIndex: {} // indices in data array for each of available unique targets
|
||||
key: "retrieveTimeVar",
|
||||
value: function retrieveTimeVar() {
|
||||
var time = this.timeSrv.timeRangeForUrl();
|
||||
return 'from=' + time.from + '&to=' + time.to;
|
||||
} // convertToBuckets groups values in data into buckets by target and timestamp.
|
||||
//
|
||||
// data is a result from datasource. It is an array of timeseries and tables:
|
||||
|
||||
};
|
||||
/* [
|
||||
// timeseries
|
||||
{
|
||||
target: "query alias",
|
||||
refId: "A",
|
||||
datapoints: [
|
||||
[0, 1582681239911],
|
||||
[3, 158....],
|
||||
...
|
||||
],
|
||||
tags?:{key: value,...}
|
||||
},
|
||||
// table
|
||||
{
|
||||
name: "table name",
|
||||
refId: "B",
|
||||
columns: [
|
||||
{text: "id"},
|
||||
{text: "info"},
|
||||
...
|
||||
],
|
||||
rows: [
|
||||
[1, "123"],
|
||||
[2, "44411"],
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
to and from — a time range of the panel.
|
||||
intervalMs — a calculated interval. It is used to split a time range.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "convertDataToBuckets",
|
||||
value: function convertDataToBuckets(data, from, to, intervalMs, mostRecentBucket) {
|
||||
var _this4 = this;
|
||||
|
||||
var bucketMatrix = new BucketMatrix();
|
||||
bucketMatrix.rangeMs = to - from;
|
||||
bucketMatrix.intervalMs = intervalMs;
|
||||
|
||||
if (!data || data.length == 0) {
|
||||
return cardsData;
|
||||
} // Collect uniq timestamps from data and spread over targets and timestamps
|
||||
// collect uniq targets and their indices
|
||||
|
||||
|
||||
_.map(data, function (d, i) {
|
||||
cardsData.targetIndex[d.target] = _.concat(_.toArray(cardsData.targetIndex[d.target]), i);
|
||||
}); // TODO add some logic for targets heirarchy
|
||||
|
||||
|
||||
cardsData.targets = _.keys(cardsData.targetIndex);
|
||||
cardsData.yBucketSize = cardsData.targets.length; // Maximum number of buckets over x axis
|
||||
|
||||
cardsData.xBucketSize = _.max(_.map(data, function (d) {
|
||||
return d.datapoints.length;
|
||||
})); // Collect all values for each bucket from datapoints with similar target.
|
||||
// TODO aggregate values into buckets over datapoint[TIME_INDEX] not over datapoint index (j).
|
||||
|
||||
for (var i = 0; i < cardsData.targets.length; i++) {
|
||||
var target = cardsData.targets[i];
|
||||
|
||||
for (var j = 0; j < cardsData.xBucketSize; j++) {
|
||||
var card = new Card();
|
||||
card.id = i * cardsData.xBucketSize + j;
|
||||
card.values = [];
|
||||
card.y = target;
|
||||
card.x = -1; // collect values from all timeseries with target
|
||||
|
||||
for (var si = 0; si < cardsData.targetIndex[target].length; si++) {
|
||||
var s = data[cardsData.targetIndex[target][si]];
|
||||
|
||||
if (s.datapoints.length <= j) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var datapoint = s.datapoints[j];
|
||||
|
||||
if (card.values.length === 0) {
|
||||
card.x = datapoint[TIME_INDEX];
|
||||
}
|
||||
|
||||
card.values.push(datapoint[VALUE_INDEX]);
|
||||
}
|
||||
|
||||
card.minValue = _.min(card.values);
|
||||
card.maxValue = _.max(card.values);
|
||||
|
||||
if (card.values.length > 1) {
|
||||
cardsData.multipleValues = true;
|
||||
card.multipleValues = true;
|
||||
card.value = card.maxValue; // max value by default
|
||||
} else {
|
||||
card.value = card.maxValue; // max value by default
|
||||
}
|
||||
|
||||
if (cardsData.maxValue < card.maxValue) cardsData.maxValue = card.maxValue;
|
||||
if (cardsData.minValue > card.minValue) cardsData.minValue = card.minValue;
|
||||
|
||||
if (card.x != -1) {
|
||||
cardsData.cards.push(card);
|
||||
}
|
||||
}
|
||||
// Mimic heatmap and graph 'no data' labels.
|
||||
bucketMatrix.targets = ["1.0", "0.0", "-1.0"];
|
||||
bucketMatrix.buckets["1.0"] = [];
|
||||
bucketMatrix.buckets["0.0"] = [];
|
||||
bucketMatrix.buckets["-1.0"] = [];
|
||||
bucketMatrix.xBucketSize = 42;
|
||||
bucketMatrix.noDatapoints = true;
|
||||
return bucketMatrix;
|
||||
}
|
||||
|
||||
return cardsData;
|
||||
var targetIndex = {}; // Group indicies of elements in data by target (y label).
|
||||
// lodash version:
|
||||
//_.map(data, (d, i) => {
|
||||
// targetIndex[d.target] = _.concat(_.toArray(targetIndex[d.target]), i);
|
||||
//});
|
||||
|
||||
data.map(function (queryResult, i) {
|
||||
var yLabel = queryResult.target;
|
||||
|
||||
if (!targetIndex.hasOwnProperty(yLabel)) {
|
||||
targetIndex[yLabel] = [];
|
||||
}
|
||||
|
||||
targetIndex[yLabel].push(i);
|
||||
});
|
||||
|
||||
var targetKeys = _.keys(targetIndex); //console.log ("targetIndex: ", targetIndex, "targetKeys: ", targetKeys);
|
||||
|
||||
|
||||
var targetTimestampRanges = {}; // Collect all timestamps for each target.
|
||||
// Make map timestamp => [from, to]. from == previous ts, to == ts from datapoint.
|
||||
|
||||
targetKeys.map(function (target) {
|
||||
var targetTimestamps = [];
|
||||
|
||||
for (var si = 0; si < targetIndex[target].length; si++) {
|
||||
var s = data[targetIndex[target][si]];
|
||||
|
||||
_.map(s.datapoints, function (datapoint, idx) {
|
||||
targetTimestamps.push(datapoint[TIME_INDEX] - from);
|
||||
});
|
||||
} //console.log("timestamps['"+target+"'] = ", targetTimestamps);
|
||||
|
||||
|
||||
targetTimestamps = _.uniq(targetTimestamps); //console.log("uniq timestamps['"+target+"'] = ", targetTimestamps);
|
||||
|
||||
targetTimestampRanges[target] = [];
|
||||
|
||||
for (var i = targetTimestamps.length - 1; i >= 0; i--) {
|
||||
var tsTo = targetTimestamps[i];
|
||||
var tsFrom = 0;
|
||||
|
||||
if (tsTo < 0) {
|
||||
tsFrom = tsTo - intervalMs;
|
||||
} else {
|
||||
if (i - 1 >= 0) {
|
||||
// Set from to previous timestamp + 1ms;
|
||||
tsFrom = targetTimestamps[i - 1] + 1; // tfTo - tfFrom should not be more than intervalMs
|
||||
|
||||
var minFrom = tsTo - intervalMs;
|
||||
|
||||
if (tsFrom < minFrom) {
|
||||
tsFrom = minFrom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetTimestampRanges[target][tsTo] = [tsFrom, tsTo];
|
||||
}
|
||||
}); // console.log ("targetTimestampRanges: ", targetTimestampRanges);
|
||||
// Create empty buckets using intervalMs to calculate ranges.
|
||||
// If mostRecentBucket is set, create a bucket with a range "to":"to"
|
||||
// to store most recent values.
|
||||
|
||||
targetKeys.map(function (target) {
|
||||
var targetEmptyBuckets = [];
|
||||
var lastTs = to - from;
|
||||
|
||||
if (mostRecentBucket) {
|
||||
var topBucket = new Bucket();
|
||||
topBucket.yLabel = target;
|
||||
topBucket.relTo = lastTs;
|
||||
topBucket.relFrom = lastTs;
|
||||
topBucket.values = [];
|
||||
topBucket.mostRecent = true;
|
||||
|
||||
if (targetTimestampRanges[target].hasOwnProperty(lastTs)) {
|
||||
topBucket.relFrom = targetTimestampRanges[target][lastTs][0];
|
||||
lastTs = topBucket.relFrom;
|
||||
}
|
||||
|
||||
topBucket.to = topBucket.relTo + from;
|
||||
topBucket.from = topBucket.relFrom + from;
|
||||
targetEmptyBuckets.push(topBucket);
|
||||
}
|
||||
|
||||
var idx = 0;
|
||||
var bucketFrom = 0;
|
||||
|
||||
while (bucketFrom >= 0) {
|
||||
var b = new Bucket();
|
||||
b.yLabel = target;
|
||||
b.relTo = lastTs - idx * intervalMs;
|
||||
b.relFrom = lastTs - (idx + 1) * intervalMs;
|
||||
b.to = b.relTo + from;
|
||||
b.from = b.relFrom + from;
|
||||
b.values = [];
|
||||
bucketFrom = b.relFrom;
|
||||
targetEmptyBuckets.push(b);
|
||||
idx++;
|
||||
}
|
||||
|
||||
targetEmptyBuckets.map(function (bucket, i) {
|
||||
bucket.xid = i;
|
||||
});
|
||||
bucketMatrix.buckets[target] = targetEmptyBuckets;
|
||||
}); //console.log ("bucketMatrix: ", bucketMatrix);
|
||||
// Put values into buckets.
|
||||
|
||||
bucketMatrix.minValue = Number.MAX_VALUE;
|
||||
bucketMatrix.maxValue = Number.MIN_SAFE_INTEGER;
|
||||
targetKeys.map(function (target) {
|
||||
targetIndex[target].map(function (dataIndex) {
|
||||
var s = data[dataIndex];
|
||||
s.datapoints.map(function (dp) {
|
||||
for (var i = 0; i < bucketMatrix.buckets[target].length; i++) {
|
||||
if (bucketMatrix.buckets[target][i].belong(dp[TIME_INDEX])) {
|
||||
bucketMatrix.buckets[target][i].put(dp[VALUE_INDEX]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
bucketMatrix.buckets[target].map(function (bucket) {
|
||||
bucket.minValue = _.min(bucket.values);
|
||||
bucket.maxValue = _.max(bucket.values);
|
||||
|
||||
if (bucket.minValue < bucketMatrix.minValue) {
|
||||
bucketMatrix.minValue = bucket.minValue;
|
||||
}
|
||||
|
||||
if (bucket.maxValue > bucketMatrix.maxValue) {
|
||||
bucketMatrix.maxValue = bucket.maxValue;
|
||||
}
|
||||
|
||||
bucket.value = bucket.maxValue;
|
||||
|
||||
if (bucket.values.length > 1) {
|
||||
bucketMatrix.multipleValues = true;
|
||||
bucket.multipleValues = true;
|
||||
bucket.value = _this4.panel.seriesFilterIndex != -1 ? bucket.values[_this4.panel.seriesFilterIndex] : bucket.maxValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
bucketMatrix.xBucketSize = Number.MIN_SAFE_INTEGER;
|
||||
targetKeys.map(function (target) {
|
||||
var bucketsLen = bucketMatrix.buckets[target].length;
|
||||
|
||||
if (bucketsLen > bucketMatrix.xBucketSize) {
|
||||
bucketMatrix.xBucketSize = bucketsLen;
|
||||
}
|
||||
}); //console.log ("bucketMatrix with values: ", bucketMatrix);
|
||||
|
||||
bucketMatrix.targets = targetKeys;
|
||||
return bucketMatrix;
|
||||
}
|
||||
}]);
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+98
-3
@@ -13,7 +13,8 @@ System.register(["app/core/utils/kbn"], function (_export, _context) {
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
function statusHeatmapOptionsEditor() {
|
||||
/** @ngInject */
|
||||
function optionsEditorCtrl() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
@@ -24,7 +25,7 @@ System.register(["app/core/utils/kbn"], function (_export, _context) {
|
||||
};
|
||||
}
|
||||
|
||||
_export("statusHeatmapOptionsEditor", statusHeatmapOptionsEditor);
|
||||
_export("optionsEditorCtrl", optionsEditorCtrl);
|
||||
|
||||
return {
|
||||
setters: [function (_appCoreUtilsKbn) {
|
||||
@@ -34,6 +35,9 @@ System.register(["app/core/utils/kbn"], function (_export, _context) {
|
||||
_export("StatusHeatmapOptionsEditorCtrl", StatusHeatmapOptionsEditorCtrl =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
StatusHeatmapOptionsEditorCtrl.$inject = ["$scope"];
|
||||
|
||||
/** @ngInject */
|
||||
function StatusHeatmapOptionsEditorCtrl($scope) {
|
||||
_classCallCheck(this, StatusHeatmapOptionsEditorCtrl);
|
||||
|
||||
@@ -47,7 +51,6 @@ System.register(["app/core/utils/kbn"], function (_export, _context) {
|
||||
this.panelCtrl = $scope.ctrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
_createClass(StatusHeatmapOptionsEditorCtrl, [{
|
||||
@@ -56,6 +59,98 @@ System.register(["app/core/utils/kbn"], function (_export, _context) {
|
||||
this.panel.data.unitFormat = subItem.value;
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
}, {
|
||||
key: "render",
|
||||
value: function render() {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
}, {
|
||||
key: "onAddThreshold",
|
||||
value: function onAddThreshold() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: this.panel.defaultColor
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onRemoveThreshold",
|
||||
value: function onRemoveThreshold(index) {
|
||||
this.panel.color.thresholds.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onRemoveThresholds",
|
||||
value: function onRemoveThresholds() {
|
||||
this.panel.color.thresholds = [];
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onAddThreeLights",
|
||||
value: function onAddThreeLights() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: "red",
|
||||
value: 2,
|
||||
tooltip: "error"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "yellow",
|
||||
value: 1,
|
||||
tooltip: "warning"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "green",
|
||||
value: 0,
|
||||
tooltip: "ok"
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
/* https://ethanschoonover.com/solarized/ */
|
||||
|
||||
}, {
|
||||
key: "onAddSolarized",
|
||||
value: function onAddSolarized() {
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#b58900",
|
||||
value: 0,
|
||||
tooltip: "yellow"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#cb4b16",
|
||||
value: 1,
|
||||
tooltip: "orange"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#dc322f",
|
||||
value: 2,
|
||||
tooltip: "red"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#d33682",
|
||||
value: 3,
|
||||
tooltip: "magenta"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#6c71c4",
|
||||
value: 4,
|
||||
tooltip: "violet"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#268bd2",
|
||||
value: 5,
|
||||
tooltip: "blue"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#2aa198",
|
||||
value: 6,
|
||||
tooltip: "cyan"
|
||||
});
|
||||
this.panel.color.thresholds.push({
|
||||
color: "#859900",
|
||||
value: 7,
|
||||
tooltip: "green"
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
}]);
|
||||
|
||||
return StatusHeatmapOptionsEditorCtrl;
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+165
@@ -0,0 +1,165 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["lodash"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var _;
|
||||
|
||||
function migrate_V0_V1(panel) {
|
||||
// Remove unused fields.
|
||||
if (_.has(panel, "xAxis.labelFormat")) {
|
||||
delete panel.xAxis.labelFormat;
|
||||
}
|
||||
|
||||
if (_.has(panel, "xAxis.minBucketWidthToShowWeekends")) {
|
||||
delete panel.xAxis.minBucketWidthToShowWeekends;
|
||||
}
|
||||
|
||||
if (_.has(panel, "xAxis.showCrosshair")) {
|
||||
delete panel.xAxis.showCrosshair;
|
||||
}
|
||||
|
||||
if (_.has(panel, "xAxis.showWeekends")) {
|
||||
delete panel.xAxis.showWeekends;
|
||||
}
|
||||
|
||||
if (_.has(panel, "yAxis.showCrosshair")) {
|
||||
delete panel.yAxis.showCrosshair;
|
||||
}
|
||||
|
||||
if (_.has(panel, "data.unitFormat")) {
|
||||
delete panel.data;
|
||||
} // Migrate cardSpacing value. Seems rare (update from version 0.0.2).
|
||||
|
||||
|
||||
if (_.has(panel, "cards.cardSpacing")) {
|
||||
if (!_.has(panel, "cards.cardVSpacing")) {
|
||||
if (panel.cards.cardSpacing) {
|
||||
panel.cards.cardVSpacing = panel.cards.cardSpacing;
|
||||
panel.cards.cardHSpacing = panel.cards.cardSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
delete panel.cards.cardSpacing;
|
||||
} // Migrate initial config for urls in tooltip (pull/86).
|
||||
// 'usingUrl' was used to show tooltip with urls on click or not.
|
||||
|
||||
|
||||
if (_.has(panel, "usingUrl")) {
|
||||
if (!_.has(panel, "tooltip.freezeOnClick")) {
|
||||
panel.tooltip.freezeOnClick = panel.usingUrl;
|
||||
}
|
||||
|
||||
delete panel.usingUrl;
|
||||
} // 'urls' array is now tooltip.items array. Also items are changed.
|
||||
|
||||
|
||||
if (_.has(panel, "urls")) {
|
||||
if (!_.has(panel, "tooltip.items")) {
|
||||
panel.tooltip.items = [];
|
||||
var hasRealItems = true;
|
||||
|
||||
if (panel.urls.length == 0) {
|
||||
hasRealItems = false;
|
||||
}
|
||||
|
||||
if (panel.urls.length == 1) {
|
||||
var url = panel.urls[0];
|
||||
|
||||
if (url.base_url === '' && url.label === '') {
|
||||
hasRealItems = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRealItems) {
|
||||
panel.tooltip.showItems = true;
|
||||
var _iteratorNormalCompletion = true;
|
||||
var _didIteratorError = false;
|
||||
var _iteratorError = undefined;
|
||||
|
||||
try {
|
||||
for (var _iterator = panel.urls[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||
var _url = _step.value;
|
||||
var item = {
|
||||
urlTemplate: _.toString(_url.base_url),
|
||||
urlText: _.toString(_url.label),
|
||||
urlIcon: _.toString(_url.icon_fa),
|
||||
urlToLowerCase: _url.forcelowercase,
|
||||
valueDateFormat: ''
|
||||
}; // replace $vars with new ${__vars} if url template is not empty
|
||||
|
||||
if (item.urlTemplate !== "") {
|
||||
// $time was a graph time with prepended &
|
||||
item.urlTemplate = _.replace(_url.base_url, /\$time/g, "&${__url_time_range}"); // $series_label was a y axis label
|
||||
|
||||
item.urlTemplate = _.replace(item.urlTemplate, /\$series_label/, "${__y_label}"); // $series_extra was a value from bucket. This value has format options and index.
|
||||
|
||||
var valueVar = "__value";
|
||||
|
||||
if (_url.useExtraSeries === true) {
|
||||
// index?
|
||||
if (_url.extraSeries.index > -1) {
|
||||
valueVar += "_" + _url.extraSeries.index;
|
||||
}
|
||||
|
||||
var format = _.toString(_url.extraSeries.format);
|
||||
|
||||
if (format === 'YYYY/MM/DD/HH_mm_ss') {
|
||||
valueVar += '_date';
|
||||
item.valueDateFormat = format;
|
||||
}
|
||||
}
|
||||
|
||||
item.urlTemplate = _.replace(item.urlTemplate, /\$series_extra/, "${".concat(valueVar, "}"));
|
||||
}
|
||||
|
||||
panel.tooltip.items.push(item);
|
||||
}
|
||||
} catch (err) {
|
||||
_didIteratorError = true;
|
||||
_iteratorError = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
|
||||
_iterator["return"]();
|
||||
}
|
||||
} finally {
|
||||
if (_didIteratorError) {
|
||||
throw _iteratorError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete panel.urls;
|
||||
} // create statusmap metadata
|
||||
|
||||
|
||||
panel.statusmap = {
|
||||
"ConfigVersion": "v1"
|
||||
};
|
||||
}
|
||||
|
||||
function migratePanelConfig(panel) {
|
||||
if (_.has(panel, "statusmap")) {
|
||||
if (panel.statusmap.ConfigVersion == "v1") {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
migrate_V0_V1(panel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_export("migratePanelConfig", migratePanelConfig);
|
||||
|
||||
return {
|
||||
setters: [function (_lodash) {
|
||||
_ = _lodash.default;
|
||||
}],
|
||||
execute: function () {}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=panel_config_migration.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+50
-21
@@ -81,9 +81,7 @@
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.onEditorRemoveThreshold($index)">
|
||||
<i class="fa fa-trash"/>
|
||||
</a>
|
||||
<i class="fa fa-trash pointer" ng-click="editor.onRemoveThreshold($index)"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,12 +89,12 @@
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form"></div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.onEditorAddThreshold()">
|
||||
<button class="btn btn-inverse" ng-click="editor.onAddThreshold()">
|
||||
<i class="fa fa-plus"></i> Add new status
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.onEditorRemoveThresholds()">
|
||||
<button class="btn btn-inverse" ng-click="editor.onRemoveThresholds()">
|
||||
<i class="fa fa-minus"></i> Remove all
|
||||
</button>
|
||||
</div>
|
||||
@@ -108,12 +106,12 @@
|
||||
<label class="gf-form-label">Presets</label>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn" ng-click="ctrl.onEditorAddThreeLights()">
|
||||
<button class="btn" ng-click="editor.onAddThreeLights()">
|
||||
Red-Yellow-Green
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn" ng-click="ctrl.onEditorAddSolarized()">
|
||||
<button class="btn" ng-click="editor.onAddSolarized()">
|
||||
Solarized
|
||||
</button>
|
||||
</div>
|
||||
@@ -145,11 +143,24 @@
|
||||
checked="ctrl.panel.legend.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Show tooltip"
|
||||
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
|
||||
label="Show X axis"
|
||||
checked="ctrl.panel.xAxis.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Y axis sort</label>
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Show Y axis"
|
||||
checked="ctrl.panel.yAxis.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Min width</label>
|
||||
<input type="number" ng-model="ctrl.panel.yAxis.minWidth" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Max width</label>
|
||||
<input type="number" ng-model="ctrl.panel.yAxis.maxWidth" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Rows sort</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-8"
|
||||
ng-model="ctrl.panel.yAxisSort"
|
||||
@@ -157,46 +168,64 @@
|
||||
ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Bucket</h5>
|
||||
<h5 class="section-heading">Buckets</h5>
|
||||
|
||||
<gf-form-switch class="gf-form" label-class="width-9"
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Multiple values"
|
||||
checked="ctrl.panel.useMax" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form" ng-show="ctrl.panel.useMax">
|
||||
Color for bucket with multiple values is determined by color mode<br/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Values index</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="-1" data-placement="right"
|
||||
bs-tooltip="'Display only values with this index in a bucket. Use -1 to display all values'"
|
||||
ng-model="ctrl.panel.seriesFilterIndex" ng-change="ctrl.refresh()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Display nulls</label>
|
||||
<label class="gf-form-label width-8">Display nulls</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-9" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['as empty', 'as zero']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Min width for 1/1</label>
|
||||
<label class="gf-form-label width-8">Min width</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">V spacing</label>
|
||||
<label class="gf-form-label width-8">V spacing</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards vertical spacing in pixels'" ng-model="ctrl.panel.cards.cardVSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">H spacing</label>
|
||||
<label class="gf-form-label width-8">H spacing</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards horizontal spacing in pixels'" ng-model="ctrl.panel.cards.cardHSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Rounding</label>
|
||||
<label class="gf-form-label width-8">Rounding</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Cards rounding radius in pixels'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Pagination</h5>
|
||||
<div class="gf-form">
|
||||
<gf-form-switch class="gf-form" label="Enable pagination" label-class="width-12"
|
||||
checked="ctrl.panel.usingPagination" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<label class="gf-form-label width-10">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show by default'" ng-model="ctrl.panel.pageSize" ng-change="ctrl.changeDefaultPaginationSize(ctrl.panel.pageSize)"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Show tooltip"
|
||||
tooltip="'Show tooltip when mouse is over the card'"
|
||||
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Freeze on click"
|
||||
tooltip="'Freeze tooltip copy when card is clicked'"
|
||||
checked="ctrl.panel.tooltip.freezeOnClick" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Show items"
|
||||
tooltip="'Show items (urls) in tooltip'"
|
||||
checked="ctrl.panel.tooltip.showItems" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Items</h5>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.panel.tooltip.items.length == 0">
|
||||
<label class="gf-form-label width-2">0</label>
|
||||
<label class="gf-form-input width-30">No items defined.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div ng-repeat="item in ctrl.panel.tooltip.items">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label width-6">
|
||||
URL
|
||||
<info-popover mode="left-normal">
|
||||
<p>Specify an URL (relative or absolute)</p>
|
||||
<span>
|
||||
Use special variables:
|
||||
<br>
|
||||
<em>${__y_label}</em> name of the time series (alias or legend in the query editor).
|
||||
<br>
|
||||
<em>${__url_time_range}</em> refers to graph range in the form of 'from=...&to=...'.
|
||||
<br>
|
||||
<em>${__value}</em> refers to current bucket value or max value if multiple values mode is on.
|
||||
<br>
|
||||
<em>${__value_N}</em> refers to Nth value in current bucket. Value indexes are started from 0. For instance,
|
||||
<em>${__value_1}</em> refers to second value.
|
||||
<br>
|
||||
<em>${__value_date}</em> refers to __value formatted as date.
|
||||
<em>${__value_N_date}</em> refers to Nth value in current bucket formatted as date.
|
||||
</span>
|
||||
</info-popover>
|
||||
</label>
|
||||
<input type="text" class="gf-form-input" placeholder="https://www.google.es" data-placement="top"
|
||||
bs-tooltip="'This is the url to be shown when the user clicks on it'" ng-model="item.urlTemplate"
|
||||
ng-change="ctrl.refresh()">
|
||||
<label class="gf-form-label">
|
||||
<i class="fa fa-trash pointer" ng-click="editor.onRemoveUrl($index)"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-4">Icon</label>
|
||||
<label class="gf-form-label width-2">
|
||||
<i class="fa fa-{{item.urlIcon}}"></i>
|
||||
</label>
|
||||
<input
|
||||
class="gf-form-input width-10"
|
||||
type="text"
|
||||
ng-model="item.urlIcon"
|
||||
bs-typeahead="editor.getFAIconClasses"
|
||||
bs-tooltip="'The icon for URL link'"
|
||||
data-placement="top"
|
||||
ng-blur="ctrl.render()"
|
||||
data-min-length="0"
|
||||
data-items="100"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-4">Label</label>
|
||||
<input type="text" class="gf-form-input width-16" placeholder="My URL" data-placement="top"
|
||||
bs-tooltip="'Text for URL link'"
|
||||
ng-model="item.urlText" ng-change="ctrl.refresh()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<gf-form-switch class="gf-form" label-class="width-8" label="Lowercase link" checked="item.urlToLowerCase"
|
||||
on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Date format</label>
|
||||
<input type="text" class="gf-form-input width-12" placeholder="YYYY/MM/DD/HH_mm_ss"
|
||||
data-placement="top" bs-tooltip="'Date format for __value_date'"
|
||||
bs-typeahead="editor.getValueDateFormats"
|
||||
data-min-length="0"
|
||||
data-items="100"
|
||||
ng-model="item.valueDateFormat"
|
||||
ng-blur="ctrl.render()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form"></div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="editor.onAddUrl()">
|
||||
<i class="fa fa-plus"></i> Add URL
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="editor.onRemoveUrls()">
|
||||
<i class="fa fa-minus"></i> Remove all
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Vendored
+5
-5
@@ -9,7 +9,7 @@
|
||||
"name": "Flant JSC",
|
||||
"url": "http://flant.com"
|
||||
},
|
||||
"keywords": ["status", "heatmap", "panel"],
|
||||
"keywords": ["status", "statusmap", "heatmap", "panel"],
|
||||
"logos": {
|
||||
"small": "img/logo.svg",
|
||||
"large": "img/logo.svg"
|
||||
@@ -17,7 +17,6 @@
|
||||
"links": [
|
||||
{"name": "Github", "url": "https://github.com/flant/grafana-statusmap"},
|
||||
{"name": "MIT Licence", "url": "https://github.com/flant/grafana-statusmap/blob/master/LICENSE"}
|
||||
|
||||
],
|
||||
"screenshots": [
|
||||
{"name": "Panel with light theme", "path": "img/flant-statusmap-panel.png"},
|
||||
@@ -25,12 +24,13 @@
|
||||
{"name": "Color mapping", "path": "img/color-mapping.png"},
|
||||
{"name": "Queries example", "path": "img/queries-example.png"}
|
||||
],
|
||||
"version": "0.2.0",
|
||||
"updated": "2019-12-03"
|
||||
"version": "0.3.2",
|
||||
"updated": "2020-08-13"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "5.1.x",
|
||||
"grafanaDependency": ">=5.4.3",
|
||||
"grafanaVersion": "5.4.x",
|
||||
"plugins": [ ]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+130
-73
@@ -1,16 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/core", "d3", "./libs/d3-scale-chromatic/index", "./tooltip", "./annotations"], function (_export, _context) {
|
||||
System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/core", "d3", "./libs/d3-scale-chromatic/index", "./tooltip", "./annotations", "./module", "./libs/grafana/events/index"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var _, $, moment, kbn, appEvents, contextSrv, d3, d3ScaleChromatic, StatusmapTooltip, AnnotationTooltip, MIN_CARD_SIZE, CARD_H_SPACING, CARD_V_SPACING, CARD_ROUND, DATA_RANGE_WIDING_FACTOR, DEFAULT_X_TICK_SIZE_PX, DEFAULT_Y_TICK_SIZE_PX, X_AXIS_TICK_PADDING, Y_AXIS_TICK_PADDING, MIN_SELECTION_WIDTH, StatusmapRenderer;
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
var _, $, moment, kbn, appEvents, contextSrv, d3, d3ScaleChromatic, StatusmapTooltip, AnnotationTooltip, renderComplete, CoreEvents, PanelEvents, MIN_CARD_SIZE, CARD_H_SPACING, CARD_V_SPACING, CARD_ROUND, DATA_RANGE_WIDING_FACTOR, DEFAULT_X_TICK_SIZE_PX, DEFAULT_Y_TICK_SIZE_PX, X_AXIS_TICK_PADDING, Y_AXIS_TICK_PADDING, MIN_SELECTION_WIDTH, Statusmap, StatusmapRenderer;
|
||||
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
function rendering(scope, elem, attrs, ctrl) {
|
||||
@@ -68,6 +68,11 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
StatusmapTooltip = _tooltip.StatusmapTooltip;
|
||||
}, function (_annotations) {
|
||||
AnnotationTooltip = _annotations.AnnotationTooltip;
|
||||
}, function (_module) {
|
||||
renderComplete = _module.renderComplete;
|
||||
}, function (_libsGrafanaEventsIndex) {
|
||||
CoreEvents = _libsGrafanaEventsIndex.CoreEvents;
|
||||
PanelEvents = _libsGrafanaEventsIndex.PanelEvents;
|
||||
}],
|
||||
execute: function () {
|
||||
MIN_CARD_SIZE = 5;
|
||||
@@ -81,6 +86,21 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
Y_AXIS_TICK_PADDING = 5;
|
||||
MIN_SELECTION_WIDTH = 2;
|
||||
|
||||
Statusmap = function Statusmap() {
|
||||
_classCallCheck(this, Statusmap);
|
||||
|
||||
_defineProperty(this, "$svg", void 0);
|
||||
|
||||
_defineProperty(this, "svg", void 0);
|
||||
|
||||
_defineProperty(this, "bucketMatrix", void 0);
|
||||
|
||||
_defineProperty(this, "timeRange", {
|
||||
from: 0,
|
||||
to: 0
|
||||
});
|
||||
};
|
||||
|
||||
_export("StatusmapRenderer", StatusmapRenderer =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
@@ -131,9 +151,9 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
|
||||
_defineProperty(this, "yGridSize", 0);
|
||||
|
||||
_defineProperty(this, "data", void 0);
|
||||
_defineProperty(this, "bucketMatrix", void 0);
|
||||
|
||||
_defineProperty(this, "cardsData", void 0);
|
||||
_defineProperty(this, "bucketMatrixPager", void 0);
|
||||
|
||||
_defineProperty(this, "panel", void 0);
|
||||
|
||||
@@ -158,7 +178,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
_defineProperty(this, "dataRangeWidingFactor", DATA_RANGE_WIDING_FACTOR);
|
||||
|
||||
// $heatmap is JQuery object, but heatmap is D3
|
||||
this.$heatmap = this.elem.find('.status-heatmap-panel');
|
||||
this.$heatmap = this.elem.find('.statusmap-panel');
|
||||
this.tooltip = new StatusmapTooltip(this.$heatmap, this.scope);
|
||||
this.annotationTooltip = new AnnotationTooltip(this.$heatmap, this.scope);
|
||||
this.yOffset = 0;
|
||||
@@ -179,18 +199,19 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
top: 10,
|
||||
bottom: 20
|
||||
};
|
||||
this.ctrl.events.on('render', this.onRender.bind(this));
|
||||
this.ctrl.events.on(PanelEvents.render, this.onRender.bind(this));
|
||||
this.ctrl.tickValueFormatter = this.tickValueFormatter.bind(this); /////////////////////////////
|
||||
// Selection and crosshair //
|
||||
/////////////////////////////
|
||||
// Shared crosshair and tooltip
|
||||
// Shared crosshair and tooltip this.empty = true;
|
||||
|
||||
appEvents.on('graph-hover', this.onGraphHover.bind(this), this.scope);
|
||||
appEvents.on('graph-hover-clear', this.onGraphHoverClear.bind(this), this.scope); // Register selection listeners
|
||||
appEvents.on(CoreEvents.graphHover, this.onGraphHover.bind(this), this.scope);
|
||||
appEvents.on(CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), this.scope); // Register selection listeners
|
||||
|
||||
this.$heatmap.on('mousedown', this.onMouseDown.bind(this));
|
||||
this.$heatmap.on('mousemove', this.onMouseMove.bind(this));
|
||||
this.$heatmap.on('mouseleave', this.onMouseLeave.bind(this));
|
||||
this.$heatmap.on('click', this.onMouseClick.bind(this));
|
||||
}
|
||||
|
||||
_createClass(StatusmapRenderer, [{
|
||||
@@ -219,7 +240,14 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height -= this.panel.legend.show ? 32 : 10; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
if (this.panel.usingPagination) {
|
||||
// TODO get height of pagination controls.
|
||||
// reserve height for legend and for a row of pagination controls.
|
||||
height -= this.panel.legend.show ? 70 : 40; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
} else {
|
||||
// reserve height for legend
|
||||
height -= this.panel.legend.show ? 32 : 4; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
}
|
||||
|
||||
this.$heatmap.css('height', height + 'px');
|
||||
return true;
|
||||
@@ -258,6 +286,10 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
key: "addXAxis",
|
||||
value: function addXAxis() {
|
||||
// Scale timestamps to cards centers
|
||||
//this.scope.xScale = this.xScale = d3.scaleTime()
|
||||
// .domain([this.timeRange.from, this.timeRange.to])
|
||||
// .range([this.xGridSize/2, this.chartWidth-this.xGridSize/2]);
|
||||
// Buckets without the most recent
|
||||
this.scope.xScale = this.xScale = d3.scaleTime().domain([this.timeRange.from, this.timeRange.to]).range([this.xGridSize / 2, this.chartWidth - this.xGridSize / 2]);
|
||||
var ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
||||
var grafanaTimeFormatter = grafanaTimeFormat(ticks, this.timeRange.from, this.timeRange.to);
|
||||
@@ -311,14 +343,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}, {
|
||||
key: "addYAxis",
|
||||
value: function addYAxis() {
|
||||
var ticks = _.uniq(_.map(this.data, function (d) {
|
||||
return d.target;
|
||||
})); // Set default Y min and max if no data
|
||||
|
||||
|
||||
if (_.isEmpty(this.data)) {
|
||||
ticks = [''];
|
||||
}
|
||||
var ticks = this.bucketMatrixPager.targets(); // TODO move sorting into bucketMatrixPager.
|
||||
|
||||
if (this.panel.yAxisSort == 'a → z') {
|
||||
ticks.sort(function (a, b) {
|
||||
@@ -386,17 +411,19 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
// calculate sizes for cards drawing
|
||||
|
||||
}, {
|
||||
key: "addHeatmapCanvas",
|
||||
value: function addHeatmapCanvas() {
|
||||
key: "addStatusmapCanvas",
|
||||
value: function addStatusmapCanvas() {
|
||||
var heatmap_elem = this.$heatmap[0];
|
||||
this.width = Math.floor(this.$heatmap.width()) - this.padding.right;
|
||||
this.height = Math.floor(this.$heatmap.height()) - this.padding.bottom;
|
||||
|
||||
if (this.heatmap) {
|
||||
this.heatmap.remove();
|
||||
}
|
||||
} // Insert svg as a first child into heatmap_elem
|
||||
// as the frozen tooltip moving logiс assumes that tooltip is the last child.
|
||||
|
||||
this.heatmap = d3.select(heatmap_elem).append("svg").attr("width", this.width).attr("height", this.height);
|
||||
|
||||
this.heatmap = d3.select(heatmap_elem).insert("svg", ":first-child").attr("width", this.width).attr("height", this.height);
|
||||
this.chartHeight = this.height - this.margin.top - this.margin.bottom;
|
||||
this.chartTop = this.margin.top;
|
||||
this.chartBottom = this.chartTop + this.chartHeight;
|
||||
@@ -404,7 +431,12 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
this.cardVSpacing = this.panel.cards.cardVSpacing !== null ? this.panel.cards.cardVSpacing : CARD_V_SPACING;
|
||||
this.cardRound = this.panel.cards.cardRound !== null ? this.panel.cards.cardRound : CARD_ROUND; // calculate yOffset for YAxis
|
||||
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.cardsData.yBucketSize);
|
||||
this.yGridSize = this.chartHeight;
|
||||
|
||||
if (this.bucketMatrixPager.targets().length > 0) {
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.bucketMatrixPager.targets().length);
|
||||
}
|
||||
|
||||
this.cardHeight = this.yGridSize ? this.yGridSize - this.cardVSpacing : 0;
|
||||
this.yOffset = this.cardHeight / 2;
|
||||
this.addYAxis();
|
||||
@@ -412,7 +444,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
this.chartWidth = this.width - this.yAxisWidth - this.margin.right; // TODO allow per-y cardWidth!
|
||||
// we need to fill chartWidth with xBucketSize cards.
|
||||
|
||||
this.xGridSize = this.chartWidth / (this.cardsData.xBucketSize + 1);
|
||||
this.xGridSize = this.chartWidth / (this.bucketMatrix.xBucketSize + 1);
|
||||
this.cardWidth = this.xGridSize - this.cardHSpacing;
|
||||
this.addXAxis();
|
||||
this.xAxisHeight = this.getXAxisHeight(this.heatmap);
|
||||
@@ -426,27 +458,34 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "addHeatmap",
|
||||
value: function addHeatmap() {
|
||||
key: "addStatusmap",
|
||||
value: function addStatusmap() {
|
||||
var _this = this;
|
||||
|
||||
this.addHeatmapCanvas();
|
||||
var maxValue = this.panel.color.max || this.cardsData.maxValue;
|
||||
var minValue = this.panel.color.min || this.cardsData.minValue;
|
||||
var maxValue = this.panel.color.max || this.bucketMatrix.maxValue;
|
||||
var minValue = this.panel.color.min || this.bucketMatrix.minValue;
|
||||
|
||||
if (this.panel.color.mode !== 'discrete') {
|
||||
this.colorScale = this.getColorScale(maxValue, minValue);
|
||||
}
|
||||
|
||||
this.setOpacityScale(maxValue);
|
||||
var cards = this.heatmap.selectAll(".status-heatmap-card").data(this.cardsData.cards);
|
||||
cards.append("title");
|
||||
cards = cards.enter().append("rect").attr("cardId", function (c) {
|
||||
return c.id;
|
||||
}).attr("x", this.getCardX.bind(this)).attr("width", this.getCardWidth.bind(this)).attr("y", this.getCardY.bind(this)).attr("height", this.getCardHeight.bind(this)).attr("rx", this.cardRound).attr("ry", this.cardRound).attr("class", "bordered status-heatmap-card").style("fill", this.getCardColor.bind(this)).style("stroke", this.getCardColor.bind(this)).style("stroke-width", 0) //.style("stroke-width", getCardStrokeWidth)
|
||||
this.setOpacityScale(maxValue); // Draw cards from buckets.
|
||||
|
||||
this.heatmap.selectAll(".statusmap-cards-row").data(this.bucketMatrixPager.targets()).enter().selectAll(".statustmap-card").data(function (target) {
|
||||
return _this.bucketMatrix.buckets[target];
|
||||
}).enter().append("rect").attr("cardId", function (b) {
|
||||
return b.id;
|
||||
}).attr("xid", function (b) {
|
||||
return b.xid;
|
||||
}).attr("yid", function (b) {
|
||||
return b.yLabel;
|
||||
}).attr("x", this.getCardX.bind(this)).attr("width", this.getCardWidth.bind(this)).attr("y", this.getCardY.bind(this)).attr("height", this.getCardHeight.bind(this)).attr("rx", this.cardRound).attr("ry", this.cardRound).attr("class", function (b) {
|
||||
return b.isEmpty() ? "empty-card" : "bordered statusmap-card";
|
||||
}).style("fill", this.getCardColor.bind(this)).style("stroke", this.getCardColor.bind(this)).style("stroke-width", 0) //.style("stroke-width", getCardStrokeWidth)
|
||||
//.style("stroke-dasharray", "3,3")
|
||||
.style("opacity", this.getCardOpacity.bind(this));
|
||||
var $cards = this.$heatmap.find(".status-heatmap-card");
|
||||
.style("opacity", this.getCardOpacity.bind(this)); // Set mouse events on cards.
|
||||
|
||||
var $cards = this.$heatmap.find(".statusmap-card + .bordered");
|
||||
$cards.on("mouseenter", function (event) {
|
||||
_this.tooltip.mouseOverBucket = true;
|
||||
|
||||
@@ -459,7 +498,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
|
||||
this._renderAnnotations();
|
||||
|
||||
this.ctrl.events.emit('render-complete', {
|
||||
this.ctrl.events.emit(renderComplete, {
|
||||
"chartWidth": this.chartWidth
|
||||
});
|
||||
}
|
||||
@@ -485,10 +524,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
|
||||
var colorScheme = _.find(this.ctrl.colorSchemes, {
|
||||
value: this.panel.color.colorScheme
|
||||
}); // if (!colorScheme) {
|
||||
//
|
||||
// }
|
||||
|
||||
});
|
||||
|
||||
var colorInterpolator = d3ScaleChromatic[colorScheme.value];
|
||||
var colorScaleInverted = colorScheme.invert === 'always' || colorScheme.invert === 'dark' && !contextSrv.user.lightTheme;
|
||||
@@ -508,10 +544,12 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}
|
||||
}, {
|
||||
key: "getCardX",
|
||||
value: function getCardX(d) {
|
||||
value: function getCardX(b) {
|
||||
var x; // cx is the center of the card. Card should be placed to the left.
|
||||
//let cx = this.xScale(d.x);
|
||||
|
||||
var cx = this.xScale(d.x);
|
||||
var rightX = b.relTo / this.bucketMatrix.rangeMs * this.chartWidth;
|
||||
var cx = rightX - this.cardWidth / 2;
|
||||
|
||||
if (cx - this.cardWidth / 2 < 0) {
|
||||
x = this.yAxisWidth + this.cardHSpacing / 2;
|
||||
@@ -524,9 +562,11 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
|
||||
}, {
|
||||
key: "getCardWidth",
|
||||
value: function getCardWidth(d) {
|
||||
value: function getCardWidth(b) {
|
||||
//return 20;
|
||||
var w;
|
||||
var cx = this.xScale(d.x);
|
||||
var rightX = b.relTo / this.bucketMatrix.rangeMs * this.chartWidth;
|
||||
var cx = rightX - this.cardWidth / 2; //let cx = this.xScale(d.x);
|
||||
|
||||
if (cx < this.cardWidth / 2) {
|
||||
// Center should not exceed half of card.
|
||||
@@ -548,16 +588,19 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
} // Top y for card.
|
||||
// yScale gives ???
|
||||
|
||||
}, {
|
||||
key: "getCardY",
|
||||
value: function getCardY(d) {
|
||||
return this.yScale(d.y) + this.chartTop - this.cardHeight - this.cardVSpacing / 2;
|
||||
value: function getCardY(b) {
|
||||
return this.yScale(b.yLabel) + this.chartTop - this.cardHeight - this.cardVSpacing / 2;
|
||||
}
|
||||
}, {
|
||||
key: "getCardHeight",
|
||||
value: function getCardHeight(d) {
|
||||
var ys = this.yScale(d.y);
|
||||
value: function getCardHeight(b) {
|
||||
//return 20;
|
||||
var ys = this.yScale(b.yLabel);
|
||||
var y = ys + this.chartTop - this.cardHeight - this.cardVSpacing / 2;
|
||||
var h = this.cardHeight; // Cut card height to prevent overlay
|
||||
|
||||
@@ -582,31 +625,35 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}
|
||||
}, {
|
||||
key: "getCardColor",
|
||||
value: function getCardColor(d) {
|
||||
value: function getCardColor(bucket) {
|
||||
if (this.panel.color.mode === 'opacity') {
|
||||
return this.panel.color.cardColor;
|
||||
} else if (this.panel.color.mode === 'spectrum') {
|
||||
return this.colorScale(d.value);
|
||||
return this.colorScale(bucket.value);
|
||||
} else if (this.panel.color.mode === 'discrete') {
|
||||
return this.ctrl.discreteHelper.getBucketColor(d.values);
|
||||
if (this.panel.seriesFilterIndex != null && this.panel.seriesFilterIndex != -1) {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColorSingle(bucket.values[this.panel.seriesFilterIndex]);
|
||||
} else {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColor(bucket.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "getCardOpacity",
|
||||
value: function getCardOpacity(d) {
|
||||
if (this.panel.nullPointMode === 'as empty' && d.value == null) {
|
||||
value: function getCardOpacity(bucket) {
|
||||
if (this.panel.nullPointMode === 'as empty' && bucket.value == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.panel.color.mode === 'opacity') {
|
||||
return this.opacityScale(d.value);
|
||||
return this.opacityScale(bucket.value);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "getCardStrokeWidth",
|
||||
value: function getCardStrokeWidth(d) {
|
||||
value: function getCardStrokeWidth(b) {
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
return '1';
|
||||
}
|
||||
@@ -663,9 +710,10 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}
|
||||
}, {
|
||||
key: "onMouseLeave",
|
||||
value: function onMouseLeave() {
|
||||
appEvents.emit('graph-hover-clear');
|
||||
this.clearCrosshair(); //annotationTooltip.destroy();
|
||||
value: function onMouseLeave(e) {
|
||||
appEvents.emit(CoreEvents.graphHoverClear);
|
||||
this.clearCrosshair();
|
||||
this.annotationTooltip.destroy();
|
||||
}
|
||||
}, {
|
||||
key: "onMouseMove",
|
||||
@@ -687,10 +735,18 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
//const pos = this.getEventPos(event, offset);
|
||||
this.emitGraphHoverEvent(event);
|
||||
this.drawCrosshair(offset.x);
|
||||
this.tooltip.show(event); //, data); // pos, this.data
|
||||
|
||||
this.tooltip.show(event);
|
||||
this.annotationTooltip.show(event);
|
||||
}
|
||||
} // TODO emit an event and move logic to panelCtrl
|
||||
|
||||
}, {
|
||||
key: "onMouseClick",
|
||||
value: function onMouseClick(e) {
|
||||
if (this.ctrl.panel.tooltip.freezeOnClick) {
|
||||
this.tooltip.showFrozen(e);
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "getEventPos",
|
||||
@@ -726,7 +782,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
|
||||
pos.panelRelY = Math.max(event.offsetY / this.height, 0.001); // broadcast to other graph panels that we are hovering
|
||||
|
||||
appEvents.emit('graph-hover', {
|
||||
appEvents.emit(CoreEvents.graphHover, {
|
||||
pos: pos,
|
||||
panel: this.panel
|
||||
});
|
||||
@@ -791,22 +847,23 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
}, {
|
||||
key: "render",
|
||||
value: function render() {
|
||||
this.data = this.ctrl.data;
|
||||
this.panel = this.ctrl.panel;
|
||||
this.timeRange = this.ctrl.range;
|
||||
this.cardsData = this.ctrl.cardsData;
|
||||
this.bucketMatrix = this.ctrl.bucketMatrix;
|
||||
this.bucketMatrixPager = this.ctrl.bucketMatrixPager;
|
||||
|
||||
if (!this.data || !this.cardsData || !this.setElementHeight()) {
|
||||
if (!this.bucketMatrix || !this.setElementHeight()) {
|
||||
return;
|
||||
} // Draw default axes and return if no data
|
||||
|
||||
|
||||
if (_.isEmpty(this.cardsData.cards)) {
|
||||
this.addHeatmapCanvas();
|
||||
this.addStatusmapCanvas();
|
||||
|
||||
if (this.bucketMatrix.noDatapoints) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addHeatmap();
|
||||
this.addStatusmap();
|
||||
this.scope.yAxisWidth = this.yAxisWidth;
|
||||
this.scope.xAxisHeight = this.xAxisHeight;
|
||||
this.scope.chartHeight = this.chartHeight;
|
||||
@@ -832,7 +889,7 @@ System.register(["lodash", "jquery", "moment", "app/core/utils/kbn", "app/core/c
|
||||
"id": i,
|
||||
"anno": d.source
|
||||
};
|
||||
}); //console.log({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
|
||||
}); //({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
|
||||
|
||||
|
||||
var anno = this.heatmap.append("g").attr("class", "statusmap-annotations").attr("transform", "translate(0.5,0)").selectAll(".statusmap-annotations").data(annoData).enter().append("g");
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+166
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
|
||||
System.register(["../panel_config_migration"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var migratePanelConfig;
|
||||
return {
|
||||
setters: [function (_panel_config_migration) {
|
||||
migratePanelConfig = _panel_config_migration.migratePanelConfig;
|
||||
}],
|
||||
execute: function () {
|
||||
describe('when migrate from config with urls', function () {
|
||||
var panel;
|
||||
describe('given full panel configuration', function () {
|
||||
var panelConfig = {
|
||||
"cards": {
|
||||
"cardHSpacing": 2,
|
||||
"cardMinWidth": 5,
|
||||
"cardRound": null,
|
||||
"cardVSpacing": 2
|
||||
},
|
||||
"color": {
|
||||
"cardColor": "#b4ff00",
|
||||
"colorScale": "sqrt",
|
||||
"colorScheme": "interpolateGnYlRd",
|
||||
"defaultColor": "#757575",
|
||||
"exponent": 0.5,
|
||||
"mode": "opacity",
|
||||
"thresholds": []
|
||||
},
|
||||
"data": {
|
||||
"decimals": null,
|
||||
"unitFormat": "short"
|
||||
},
|
||||
"datasource": "TestData DB",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"highlightCards": true,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"nullPointMode": "as empty",
|
||||
"seriesFilterIndex": -1,
|
||||
"targets": [{
|
||||
"aggregation": "Last",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1,3,3"
|
||||
},
|
||||
"decimals": 2,
|
||||
"displayAliasType": "Warning / Critical",
|
||||
"displayType": "Regular",
|
||||
"displayValueWithAlias": "Never",
|
||||
"refId": "A",
|
||||
"scenarioId": "predictable_csv_wave",
|
||||
"stringInput": "",
|
||||
"units": "none",
|
||||
"valueHandler": "Number Threshold"
|
||||
}],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Panel Title",
|
||||
"tooltip": {
|
||||
"show": true
|
||||
},
|
||||
"type": "flant-statusmap-panel",
|
||||
"urls": [{
|
||||
"base_url": "",
|
||||
"extraSeries": {
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "",
|
||||
"tooltip": "",
|
||||
"useExtraSeries": false,
|
||||
"useseriesname": true
|
||||
}],
|
||||
"useMax": true,
|
||||
"usingUrl": true,
|
||||
"xAxis": {
|
||||
"labelFormat": "%a %m/%d",
|
||||
"show": true
|
||||
},
|
||||
"yAxis": {
|
||||
"maxWidth": -1,
|
||||
"minWidth": -1,
|
||||
"show": true
|
||||
},
|
||||
"yAxisSort": "metrics"
|
||||
};
|
||||
beforeEach(function () {
|
||||
panel = Object.assign({}, panelConfig);
|
||||
migratePanelConfig(panel);
|
||||
});
|
||||
it('should migrate usingUrls to tooltip.freezeOnClick', function () {
|
||||
expect(panel.usingUrl).toBeUndefined();
|
||||
expect(panel.tooltip).toHaveProperty("freezeOnClick", true);
|
||||
expect(panel.tooltip.freezeOnClick).toBeTruthy();
|
||||
});
|
||||
it('should have no urls', function () {
|
||||
expect(panel.urls).toBeUndefined();
|
||||
});
|
||||
it('should have empty items in tooltip', function () {
|
||||
expect(panel.tooltip).toHaveProperty("items", []);
|
||||
expect(panel.tooltip.items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
describe("given configuration with non-empty base_url", function () {
|
||||
describe("with variables", function () {
|
||||
var panelConfig = {
|
||||
"tooltip": {
|
||||
"show": true
|
||||
},
|
||||
"urls": [{
|
||||
"base_url": "https://google.com/$series_label$time",
|
||||
"extraSeries": {
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "google",
|
||||
"tooltip": "",
|
||||
"useExtraSeries": false,
|
||||
"useseriesname": true
|
||||
}, {
|
||||
"base_url": "example.com/$series_extra",
|
||||
"extraSeries": {
|
||||
"format": "YYYY/MM/DD/HH_mm_ss",
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "DateLink",
|
||||
"useExtraSeries": true
|
||||
}]
|
||||
};
|
||||
beforeEach(function () {
|
||||
panel = Object.assign({}, panelConfig);
|
||||
migratePanelConfig(panel);
|
||||
});
|
||||
it("should have equal size of tooltip.items", function () {
|
||||
expect(panel.tooltip).toHaveProperty("items");
|
||||
expect(panel.tooltip.items).toHaveLength(panelConfig.urls.length);
|
||||
});
|
||||
it("should replace time variable with __url_time_range", function () {
|
||||
expect(panel.tooltip.items[0].urlTemplate).toContain("${__url_time_range}");
|
||||
});
|
||||
it("should replace series_label variable with __y_label", function () {
|
||||
expect(panel.tooltip.items[0].urlTemplate).toContain("${__y_label}");
|
||||
});
|
||||
it("should replace series_extra variable with __value_date", function () {
|
||||
expect(panel.tooltip.items[1].urlTemplate).toContain("${__value_date}");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=panel_config_migration.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+267
-34
@@ -3,69 +3,302 @@
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var Card, CardsStorage;
|
||||
var Bucket, BucketMatrix, pagerChanged, BucketMatrixPager;
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
// A holder of values
|
||||
_export("Card", Card = // uniq
|
||||
// Array of values in this bucket
|
||||
// card has multiple values
|
||||
// card has values that has no color
|
||||
//
|
||||
//
|
||||
//
|
||||
function Card() {
|
||||
_classCallCheck(this, Card);
|
||||
// A holder of a group of values
|
||||
_export("Bucket", Bucket =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
// uniq id
|
||||
// Array of values in this bucket
|
||||
// From pr/86
|
||||
// a bucket has multiple values
|
||||
// a bucket has values that has no mapped color
|
||||
// y label
|
||||
// This value can be used to calculate a x coordinate on a graph
|
||||
// a time range of this bucket
|
||||
// to and from relative to real "from"
|
||||
// Saved minimum and maximum of values in this bucket
|
||||
// A value if multiple values is not allowed
|
||||
function Bucket() {
|
||||
_classCallCheck(this, Bucket);
|
||||
|
||||
_defineProperty(this, "id", 0);
|
||||
_defineProperty(this, "id", 0);
|
||||
|
||||
_defineProperty(this, "values", []);
|
||||
_defineProperty(this, "values", []);
|
||||
|
||||
_defineProperty(this, "multipleValues", false);
|
||||
_defineProperty(this, "columns", []);
|
||||
|
||||
_defineProperty(this, "noColorDefined", false);
|
||||
_defineProperty(this, "multipleValues", false);
|
||||
|
||||
_defineProperty(this, "y", "");
|
||||
_defineProperty(this, "noColorDefined", false);
|
||||
|
||||
_defineProperty(this, "x", 0);
|
||||
_defineProperty(this, "y", "");
|
||||
|
||||
_defineProperty(this, "minValue", 0);
|
||||
_defineProperty(this, "yLabel", "");
|
||||
|
||||
_defineProperty(this, "maxValue", 0);
|
||||
_defineProperty(this, "x", 0);
|
||||
|
||||
_defineProperty(this, "value", 0);
|
||||
_defineProperty(this, "xid", 0);
|
||||
|
||||
_defineProperty(this, "from", 0);
|
||||
|
||||
_defineProperty(this, "to", 0);
|
||||
|
||||
_defineProperty(this, "relFrom", 0);
|
||||
|
||||
_defineProperty(this, "relTo", 0);
|
||||
|
||||
_defineProperty(this, "mostRecent", false);
|
||||
|
||||
_defineProperty(this, "minValue", 0);
|
||||
|
||||
_defineProperty(this, "maxValue", 0);
|
||||
|
||||
_defineProperty(this, "value", 0);
|
||||
}
|
||||
|
||||
_createClass(Bucket, [{
|
||||
key: "belong",
|
||||
value: function belong(ts) {
|
||||
return ts >= this.from && ts <= this.to;
|
||||
}
|
||||
}, {
|
||||
key: "put",
|
||||
value: function put(value) {
|
||||
this.values.push(value);
|
||||
}
|
||||
}, {
|
||||
key: "done",
|
||||
value: function done() {// calculate min, max, value
|
||||
}
|
||||
}, {
|
||||
key: "isEmpty",
|
||||
value: function isEmpty() {
|
||||
return this.values.length == 0;
|
||||
}
|
||||
}]);
|
||||
|
||||
return Bucket;
|
||||
}());
|
||||
|
||||
_export("BucketMatrix", BucketMatrix =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
// buckets for each y label
|
||||
// a flag that indicate that buckets has stub values
|
||||
// An array of row labels
|
||||
// TODO remove: a transition from CardsData
|
||||
function BucketMatrix() {
|
||||
_classCallCheck(this, BucketMatrix);
|
||||
|
||||
_defineProperty(this, "buckets", {});
|
||||
|
||||
_defineProperty(this, "maxValue", 0);
|
||||
|
||||
_defineProperty(this, "minValue", 0);
|
||||
|
||||
_defineProperty(this, "multipleValues", false);
|
||||
|
||||
_defineProperty(this, "noColorDefined", false);
|
||||
|
||||
_defineProperty(this, "noDatapoints", false);
|
||||
|
||||
_defineProperty(this, "targets", []);
|
||||
|
||||
_defineProperty(this, "rangeMs", 0);
|
||||
|
||||
_defineProperty(this, "intervalMs", 0);
|
||||
|
||||
_defineProperty(this, "xBucketSize", 0);
|
||||
}
|
||||
|
||||
_createClass(BucketMatrix, [{
|
||||
key: "get",
|
||||
value: function get(yid, xid) {
|
||||
if (yid in this.buckets) {
|
||||
if (xid in this.buckets[yid]) {
|
||||
return this.buckets[yid][xid];
|
||||
}
|
||||
}
|
||||
|
||||
return new Bucket();
|
||||
}
|
||||
}, {
|
||||
key: "hasData",
|
||||
value: function hasData() {
|
||||
var _this = this;
|
||||
|
||||
var hasData = false;
|
||||
|
||||
if (this.targets.length > 0) {
|
||||
this.targets.map(function (target) {
|
||||
if (_this.buckets[target].length > 0) {
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hasData;
|
||||
}
|
||||
}]);
|
||||
|
||||
return BucketMatrix;
|
||||
}());
|
||||
|
||||
_export("pagerChanged", pagerChanged = {
|
||||
name: 'statusmap-pager-changed'
|
||||
});
|
||||
|
||||
_export("CardsStorage", CardsStorage = function CardsStorage() {
|
||||
_classCallCheck(this, CardsStorage);
|
||||
_export("BucketMatrixPager", BucketMatrixPager =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
function BucketMatrixPager() {
|
||||
_classCallCheck(this, BucketMatrixPager);
|
||||
|
||||
_defineProperty(this, "cards", []);
|
||||
_defineProperty(this, "bucketMatrix", void 0);
|
||||
|
||||
_defineProperty(this, "xBucketSize", 0);
|
||||
_defineProperty(this, "enable", void 0);
|
||||
|
||||
_defineProperty(this, "yBucketSize", 0);
|
||||
_defineProperty(this, "defaultPageSize", -1);
|
||||
|
||||
_defineProperty(this, "maxValue", void 0);
|
||||
_defineProperty(this, "pageSize", -1);
|
||||
|
||||
_defineProperty(this, "minValue", void 0);
|
||||
_defineProperty(this, "currentPage", 0);
|
||||
|
||||
_defineProperty(this, "multipleValues", false);
|
||||
var m = new BucketMatrix();
|
||||
this.bucketMatrix = m;
|
||||
} // An array of row labels for current page.
|
||||
|
||||
_defineProperty(this, "noColorDefined", false);
|
||||
|
||||
_defineProperty(this, "targets", []);
|
||||
_createClass(BucketMatrixPager, [{
|
||||
key: "targets",
|
||||
value: function targets() {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.targets;
|
||||
}
|
||||
|
||||
_defineProperty(this, "targetIndex", void 0);
|
||||
return this.bucketMatrix.targets.slice(this.pageSize * this.currentPage, this.pageSize * (this.currentPage + 1));
|
||||
}
|
||||
}, {
|
||||
key: "buckets",
|
||||
value: function buckets() {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.buckets;
|
||||
}
|
||||
|
||||
this.maxValue = 0;
|
||||
this.minValue = 0;
|
||||
});
|
||||
var buckets = {};
|
||||
var me = this;
|
||||
this.targets().map(function (rowLabel) {
|
||||
buckets[rowLabel] = me.bucketMatrix.buckets[rowLabel];
|
||||
});
|
||||
return buckets;
|
||||
}
|
||||
}, {
|
||||
key: "setEnable",
|
||||
value: function setEnable(enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
}, {
|
||||
key: "setCurrent",
|
||||
value: function setCurrent(num) {
|
||||
this.currentPage = num;
|
||||
}
|
||||
}, {
|
||||
key: "setDefaultPageSize",
|
||||
value: function setDefaultPageSize(num) {
|
||||
this.defaultPageSize = num;
|
||||
}
|
||||
}, {
|
||||
key: "setPageSize",
|
||||
value: function setPageSize(num) {
|
||||
this.pageSize = num;
|
||||
}
|
||||
}, {
|
||||
key: "pages",
|
||||
value: function pages() {
|
||||
return Math.ceil(this.totalRows() / this.pageSize);
|
||||
}
|
||||
}, {
|
||||
key: "totalRows",
|
||||
value: function totalRows() {
|
||||
return this.bucketMatrix.targets.length;
|
||||
}
|
||||
}, {
|
||||
key: "pageStartRow",
|
||||
value: function pageStartRow() {
|
||||
if (!this.enable) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return this.pageSize * this.currentPage + 1;
|
||||
}
|
||||
}, {
|
||||
key: "pageEndRow",
|
||||
value: function pageEndRow() {
|
||||
if (!this.enable) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
var last = this.pageSize * (this.currentPage + 1);
|
||||
|
||||
if (last > this.totalRows()) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
}, {
|
||||
key: "hasNext",
|
||||
value: function hasNext() {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.currentPage + 1) * this.pageSize >= this.totalRows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // currentPage >= ctrl.bucketMatrix.targets.length/ctrl.pageSizeViewer - 1 || ctrl.numberOfPages == 0
|
||||
}
|
||||
}, {
|
||||
key: "hasPrev",
|
||||
value: function hasPrev() {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.currentPage > 0;
|
||||
}
|
||||
}, {
|
||||
key: "switchToNext",
|
||||
value: function switchToNext() {
|
||||
if (this.hasNext()) {
|
||||
this.currentPage = this.currentPage + 1;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "switchToPrev",
|
||||
value: function switchToPrev() {
|
||||
if (this.hasPrev()) {
|
||||
this.currentPage = this.currentPage - 1;
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return BucketMatrixPager;
|
||||
}());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+217
-43
@@ -3,7 +3,7 @@
|
||||
System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var d3, $, _, TOOLTIP_PADDING_X, TOOLTIP_PADDING_Y, StatusmapTooltip;
|
||||
var d3, $, _, TOOLTIP_PADDING_X, TOOLTIP_PADDING_Y, DefaultValueDateFormat, StatusmapTooltip;
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
@@ -23,7 +23,10 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
}],
|
||||
execute: function () {
|
||||
TOOLTIP_PADDING_X = 30;
|
||||
TOOLTIP_PADDING_Y = 5;
|
||||
TOOLTIP_PADDING_Y = 5; // TODO rename file to tooltip_ctrl.ts
|
||||
// TODO move DefaultValueDateFormat into tooltip.ts, as it used in several places (migration, partial and tooltip render)
|
||||
|
||||
DefaultValueDateFormat = 'YYYY/MM/DD/HH_mm_ss';
|
||||
|
||||
_export("StatusmapTooltip", StatusmapTooltip =
|
||||
/*#__PURE__*/
|
||||
@@ -41,17 +44,21 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
|
||||
_defineProperty(this, "panel", void 0);
|
||||
|
||||
_defineProperty(this, "heatmapPanel", void 0);
|
||||
_defineProperty(this, "panelElem", void 0);
|
||||
|
||||
_defineProperty(this, "mouseOverBucket", void 0);
|
||||
|
||||
_defineProperty(this, "originalFillColor", void 0);
|
||||
|
||||
_defineProperty(this, "tooltipWidth", void 0);
|
||||
|
||||
_defineProperty(this, "tooltipFrozen", void 0);
|
||||
|
||||
this.scope = scope;
|
||||
this.dashboard = scope.ctrl.dashboard;
|
||||
this.panelCtrl = scope.ctrl;
|
||||
this.panel = scope.ctrl.panel;
|
||||
this.heatmapPanel = elem;
|
||||
this.panelElem = elem;
|
||||
this.mouseOverBucket = false;
|
||||
this.originalFillColor = null;
|
||||
elem.on("mouseover", this.onMouseOver.bind(this));
|
||||
@@ -67,7 +74,7 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
|
||||
if (!this.tooltip) {
|
||||
this.add();
|
||||
this.move(e);
|
||||
this.move(e, this.tooltip);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
@@ -82,12 +89,12 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.move(e);
|
||||
this.move(e, this.tooltip);
|
||||
}
|
||||
}, {
|
||||
key: "add",
|
||||
value: function add() {
|
||||
this.tooltip = d3.select("body").append("div").attr("class", "statusmap-tooltip graph-tooltip grafana-tooltip");
|
||||
this.tooltip = d3.select("body").append("div").attr("class", "graph-tooltip statusmap-tooltip");
|
||||
}
|
||||
}, {
|
||||
key: "destroy",
|
||||
@@ -98,90 +105,225 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
|
||||
this.tooltip = null;
|
||||
}
|
||||
}, {
|
||||
key: "removeFrozen",
|
||||
value: function removeFrozen() {
|
||||
if (this.tooltipFrozen) {
|
||||
this.tooltipFrozen.remove();
|
||||
this.tooltipFrozen = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "showFrozen",
|
||||
value: function showFrozen(pos) {
|
||||
this.removeFrozen();
|
||||
this.tooltipFrozen = d3.select(this.panelElem[0]).append("div").attr("class", "graph-tooltip statusmap-tooltip statusmap-tooltip-frozen");
|
||||
this.displayTooltip(pos, this.tooltipFrozen, true);
|
||||
this.moveRelative(pos, this.tooltipFrozen);
|
||||
}
|
||||
}, {
|
||||
key: "show",
|
||||
value: function show(pos) {
|
||||
if (!this.panel.tooltip.show || !this.tooltip) {
|
||||
return;
|
||||
} // shared tooltip mode
|
||||
} // TODO support for shared tooltip mode
|
||||
|
||||
|
||||
if (pos.panelRelY) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cardId = d3.select(pos.target).attr('cardId');
|
||||
this.displayTooltip(pos, this.tooltip, false);
|
||||
this.move(pos, this.tooltip);
|
||||
} // Retrieve bucket and create html content inside tooltip’s div element.
|
||||
|
||||
if (!cardId) {
|
||||
}, {
|
||||
key: "displayTooltip",
|
||||
value: function displayTooltip(pos, tooltip, frozen) {
|
||||
var cardEl = d3.select(pos.target);
|
||||
var yid = cardEl.attr('yid');
|
||||
var xid = cardEl.attr('xid');
|
||||
var bucket = this.panelCtrl.bucketMatrix.get(yid, xid); // TODO string-to-number conversion for xid
|
||||
|
||||
if (!bucket || bucket.isEmpty()) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
var card = this.panelCtrl.cardsData.cards[cardId];
|
||||
var timestamp = bucket.to;
|
||||
var yLabel = bucket.yLabel;
|
||||
var value = bucket.value;
|
||||
var values = bucket.values; // TODO create option for this formatting.
|
||||
|
||||
if (!card) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
var x = card.x;
|
||||
var y = card.y;
|
||||
var value = card.value;
|
||||
var values = card.values;
|
||||
var tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
var time = this.dashboard.formatDate(+x, tooltipTimeFormat);
|
||||
var tooltipHtml = "<div class=\"graph-tooltip-time\">".concat(time, "</div>\n <div class=\"statusmap-histogram\"></div>");
|
||||
var time = this.dashboard.formatDate(+timestamp, tooltipTimeFormat); // Close button for the frozen tooltip.
|
||||
|
||||
var tooltipClose = "";
|
||||
|
||||
if (frozen) {
|
||||
tooltipClose = "\n<a class=\"pointer pull-right small tooltip-close\">\n <i class=\"fa fa-remove\"></i>\n</a>\n";
|
||||
}
|
||||
|
||||
var tooltipHtml = "<div class=\"graph-tooltip-time\">".concat(time).concat(tooltipClose, "</div>");
|
||||
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
var statuses = this.panelCtrl.discreteHelper.convertValuesToTooltips(values);
|
||||
var statusesHtml = '';
|
||||
var statuses;
|
||||
|
||||
if (statuses.length === 1) {
|
||||
statusesHtml = "status:";
|
||||
} else if (statuses.length > 1) {
|
||||
statusesHtml = "statuses:";
|
||||
if (this.panel.seriesFilterIndex >= 0) {
|
||||
statuses = this.panelCtrl.discreteExtraSeries.convertValueToTooltips(value);
|
||||
} else {
|
||||
statuses = this.panelCtrl.discreteExtraSeries.convertValuesToTooltips(values);
|
||||
}
|
||||
|
||||
tooltipHtml += "\n <div>\n name: <b>".concat(y, "</b> <br>\n ").concat(statusesHtml, "\n <ul>\n ").concat(_.join(_.map(statuses, function (v) {
|
||||
return "<li style=\"background-color: ".concat(v.color, "\" class=\"discrete-item\">").concat(v.tooltip, "</li>");
|
||||
var statusTitle = "status:";
|
||||
|
||||
if (statuses.length > 1) {
|
||||
statusTitle = "statuses:";
|
||||
}
|
||||
|
||||
tooltipHtml += "\n <div>\n name: <b>".concat(yLabel, "</b>\n <br>\n <span>").concat(statusTitle, "</span>\n <ul>\n ").concat(_.join(_.map(statuses, function (v) {
|
||||
return "<li style=\"background-color: ".concat(v.color, "; text-align:center\" class=\"discrete-item\">").concat(v.tooltip, "</li>");
|
||||
}), ""), "\n </ul>\n </div>");
|
||||
} else {
|
||||
if (values.length === 1) {
|
||||
tooltipHtml += "<div> \n name: <b>".concat(y, "</b> <br>\n value: <b>").concat(value, "</b> <br>\n </div>");
|
||||
tooltipHtml += "<div> \n name: <b>".concat(name, "</b> <br>\n value: <b>").concat(value, "</b> <br>\n </div>");
|
||||
} else {
|
||||
tooltipHtml += "<div>\n name: <b>".concat(y, "</b> <br>\n values:\n <ul>\n ").concat(_.join(_.map(values, function (v) {
|
||||
tooltipHtml += "<div>\n name: <b>".concat(name, "</b> <br>\n values:\n <ul>\n ").concat(_.join(_.map(values, function (v) {
|
||||
return "<li>".concat(v, "</li>");
|
||||
}), ""), "\n </ul>\n </div>");
|
||||
}
|
||||
} // "Ambiguous bucket state: Multiple values!";
|
||||
}
|
||||
|
||||
tooltipHtml += "<div class=\"statusmap-histogram\"></div>";
|
||||
|
||||
if (this.panel.tooltip.showItems) {
|
||||
// Additional information: urls, etc.
|
||||
// Clone additional items
|
||||
var items = JSON.parse(JSON.stringify(this.panel.tooltip.items));
|
||||
var scopedVars = {};
|
||||
var valueVar;
|
||||
|
||||
for (var i = 0; i < bucket.values.length; i++) {
|
||||
valueVar = "__value_".concat(i);
|
||||
scopedVars[valueVar] = {
|
||||
value: bucket.values[i]
|
||||
};
|
||||
}
|
||||
|
||||
scopedVars["__value"] = {
|
||||
value: bucket.value
|
||||
};
|
||||
scopedVars["__y_label"] = {
|
||||
value: yLabel
|
||||
}; // Grafana 7.0 compatible
|
||||
|
||||
scopedVars["__url_time_range"] = {
|
||||
value: this.panelCtrl.retrieveTimeVar()
|
||||
};
|
||||
var _iteratorNormalCompletion = true;
|
||||
var _didIteratorError = false;
|
||||
var _iteratorError = undefined;
|
||||
|
||||
try {
|
||||
for (var _iterator = items[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||
var item = _step.value;
|
||||
|
||||
if (_.isEmpty(item.urlTemplate)) {
|
||||
item.link = "#";
|
||||
} else {
|
||||
var dateFormat = item.valueDateFormat;
|
||||
|
||||
if (dateFormat == '') {
|
||||
dateFormat = DefaultValueDateFormat;
|
||||
}
|
||||
|
||||
var valueDateVar = void 0;
|
||||
|
||||
for (var _i = 0; _i < bucket.values.length; _i++) {
|
||||
valueDateVar = "__value_".concat(_i, "_date");
|
||||
scopedVars[valueDateVar] = {
|
||||
value: this.dashboard.formatDate(+bucket.values[_i], dateFormat)
|
||||
};
|
||||
}
|
||||
|
||||
scopedVars["__value_date"] = {
|
||||
value: this.dashboard.formatDate(+bucket.value, dateFormat)
|
||||
};
|
||||
item.link = this.panelCtrl.templateSrv.replace(item.urlTemplate, scopedVars); // Force lowercase for link
|
||||
|
||||
if (item.urlToLowerCase) {
|
||||
item.link = item.link.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
item.label = item.urlText;
|
||||
|
||||
if (_.isEmpty(item.label)) {
|
||||
item.label = _.isEmpty(item.urlTemplate) ? "Empty URL" : _.truncate(item.link);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
_didIteratorError = true;
|
||||
_iteratorError = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
|
||||
_iterator["return"]();
|
||||
}
|
||||
} finally {
|
||||
if (_didIteratorError) {
|
||||
throw _iteratorError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tooltipHtml += _.join(_.map(items, function (v) {
|
||||
return "\n <div>\n <a href=\"".concat(v.link, "\" target=\"_blank\">\n <div class=\"dashlist-item\">\n <p class=\"dashlist-link dashlist-link-dash-db\">\n <span style=\"word-wrap: break-word;\" class=\"dash-title\">").concat(v.label, "</span><span class=\"dashlist-star\">\n <i class=\"fa fa-").concat(v.urlIcon, "\"></i>\n </span></p> </div></a><div>");
|
||||
}), "\n");
|
||||
} // Ambiguous state: there multiple values in bucket!
|
||||
// TODO rename useMax to expectMultipleValues
|
||||
|
||||
|
||||
if (!this.panel.useMax && card.multipleValues) {
|
||||
if (!this.panel.useMax && bucket.multipleValues) {
|
||||
tooltipHtml += "<div><b>Error:</b> ".concat(this.panelCtrl.dataWarnings.multipleValues.title, "</div>");
|
||||
} // Discrete mode errors
|
||||
|
||||
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
if (card.noColorDefined) {
|
||||
var badValues = this.panelCtrl.discreteHelper.getNotColoredValues(values);
|
||||
if (bucket.noColorDefined) {
|
||||
var badValues = this.panelCtrl.discreteExtraSeries.getNotColoredValues(values);
|
||||
tooltipHtml += "<div><b>Error:</b> ".concat(this.panelCtrl.dataWarnings.noColorDefined.title, "\n <br>not colored values:\n <ul>\n ").concat(_.join(_.map(badValues, function (v) {
|
||||
return "<li>".concat(v, "</li>");
|
||||
}), ""), "\n </ul>\n </div>");
|
||||
}
|
||||
}
|
||||
|
||||
this.tooltip.html(tooltipHtml);
|
||||
this.move(pos);
|
||||
}
|
||||
tooltip.html(tooltipHtml); // Assign mouse event handlers for "frozen" tooltip.
|
||||
|
||||
if (frozen) {
|
||||
// Stop propagation mouse events up to parents to allow interaction with frozen tooltip’s elements.
|
||||
tooltip.on("click", function () {
|
||||
d3.event.stopPropagation();
|
||||
}).on("mousedown", function () {
|
||||
d3.event.stopPropagation();
|
||||
}).on("mouseup", function () {
|
||||
d3.event.stopPropagation();
|
||||
}); // Activate close button
|
||||
|
||||
tooltip.select("a.tooltip-close").on("click", this.removeFrozen.bind(this));
|
||||
}
|
||||
} // Move tooltip as absolute positioned element.
|
||||
|
||||
}, {
|
||||
key: "move",
|
||||
value: function move(pos) {
|
||||
if (!this.tooltip) {
|
||||
value: function move(pos, tooltip) {
|
||||
if (!tooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
var elem = $(this.tooltip.node())[0];
|
||||
var elem = $(tooltip.node())[0];
|
||||
var tooltipWidth = elem.clientWidth;
|
||||
this.tooltipWidth = tooltipWidth;
|
||||
var tooltipHeight = elem.clientHeight;
|
||||
var left = pos.pageX + TOOLTIP_PADDING_X;
|
||||
var top = pos.pageY + TOOLTIP_PADDING_Y;
|
||||
@@ -194,7 +336,39 @@ System.register(["d3", "jquery", "lodash"], function (_export, _context) {
|
||||
top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y;
|
||||
}
|
||||
|
||||
return this.tooltip.style("left", left + "px").style("top", top + "px");
|
||||
return tooltip.style("left", left + "px").style("top", top + "px");
|
||||
} // Move tooltip relative to svg element of panel.
|
||||
|
||||
}, {
|
||||
key: "moveRelative",
|
||||
value: function moveRelative(pos, tooltip) {
|
||||
if (!tooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panelX = pos.pageX - this.panelElem.offset().left;
|
||||
var panelY = pos.pageY - this.panelElem.offset().top;
|
||||
var panelWidth = this.panelElem.width();
|
||||
var panelHeight = this.panelElem.height(); // 'position: relative' sets tooltip’s width to 100% of panel element.
|
||||
// Restore width from floating tooltip and add more space for 'Close' button.
|
||||
|
||||
var tooltipWidth = this.tooltipWidth + 25; // Left property is clamped so tooltip stays inside panel bound box.
|
||||
|
||||
var tooltipLeft = panelX + TOOLTIP_PADDING_X;
|
||||
|
||||
if (tooltipLeft + tooltipWidth > panelWidth) {
|
||||
tooltipLeft = panelWidth - tooltipWidth;
|
||||
}
|
||||
|
||||
if (tooltipLeft < 0) {
|
||||
tooltipLeft = 0;
|
||||
} // Frozen tooltip’s root element is appended next to panel’s svg element,
|
||||
// so top property is adjusted to move tooltip’s root element
|
||||
// up to the mouse pointer position.
|
||||
|
||||
|
||||
var tooltipTop = -(panelHeight - panelY + TOOLTIP_PADDING_Y);
|
||||
return tooltip.style("left", tooltipLeft + "px").style("top", tooltipTop + "px").style("width", tooltipWidth + "px");
|
||||
}
|
||||
}]);
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
|
||||
System.register([], function (_export, _context) {
|
||||
"use strict";
|
||||
|
||||
var emptyTooltipItem, TooltipEditorCtrl;
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
function tooltipEditorCtrl() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
templateUrl: 'public/plugins/flant-statusmap-panel/partials/tooltip_editor.html',
|
||||
controller: TooltipEditorCtrl
|
||||
};
|
||||
}
|
||||
|
||||
_export("tooltipEditorCtrl", tooltipEditorCtrl);
|
||||
|
||||
return {
|
||||
setters: [],
|
||||
execute: function () {
|
||||
emptyTooltipItem = {
|
||||
urlText: '',
|
||||
urlTemplate: '',
|
||||
urlIcon: 'external-link',
|
||||
urlToLowerCase: true,
|
||||
valueDateFormat: ''
|
||||
};
|
||||
|
||||
_export("TooltipEditorCtrl", TooltipEditorCtrl =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
function TooltipEditorCtrl($scope) {
|
||||
_classCallCheck(this, TooltipEditorCtrl);
|
||||
|
||||
_defineProperty(this, "panel", void 0);
|
||||
|
||||
_defineProperty(this, "panelCtrl", void 0);
|
||||
|
||||
$scope.editor = this;
|
||||
this.panelCtrl = $scope.ctrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
}
|
||||
|
||||
_createClass(TooltipEditorCtrl, [{
|
||||
key: "render",
|
||||
value: function render() {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
}, {
|
||||
key: "onAddUrl",
|
||||
value: function onAddUrl() {
|
||||
this.panel.tooltip.items.push(Object.assign({}, emptyTooltipItem));
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onRemoveUrl",
|
||||
value: function onRemoveUrl(index) {
|
||||
this.panel.tooltip.items.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "onRemoveUrls",
|
||||
value: function onRemoveUrls() {
|
||||
this.panel.tooltip.items = [];
|
||||
this.render();
|
||||
}
|
||||
}, {
|
||||
key: "getFAIconClasses",
|
||||
value: function getFAIconClasses() {
|
||||
return ['external-link', 'plus', 'anchor', 'ban', 'globe', 'gear', 'cloud', 'download', 'cloud-download'];
|
||||
}
|
||||
}, {
|
||||
key: "getValueDateFormats",
|
||||
value: function getValueDateFormats() {
|
||||
return ['YYYY/MM/DD/HH_mm_ss', 'YYYYMMDDHHmmss', 'YYYY-MM-DD-HH-mm-ss'];
|
||||
}
|
||||
}]);
|
||||
|
||||
return TooltipEditorCtrl;
|
||||
}());
|
||||
}
|
||||
};
|
||||
});
|
||||
//# sourceMappingURL=tooltip_editor.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../src/tooltip_editor.ts"],"names":["tooltipEditorCtrl","restrict","scope","templateUrl","controller","TooltipEditorCtrl","emptyTooltipItem","urlText","urlTemplate","urlIcon","urlToLowerCase","valueDateFormat","$scope","editor","panelCtrl","ctrl","panel","render","tooltip","items","push","Object","assign","index","splice"],"mappings":";;;;;;;;;;;;;;;AAoDO,WAASA,iBAAT,GAA6B;AAClC;;AACA,WAAO;AACLC,MAAAA,QAAQ,EAAE,GADL;AAELC,MAAAA,KAAK,EAAE,IAFF;AAGLC,MAAAA,WAAW,EAAE,mEAHR;AAILC,MAAAA,UAAU,EAAEC;AAJP,KAAP;AAMD;;+BAReL,iB;;;;;AAlDZM,MAAAA,gB,GAAmB;AACrBC,QAAAA,OAAO,EAAE,EADY;AAErBC,QAAAA,WAAW,EAAE,EAFQ;AAGrBC,QAAAA,OAAO,EAAE,eAHY;AAIrBC,QAAAA,cAAc,EAAE,IAJK;AAKrBC,QAAAA,eAAe,EAAE;AALI,O;;mCAQVN,iB;;;AAIX,mCAAYO,MAAZ,EAAyB;AAAA;;AAAA;;AAAA;;AACvBA,UAAAA,MAAM,CAACC,MAAP,GAAgB,IAAhB;AACA,eAAKC,SAAL,GAAiBF,MAAM,CAACG,IAAxB;AACA,eAAKC,KAAL,GAAa,KAAKF,SAAL,CAAeE,KAA5B;AACD;;;;mCAEQ;AACP,iBAAKF,SAAL,CAAeG,MAAf;AACD;;;qCAEU;AACT,iBAAKD,KAAL,CAAWE,OAAX,CAAmBC,KAAnB,CAAyBC,IAAzB,CAA8BC,MAAM,CAACC,MAAP,CAAc,EAAd,EAAkBhB,gBAAlB,CAA9B;AACA,iBAAKW,MAAL;AACD;;;sCAEWM,K,EAAe;AACzB,iBAAKP,KAAL,CAAWE,OAAX,CAAmBC,KAAnB,CAAyBK,MAAzB,CAAgCD,KAAhC,EAAuC,CAAvC;AACA,iBAAKN,MAAL;AACD;;;yCAEc;AACb,iBAAKD,KAAL,CAAWE,OAAX,CAAmBC,KAAnB,GAA2B,EAA3B;AACA,iBAAKF,MAAL;AACD;;;6CAEkB;AACjB,mBAAO,CAAC,eAAD,EAAkB,MAAlB,EAA0B,QAA1B,EAAoC,KAApC,EAA2C,OAA3C,EAAoD,MAApD,EAA4D,OAA5D,EAAqE,UAArE,EAAiF,gBAAjF,CAAP;AACD;;;gDAEqB;AACpB,mBAAO,CACH,qBADG,EAEH,gBAFG,EAGH,qBAHG,CAAP;AAKD","sourcesContent":["import { StatusHeatmapCtrl } from './module';\n\nlet emptyTooltipItem = {\n urlText: '',\n urlTemplate: '',\n urlIcon: 'external-link',\n urlToLowerCase: true,\n valueDateFormat: ''\n};\n\nexport class TooltipEditorCtrl {\n panel: any;\n panelCtrl: StatusHeatmapCtrl;\n\n constructor($scope: any) {\n $scope.editor = this;\n this.panelCtrl = $scope.ctrl as StatusHeatmapCtrl;\n this.panel = this.panelCtrl.panel;\n }\n\n render() {\n this.panelCtrl.render();\n }\n\n onAddUrl() {\n this.panel.tooltip.items.push(Object.assign({}, emptyTooltipItem));\n this.render();\n }\n\n onRemoveUrl(index: number) {\n this.panel.tooltip.items.splice(index, 1);\n this.render();\n }\n\n onRemoveUrls() {\n this.panel.tooltip.items = [];\n this.render();\n }\n\n getFAIconClasses() {\n return ['external-link', 'plus', 'anchor', 'ban', 'globe', 'gear', 'cloud', 'download', 'cloud-download'];\n }\n\n getValueDateFormats() {\n return [\n 'YYYY/MM/DD/HH_mm_ss',\n 'YYYYMMDDHHmmss',\n 'YYYY-MM-DD-HH-mm-ss'\n ];\n }\n}\n\nexport function tooltipEditorCtrl() {\n 'use strict';\n return {\n restrict: 'E',\n scope: true,\n templateUrl: 'public/plugins/flant-statusmap-panel/partials/tooltip_editor.html',\n controller: TooltipEditorCtrl,\n };\n}\n"],"file":"tooltip_editor.js"}
|
||||
+14
-18
@@ -63,10 +63,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"decimals": null,
|
||||
"unitFormat": "short"
|
||||
},
|
||||
"datasource": "prometheus",
|
||||
"gridPos": {
|
||||
"h": 16,
|
||||
@@ -130,21 +126,21 @@
|
||||
],
|
||||
"title": "Pod Status",
|
||||
"tooltip": {
|
||||
"show": true
|
||||
"show": true,
|
||||
"freezeOnClick": false,
|
||||
"showItems": false,
|
||||
"items": []
|
||||
},
|
||||
"transparent": true,
|
||||
"type": "flant-statusmap-panel",
|
||||
"useMax": true,
|
||||
"xAxis": {
|
||||
"labelFormat": "%a %m/%d",
|
||||
"minBucketWidthToShowWeekends": 4,
|
||||
"show": true,
|
||||
"showCrosshair": true,
|
||||
"showWeekends": true
|
||||
"show": true
|
||||
},
|
||||
"yAxis": {
|
||||
"show": true,
|
||||
"showCrosshair": false
|
||||
"minWidth": -1,
|
||||
"maxWidth": -1
|
||||
},
|
||||
"yAxisSort": "a → z"
|
||||
},
|
||||
@@ -310,21 +306,21 @@
|
||||
],
|
||||
"title": "Container Status",
|
||||
"tooltip": {
|
||||
"show": true
|
||||
"show": true,
|
||||
"freezeOnClick": true,
|
||||
"showItems": false,
|
||||
"items": []
|
||||
},
|
||||
"transparent": true,
|
||||
"type": "flant-statusmap-panel",
|
||||
"useMax": true,
|
||||
"xAxis": {
|
||||
"labelFormat": "%a %m/%d",
|
||||
"minBucketWidthToShowWeekends": 4,
|
||||
"show": true,
|
||||
"showCrosshair": true,
|
||||
"showWeekends": true
|
||||
"show": true
|
||||
},
|
||||
"yAxis": {
|
||||
"show": true,
|
||||
"showCrosshair": false
|
||||
"minWidth": -1,
|
||||
"maxWidth": -1
|
||||
},
|
||||
"yAxisSort": "a → z"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
verbose: false,
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
},
|
||||
moduleDirectories: ['node_modules'],
|
||||
roots: ['<rootDir>/src'],
|
||||
testRegex: '(\\.|/)(test)\\.(jsx?|tsx?)$',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
||||
//setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'],
|
||||
//snapshotSerializers: ['enzyme-to-json/serializer'],
|
||||
globals: { 'ts-jest': { isolatedModules: true } },
|
||||
};
|
||||
Generated
+2237
-7
File diff suppressed because it is too large
Load Diff
+10
-3
@@ -5,7 +5,8 @@
|
||||
"main": "README.md",
|
||||
"scripts": {
|
||||
"build": "grunt",
|
||||
"check-types": "node_modules/typescript/bin/tsc"
|
||||
"check-types": "node_modules/typescript/bin/tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "Flant JSC",
|
||||
"license": "Apache-2.0",
|
||||
@@ -26,6 +27,7 @@
|
||||
"@types/d3-scale-chromatic": "1.3.1",
|
||||
"@types/angular": "1.6.56",
|
||||
"@types/grafana": "github:flant/types-grafana",
|
||||
"@types/jest": "24.0.13",
|
||||
"@types/jquery": "1.10.35",
|
||||
"@types/lodash": "4.14.123",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
@@ -43,13 +45,18 @@
|
||||
"grunt-contrib-uglify": "^3.0.1",
|
||||
"grunt-contrib-watch": "^1.0.0",
|
||||
"grunt-notify": "^0.4.5",
|
||||
"jest": "24.8.0",
|
||||
"jest-canvas-mock": "2.1.2",
|
||||
"jest-date-mock": "1.0.7",
|
||||
"load-grunt-tasks": "^3.5.2",
|
||||
"typescript": "3.7.2"
|
||||
"typescript": "3.7.2",
|
||||
"ts-jest": "24.1.0",
|
||||
"tslib": "1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "4.13.0",
|
||||
"d3-scale-chromatic": "1.5.0",
|
||||
"lodash": "4.17.15",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.24.0"
|
||||
}
|
||||
}
|
||||
|
||||
+37
-23
@@ -5,6 +5,8 @@ import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
|
||||
import {contextSrv} from 'app/core/core';
|
||||
import {tickStep} from 'app/core/utils/ticks';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { StatusHeatmapCtrl } from "./module";
|
||||
import { PanelEvents } from './libs/grafana/events/index';
|
||||
|
||||
const LEGEND_STEP_WIDTH = 2;
|
||||
|
||||
@@ -15,13 +17,13 @@ coreModule.directive('optionsColorLegend', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<div class="status-heatmap-color-legend"><svg width="16.8rem" height="24px"></svg></div>',
|
||||
link: function(scope, elem, attrs) {
|
||||
link: function(scope: any, elem, attrs) {
|
||||
let ctrl = scope.ctrl;
|
||||
let panel = scope.ctrl.panel;
|
||||
|
||||
render();
|
||||
|
||||
ctrl.events.on('render', function() {
|
||||
ctrl.events.on(PanelEvents.render, function() {
|
||||
render();
|
||||
});
|
||||
|
||||
@@ -50,25 +52,38 @@ coreModule.directive('statusHeatmapLegend', function() {
|
||||
restrict: 'E',
|
||||
template: '<div class="status-heatmap-color-legend"><svg width="100px" height="6px"></svg></div>',
|
||||
link: function(scope, elem, attrs) {
|
||||
let ctrl = scope.ctrl;
|
||||
let ctrl:StatusHeatmapCtrl = scope.ctrl;
|
||||
let panel = scope.ctrl.panel;
|
||||
|
||||
render();
|
||||
ctrl.events.on('render', function() {
|
||||
ctrl.events.on(PanelEvents.render, function() {
|
||||
render();
|
||||
});
|
||||
|
||||
function render() {
|
||||
clearLegend(elem);
|
||||
if (!ctrl.panel.legend.show) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) {
|
||||
let rangeFrom = ctrl.cardsData.minValue;
|
||||
let rangeTo = ctrl.cardsData.maxValue;
|
||||
if (ctrl.bucketMatrix) {
|
||||
let rangeFrom = ctrl.bucketMatrix.minValue;
|
||||
let rangeTo = ctrl.bucketMatrix.maxValue;
|
||||
let maxValue = panel.color.max || rangeTo;
|
||||
let minValue = panel.color.min || rangeFrom;
|
||||
|
||||
if (ctrl.bucketMatrix.noDatapoints) {
|
||||
if (!panel.color.max) {
|
||||
rangeTo = maxValue = 100;
|
||||
} else {
|
||||
rangeTo = 100;
|
||||
}
|
||||
if (!panel.color.min) {
|
||||
rangeFrom = minValue = 0;
|
||||
} else {
|
||||
rangeFrom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.color.mode === 'spectrum') {
|
||||
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
|
||||
drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue);
|
||||
@@ -77,7 +92,7 @@ coreModule.directive('statusHeatmapLegend', function() {
|
||||
drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue);
|
||||
} else if (panel.color.mode === 'discrete') {
|
||||
let colorOptions = panel.color;
|
||||
drawDiscreteColorLegend(elem, colorOptions, ctrl.discreteHelper);
|
||||
drawDiscreteColorLegend(elem, colorOptions, ctrl.discreteExtraSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,9 +100,9 @@ coreModule.directive('statusHeatmapLegend', function() {
|
||||
};
|
||||
});
|
||||
|
||||
function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) {
|
||||
function drawColorLegend(elem, colorScheme, rangeFrom: number, rangeTo:number, maxValue: number, minValue:number) {
|
||||
let legendElem = $(elem).find('svg');
|
||||
let legend = d3.select(legendElem.get(0));
|
||||
let legend: any = d3.select(legendElem.get(0));
|
||||
clearLegend(elem);
|
||||
|
||||
let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values
|
||||
@@ -105,7 +120,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
|
||||
.enter().append("rect")
|
||||
// translate from range space into pixels
|
||||
// and shift all rectangles to the right by 10
|
||||
.attr("x", d => d * widthFactor+10)
|
||||
.attr("x", d => ((d - rangeFrom) * widthFactor)+10)
|
||||
.attr("y", 0)
|
||||
// rectangles are slightly overlaped to prevent gaps
|
||||
.attr("width", LEGEND_STEP_WIDTH+1)
|
||||
@@ -148,7 +163,7 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
|
||||
drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
|
||||
}
|
||||
|
||||
function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
|
||||
function drawDiscreteColorLegend(elem, colorOptions, discreteExtraSeries) {
|
||||
let legendElem = $(elem).find('svg');
|
||||
let legend = d3.select(legendElem.get(0));
|
||||
clearLegend(elem);
|
||||
@@ -158,7 +173,7 @@ function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
|
||||
let valuesNumber = thresholds.length;
|
||||
|
||||
// graph width as a fallback
|
||||
const $heatmap = $(elem).parent().parent().parent().find('.status-heatmap-panel');
|
||||
const $heatmap = $(elem).parent().parent().parent().find('.statusmap-panel');
|
||||
const graphWidthAttr = $heatmap.find('svg').attr("width");
|
||||
let graphWidth = parseInt(graphWidthAttr);
|
||||
|
||||
@@ -196,13 +211,13 @@ function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
|
||||
.attr("width", itemWidth + 1) // Overlap rectangles to prevent gaps
|
||||
.attr("height", legendHeight)
|
||||
.attr("stroke-width", 0)
|
||||
.attr("fill", d => discreteHelper.getDiscreteColor(d));
|
||||
.attr("fill", d => discreteExtraSeries.getDiscreteColor(d));
|
||||
|
||||
drawDiscreteLegendValues(elem, colorOptions, legendWidth);
|
||||
}
|
||||
|
||||
|
||||
function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) {
|
||||
function drawLegendValues(elem, colorScale, rangeFrom: number, rangeTo: number, maxValue: number, minValue: number, legendWidth: number) {
|
||||
let legendElem = $(elem).find('svg');
|
||||
let legend = d3.select(legendElem.get(0));
|
||||
|
||||
@@ -211,10 +226,10 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
|
||||
}
|
||||
|
||||
let legendValueScale = d3.scaleLinear()
|
||||
.domain([0, rangeTo])
|
||||
.domain([rangeFrom, rangeTo])
|
||||
.range([0, legendWidth]);
|
||||
|
||||
let ticks = buildLegendTicks(0, rangeTo, maxValue, minValue);
|
||||
let ticks = buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue);
|
||||
let xAxis = d3.axisBottom(legendValueScale)
|
||||
.tickValues(ticks)
|
||||
.tickSize(2);
|
||||
@@ -243,8 +258,7 @@ function drawDiscreteLegendValues(elem, colorOptions, legendWidth) {
|
||||
|
||||
let valuesNumber = thresholds.length;
|
||||
let rangeStep = Math.floor(legendWidth / valuesNumber);
|
||||
let valuesRange = d3.range(0, legendWidth, rangeStep);
|
||||
|
||||
//let valuesRange = d3.range(0, legendWidth, rangeStep);
|
||||
|
||||
let legendValueScale = d3.scaleLinear()
|
||||
.domain([0, valuesNumber])
|
||||
@@ -261,7 +275,7 @@ function drawDiscreteLegendValues(elem, colorOptions, legendWidth) {
|
||||
.tickValues(d3.range(0, valuesNumber, 1)) //thresholdValues)
|
||||
.tickSize(2)
|
||||
.tickFormat((t) => {
|
||||
let i = Math.floor(t);
|
||||
let i = Math.floor(t.valueOf());
|
||||
let v = thresholdTooltips[i];
|
||||
if (v != undefined) {
|
||||
return ""+v;
|
||||
@@ -401,7 +415,7 @@ function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) {
|
||||
let ticks:any = [];
|
||||
|
||||
for (let i = 0; i < ticksNum; i++) {
|
||||
let current = tickStepSize * i;
|
||||
let current = tickStepSize * i + rangeFrom;
|
||||
// Add user-defined min and max if it had been set
|
||||
if (isValueCloseTo(minValue, current, tickStepSize)) {
|
||||
ticks.push(minValue);
|
||||
@@ -415,7 +429,7 @@ function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) {
|
||||
} else if (maxValue < current) {
|
||||
ticks.push(maxValue);
|
||||
}
|
||||
ticks.push(tickStepSize * i);
|
||||
ticks.push(current);
|
||||
}
|
||||
if (!isValueCloseTo(maxValue, rangeTo, tickStepSize)) {
|
||||
ticks.push(maxValue);
|
||||
|
||||
+70
-16
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import {StatusHeatmapCtrl} from "./status_heatmap_ctrl";
|
||||
import { Bucket } from "./statusmap_data";
|
||||
import { StatusHeatmapCtrl } from "./module";
|
||||
|
||||
interface Tooltip {
|
||||
tooltip: string;
|
||||
@@ -12,7 +13,7 @@ declare class DiscreteColorThreshold {
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
// Helper methods to handle discrete color mode
|
||||
// Extra Series methods to handle discrete color mode
|
||||
export class ColorModeDiscrete {
|
||||
scope: any;
|
||||
panelCtrl: StatusHeatmapCtrl;
|
||||
@@ -42,6 +43,23 @@ export class ColorModeDiscrete {
|
||||
return tooltips;
|
||||
}
|
||||
|
||||
convertValueToTooltips(values) {
|
||||
let thresholds = this.panel.color.thresholds;
|
||||
let tooltips = [];
|
||||
|
||||
for (let i = 0; i < thresholds.length; i++) {
|
||||
//for (let j = 0; j < values.length; j++) {
|
||||
if (values == thresholds[i].value) {
|
||||
tooltips.push({
|
||||
"tooltip": thresholds[i].tooltip?thresholds[i].tooltip:values,
|
||||
"color": thresholds[i].color
|
||||
});
|
||||
//}
|
||||
}
|
||||
}
|
||||
return tooltips;
|
||||
}
|
||||
|
||||
getNotMatchedValues(values:any[]) {
|
||||
let notMatched:any[] = [];
|
||||
for (let j = 0; j < values.length; j++) {
|
||||
@@ -71,6 +89,22 @@ export class ColorModeDiscrete {
|
||||
return color;
|
||||
}
|
||||
|
||||
getBucketColorSingle(value) {
|
||||
//let thresholds = this.panel.color.thresholds;
|
||||
if (value == null) {
|
||||
// treat as null value
|
||||
return 'rgba(0,0,0,1)';
|
||||
//return this.getMatchedThreshold(null).color;
|
||||
}
|
||||
let threshold = this.getMatchedThreshold(value);
|
||||
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
return 'rgba(0,0,0,1)';
|
||||
} else {
|
||||
return threshold.color;
|
||||
}
|
||||
}
|
||||
|
||||
// returns color from first matched thresold in order from 0 to thresholds.length
|
||||
getBucketColor(values) {
|
||||
let thresholds = this.panel.color.thresholds;
|
||||
@@ -109,24 +143,44 @@ export class ColorModeDiscrete {
|
||||
return 'rgba(0,0,0,1)';
|
||||
}
|
||||
|
||||
|
||||
updateCardsValuesHasColorInfoSingle() {
|
||||
if (!this.panelCtrl.bucketMatrix) {
|
||||
return;
|
||||
}
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = false;
|
||||
|
||||
this.panelCtrl.bucketMatrix.targets.map((target:string) => {
|
||||
this.panelCtrl.bucketMatrix.buckets[target].map((bucket:Bucket) => {
|
||||
bucket.noColorDefined = false;
|
||||
let threshold = this.getMatchedThreshold(bucket.value);
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
bucket.noColorDefined = true;
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateCardsValuesHasColorInfo() {
|
||||
if (!this.panelCtrl.cardsData) {
|
||||
if (!this.panelCtrl.bucketMatrix) {
|
||||
return
|
||||
}
|
||||
this.panelCtrl.cardsData.noColorDefined = false;
|
||||
let cards = this.panelCtrl.cardsData.cards;
|
||||
for (let i=0; i<cards.length; i++) {
|
||||
cards[i].noColorDefined = false;
|
||||
let values = cards[i].values;
|
||||
for (let j=0; j<values.length; j++) {
|
||||
let threshold = this.getMatchedThreshold(values[j]);
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
cards[i].noColorDefined = true;
|
||||
this.panelCtrl.cardsData.noColorDefined = true;
|
||||
break;
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = false;
|
||||
|
||||
this.panelCtrl.bucketMatrix.targets.map((target:string) => {
|
||||
this.panelCtrl.bucketMatrix.buckets[target].map((bucket:Bucket) => {
|
||||
bucket.noColorDefined = false;
|
||||
for (let j=0; j<bucket.values.length; j++) {
|
||||
let threshold = this.getMatchedThreshold(bucket.values[j]);
|
||||
if (!threshold || !threshold.color || threshold.color == "") {
|
||||
bucket.noColorDefined = true;
|
||||
this.panelCtrl.bucketMatrix.noColorDefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getMatchedThreshold(value) {
|
||||
|
||||
+49
-3
@@ -14,7 +14,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.status-heatmap-panel {
|
||||
.statusmap-panel {
|
||||
position: relative;
|
||||
|
||||
.axis .tick {
|
||||
@@ -41,19 +41,37 @@
|
||||
}
|
||||
|
||||
.statusmap-tooltip {
|
||||
// from .grafana-tooltip
|
||||
// '.grafana-tooltip a' makes links not readable for tooltip items.
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
font-weight: 200;
|
||||
border-radius: 5px;
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
line-height: 14px;
|
||||
z-index: 9999;
|
||||
|
||||
// more overrides
|
||||
white-space: nowrap;
|
||||
font-size: $font-size-sm;
|
||||
background-color: $graph-tooltip-bg;
|
||||
color: $text-color;
|
||||
|
||||
.discrete-item {
|
||||
color: #52545c;
|
||||
color: #ffffff;
|
||||
padding: 1px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 0.2em #FFF, 0 0 0.2em #FFF, 0 0 0.2em #FFF;
|
||||
text-shadow: 0 0 0.2em #212124, 0 0 0.2em #212124, 0 0 0.2em #212124;
|
||||
}
|
||||
}
|
||||
|
||||
.statusmap-tooltip-frozen {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.statusmap-histogram rect {
|
||||
fill: $text-color-weak;
|
||||
}
|
||||
@@ -71,6 +89,34 @@
|
||||
stroke: rgba(102, 102, 102, 0.8);
|
||||
}
|
||||
|
||||
.width-c-40 {
|
||||
width: 40.0rem!important
|
||||
}
|
||||
.width-c-50 {
|
||||
width: 50.0rem!important
|
||||
}
|
||||
.width-c-60 {
|
||||
width: 60.0rem!important
|
||||
}
|
||||
.width-c-70 {
|
||||
width: 70.0rem!important
|
||||
}
|
||||
|
||||
.statusmap-pagination {
|
||||
// space between legend and pagination controls
|
||||
margin-top: 5px;
|
||||
label {
|
||||
margin-right:0px;
|
||||
}
|
||||
input {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.last-in-group {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-buttons{text-align: right;}
|
||||
|
||||
.status-heatmap-legend-wrapper {
|
||||
margin: 0 10px;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,4 @@
|
||||
export interface AppEvent<T> {
|
||||
readonly name: string;
|
||||
payload?: T;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {AppEvent} from './appEvents';
|
||||
|
||||
export interface GraphHoverPayload {
|
||||
pos: any;
|
||||
panel: {
|
||||
id: number;
|
||||
};
|
||||
}
|
||||
|
||||
export var graphHover:(AppEvent<GraphHoverPayload>|string) = {name: 'graph-hover'};
|
||||
export var graphHoverClear:(AppEvent<any>|string) = {name: 'graph-hover-clear'};
|
||||
|
||||
export function fallbackToStringEvents() {
|
||||
graphHover = 'graph-hover';
|
||||
graphHoverClear = 'graph-hover-clear';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import * as CoreEvents from './events';
|
||||
import * as PanelEvents from './panelEvents';
|
||||
export { CoreEvents, PanelEvents }
|
||||
@@ -0,0 +1,29 @@
|
||||
import { AppEvent } from './appEvents';
|
||||
|
||||
export interface DataQueryError {
|
||||
data?: {
|
||||
message?: string;
|
||||
error?: string;
|
||||
};
|
||||
message?: string;
|
||||
status?: string;
|
||||
statusText?: string;
|
||||
refId?: string;
|
||||
cancelled?: boolean;
|
||||
}
|
||||
|
||||
export var refresh:(AppEvent<undefined>|string) = {name: 'refresh'};
|
||||
export var render:(AppEvent<any>|string) = {name: 'render'};
|
||||
export var dataError:(AppEvent<DataQueryError>|string) = {name: 'data-error'};
|
||||
export var dataReceived:(AppEvent<any[]>|string) = {name: 'data-received'};
|
||||
export var dataSnapshotLoad:(AppEvent<any[]>|string) = {name: 'data-snapshot-load'};
|
||||
export var editModeInitialized:(AppEvent<undefined>|string) = {name: 'init-edit-mode'};
|
||||
|
||||
export function fallbackToStringEvents() {
|
||||
refresh = 'refresh';
|
||||
render = 'render';
|
||||
dataError = 'data-error';
|
||||
dataReceived = 'data-received';
|
||||
dataSnapshotLoad = 'data-snapshot-load';
|
||||
editModeInitialized = 'init-edit-mode';
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
1. @grafana/data is not supported in grafana 5.x and 6.2. The workaround is
|
||||
the module `./libs/grafana/events` that include copy-pasted events
|
||||
from @grafana/data and app/core from grafana sources.
|
||||
|
||||
2. Newly Grafana versions flood Console with
|
||||
"Usage strings as events is deprecated ..." messages.
|
||||
This module has a function to detect if Grafana’s Emitter support AppEvent style event ids.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
|
||||
// Old Grafana releases use strings as event ids and
|
||||
// new event ids in form of object {name: "event-id"} are not
|
||||
// supported properly: first defined listener will receive
|
||||
// all emitted events despite of the value in 'name' field.
|
||||
//
|
||||
// This method detects this behaviour and return true
|
||||
// only for new Grafana versions.
|
||||
export function hasAppEventCompatibleEmitter(emitter: Emitter):boolean {
|
||||
let receiveEvents = 0;
|
||||
let eventId: any = {name: "non-existed-event-id"};
|
||||
let eventId2: any = {name: "non-existed-event-id-2"};
|
||||
emitter.on(eventId, function(){
|
||||
receiveEvents++;
|
||||
});
|
||||
emitter.emit(eventId);
|
||||
emitter.emit(eventId2);
|
||||
emitter.removeAllListeners(eventId);
|
||||
|
||||
// New Grafana versions should receive one event.
|
||||
return receiveEvents == 1;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import * as Polygrafill from './funcs';
|
||||
export { Polygrafill };
|
||||
+26
-4
@@ -1,15 +1,37 @@
|
||||
<div class="status-heatmap-wrapper">
|
||||
<div class="status-heatmap-canvas-wrapper">
|
||||
|
||||
<div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined">
|
||||
<div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined || ctrl.noDatapoints">
|
||||
<span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span>
|
||||
<span class="small" ng-if="ctrl.noDatapoints" bs-tooltip="'{{ctrl.dataWarnings.noDatapoints.tip}}'">{{ctrl.dataWarnings.noDatapoints.title}}</span>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
<div class="statusmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
|
||||
<div class="status-heatmap-legend-wrapper" ng-if="ctrl.panel.legend.show">
|
||||
<status-heatmap-legend></status-heatmap-legend>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="statusmap-pagination gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-disabled="!pager.hasPrev()" ng-click="ctrl.onPrevPage()">
|
||||
<i class="fa fa-arrow-left"></i> Previous
|
||||
</button>
|
||||
<label class="gf-form-label">{{pager.currentPage+1}}/{{pager.pages()}}</label>
|
||||
<button class="btn btn-inverse last-in-group" ng-disabled="!pager.hasNext()" ng-click="ctrl.onNextPage()">
|
||||
Next <i class="fa fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-3 last-in-group" placeholder="12" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show per page.'" ng-model="ctrl.pageSizeViewer" ng-change="ctrl.onChangePageSize()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
<label class="gf-form-label">Show {{pager.pageStartRow()}} to {{pager.pageEndRow()}} of {{pager.totalRows()}} rows</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
+401
-195
@@ -4,6 +4,9 @@ import { auto } from 'angular';
|
||||
|
||||
// Components
|
||||
import './color_legend';
|
||||
import { optionsEditorCtrl } from './options_editor';
|
||||
import { tooltipEditorCtrl } from './tooltip_editor';
|
||||
import { migratePanelConfig } from './panel_config_migration';
|
||||
|
||||
// Utils
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
@@ -12,21 +15,17 @@ import {loadPluginCss} from 'app/plugins/sdk';
|
||||
// Types
|
||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||
import { AnnotationsSrv } from 'app/features/annotations/annotations_srv';
|
||||
import {CardsStorage, Card} from './statusmap_data';
|
||||
import { CoreEvents, PanelEvents } from './libs/grafana/events/index';
|
||||
import {Bucket, BucketMatrix, BucketMatrixPager } from './statusmap_data';
|
||||
import rendering from './rendering';
|
||||
// import aggregates, { aggregatesMap } from './aggregates';
|
||||
// import fragments, { fragmentsMap } from './fragments';
|
||||
// import { labelFormats } from './xAxisLabelFormats';
|
||||
import {statusHeatmapOptionsEditor} from './options_editor';
|
||||
import { Polygrafill } from './libs/polygrafill/index';
|
||||
|
||||
|
||||
import {ColorModeDiscrete} from "./color_mode_discrete";
|
||||
|
||||
const CANVAS = 'CANVAS';
|
||||
const SVG = 'SVG';
|
||||
const VALUE_INDEX = 0,
|
||||
TIME_INDEX = 1;
|
||||
|
||||
const renderer = CANVAS;
|
||||
|
||||
const colorSchemes = [
|
||||
// Diverging
|
||||
{ name: 'Spectral', value: 'interpolateSpectral', invert: 'always' },
|
||||
@@ -59,46 +58,40 @@ const colorSchemes = [
|
||||
let colorModes = ['opacity', 'spectrum', 'discrete'];
|
||||
let opacityScales = ['linear', 'sqrt'];
|
||||
|
||||
interface DataWarning {
|
||||
title: string;
|
||||
tip: string;
|
||||
}
|
||||
|
||||
interface DataWarnings {
|
||||
noColorDefined: DataWarning;
|
||||
multipleValues: DataWarning;
|
||||
}
|
||||
|
||||
interface ColorThreshold {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
loadPluginCss({
|
||||
dark: 'plugins/flant-statusmap-panel/css/statusmap.dark.css',
|
||||
light: 'plugins/flant-statusmap-panel/css/statusmap.light.css'
|
||||
});
|
||||
|
||||
export var renderComplete:any = {name:'statusmap-render-complete'}; // eventFactory('statusmap-render-complete');
|
||||
|
||||
class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
|
||||
data: any;
|
||||
bucketMatrix: BucketMatrix;
|
||||
bucketMatrixPager: BucketMatrixPager;
|
||||
|
||||
graph: any;
|
||||
discreteHelper: ColorModeDiscrete;
|
||||
opacityScales: any = [];
|
||||
colorModes: any = [];
|
||||
colorSchemes: any = [];
|
||||
unitFormats: any;
|
||||
data: any;
|
||||
cardsData: any;
|
||||
graph: any;
|
||||
|
||||
dataWarnings: {[warningId: string]: {title: string, tip: string}} = {};
|
||||
multipleValues: boolean;
|
||||
noColorDefined: boolean;
|
||||
discreteHelper: ColorModeDiscrete;
|
||||
dataWarnings: DataWarnings;
|
||||
noDatapoints: boolean;
|
||||
|
||||
discreteExtraSeries: ColorModeDiscrete;
|
||||
|
||||
annotations: object[] = [];
|
||||
annotationsPromise: any;
|
||||
|
||||
// TODO remove this transient variable: use ng-model-options="{ getterSetter: true }"
|
||||
pageSizeViewer: number = 15;
|
||||
|
||||
panelDefaults: any = {
|
||||
// datasource name, null = default datasource
|
||||
datasource: null,
|
||||
@@ -121,39 +114,57 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
cardRound: null
|
||||
},
|
||||
xAxis: {
|
||||
show: true,
|
||||
showWeekends: true,
|
||||
minBucketWidthToShowWeekends: 4,
|
||||
showCrosshair: true,
|
||||
labelFormat: '%a %m/%d'
|
||||
show: true
|
||||
},
|
||||
yAxis: {
|
||||
show: true,
|
||||
showCrosshair: false
|
||||
minWidth: -1,
|
||||
maxWidth: -1
|
||||
},
|
||||
tooltip: {
|
||||
show: true
|
||||
show: true,
|
||||
freezeOnClick: true,
|
||||
showItems: false,
|
||||
items: [] // see tooltip_editor.ts
|
||||
},
|
||||
legend: {
|
||||
show: true
|
||||
},
|
||||
data: {
|
||||
unitFormat: 'short',
|
||||
decimals: null
|
||||
},
|
||||
// how null points should be handled
|
||||
nullPointMode: 'as empty',
|
||||
yAxisSort: 'metrics',
|
||||
highlightCards: true,
|
||||
useMax: true
|
||||
useMax: true,
|
||||
|
||||
seriesFilterIndex: -1,
|
||||
|
||||
// Pagination options
|
||||
usingPagination: false,
|
||||
pageSize: 15
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $injector: auto.IInjectorService, private annotationsSrv: AnnotationsSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
if(!Polygrafill.hasAppEventCompatibleEmitter(this.events)){
|
||||
CoreEvents.fallbackToStringEvents();
|
||||
PanelEvents.fallbackToStringEvents();
|
||||
renderComplete = renderComplete.name;
|
||||
}
|
||||
|
||||
migratePanelConfig(this.panel);
|
||||
_.defaultsDeep(this.panel, this.panelDefaults);
|
||||
|
||||
this.bucketMatrix = new BucketMatrix();
|
||||
|
||||
// Create pager for bucketMatrix and restore page size.
|
||||
this.bucketMatrixPager = new BucketMatrixPager();
|
||||
this.bucketMatrixPager.setEnable(this.panel.usingPagination);
|
||||
this.bucketMatrixPager.setDefaultPageSize(this.panel.pageSize);
|
||||
this.bucketMatrixPager.setPageSize(this.panel.pageSize);
|
||||
$scope.pager = this.bucketMatrixPager;
|
||||
|
||||
this.opacityScales = opacityScales;
|
||||
this.colorModes = colorModes;
|
||||
this.colorSchemes = colorSchemes;
|
||||
@@ -166,42 +177,89 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
this.multipleValues = false;
|
||||
this.noColorDefined = false;
|
||||
|
||||
this.discreteHelper = new ColorModeDiscrete($scope);
|
||||
this.discreteExtraSeries = new ColorModeDiscrete($scope);
|
||||
|
||||
this.dataWarnings = {
|
||||
"noColorDefined": {
|
||||
noColorDefined: {
|
||||
title: 'Data has value with undefined color',
|
||||
tip: 'Check metric values, color values or define a new color',
|
||||
},
|
||||
"multipleValues": {
|
||||
multipleValues: {
|
||||
title: 'Data has multiple values for one target',
|
||||
tip: 'Change targets definitions or set "use max value"',
|
||||
},
|
||||
noDatapoints: {
|
||||
title: 'No data points',
|
||||
tip: 'No datapoints returned from data query',
|
||||
}
|
||||
};
|
||||
|
||||
this.annotations = [];
|
||||
this.annotationsSrv = annotationsSrv;
|
||||
|
||||
this.events.on('render', this.onRender.bind(this));
|
||||
this.events.on('data-received', this.onDataReceived.bind(this));
|
||||
this.events.on('data-error', this.onDataError.bind(this));
|
||||
this.events.on('data-snapshot-load', this.onDataReceived.bind(this));
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
|
||||
this.events.on('refresh', this.postRefresh.bind(this));
|
||||
this.events.on(PanelEvents.render, this.onRender.bind(this));
|
||||
this.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this));
|
||||
this.events.on(PanelEvents.dataError, this.onDataError.bind(this));
|
||||
this.events.on(PanelEvents.dataSnapshotLoad, this.onDataReceived.bind(this));
|
||||
this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));
|
||||
this.events.on(PanelEvents.refresh, this.postRefresh.bind(this));
|
||||
// custom event from rendering.js
|
||||
this.events.on('render-complete', this.onRenderComplete.bind(this));
|
||||
this.events.on(renderComplete, this.onRenderComplete.bind(this));
|
||||
|
||||
this.onCardColorChange = this.onCardColorChange.bind(this);
|
||||
}
|
||||
|
||||
onRenderComplete(data):void {
|
||||
onRenderComplete(data: any):void {
|
||||
this.graph.chartWidth = data.chartWidth;
|
||||
this.renderingCompleted();
|
||||
}
|
||||
|
||||
changeDefaultPaginationSize(defaultPageSize: number): void {
|
||||
this.bucketMatrixPager.setDefaultPageSize(defaultPageSize);
|
||||
this.bucketMatrixPager.setPageSize(defaultPageSize);
|
||||
this.pageSizeViewer = defaultPageSize;
|
||||
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
onChangePageSize(): void {
|
||||
if (this.pageSizeViewer <= 0) {
|
||||
this.pageSizeViewer = this.bucketMatrixPager.defaultPageSize;
|
||||
}
|
||||
this.bucketMatrixPager.setPageSize(this.pageSizeViewer);
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
|
||||
this.render();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
onPrevPage(): void {
|
||||
this.bucketMatrixPager.switchToPrev();
|
||||
this.render();
|
||||
}
|
||||
|
||||
onNextPage(): void {
|
||||
this.bucketMatrixPager.switchToNext();
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
||||
// getChartWidth returns an approximation of chart canvas width or
|
||||
// a saved value calculated during a render.
|
||||
getChartWidth():number {
|
||||
if (this.graph.chartWidth > 0) {
|
||||
return this.graph.chartWidth;
|
||||
}
|
||||
|
||||
const wndWidth = $(window).width();
|
||||
// gripPos.w is a width in grid's measurements. Grid size in Grafana is 24.
|
||||
const panelWidthFactor = this.panel.gridPos.w / 24;
|
||||
const panelWidth = Math.ceil(wndWidth * panelWidthFactor);
|
||||
// approximate chartWidth because y axis ticks not rendered yet on first data receive.
|
||||
// approximate width of the chart draw canvas:
|
||||
// - y axis ticks are not rendered yet on first data receive,
|
||||
// so choose 200 as a decent value for y legend width
|
||||
// - chartWidth can not be lower than the half of the panel width.
|
||||
const chartWidth = _.max([
|
||||
panelWidth - 200,
|
||||
panelWidth/2
|
||||
@@ -210,7 +268,18 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
return chartWidth!;
|
||||
}
|
||||
|
||||
// override calculateInterval for discrete color mode
|
||||
// Quick workaround for 6.7 and 7.0+. There is no call to
|
||||
// calculateInterval in updateTimeRange in those versions.
|
||||
// TODO ts type has no argument for this method.
|
||||
updateTimeRange(datasource?: any) {
|
||||
let ret = super.updateTimeRange(datasource);
|
||||
this.calculateInterval();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// calculateInterval is called on 'refresh' to calculate an interval
|
||||
// for datasource.
|
||||
// It is override of calculateInterval from MetricsPanelCtrl.
|
||||
calculateInterval() {
|
||||
let chartWidth = this.getChartWidth();
|
||||
|
||||
@@ -222,7 +291,7 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
let rangeMs = this.range.to.valueOf() - this.range.from.valueOf();
|
||||
|
||||
// this is minimal interval! kbn.round_interval will lower it.
|
||||
intervalMs = this.discreteHelper.roundIntervalCeil(rangeMs / maxCardsCount);
|
||||
intervalMs = this.discreteExtraSeries.roundIntervalCeil(rangeMs / maxCardsCount);
|
||||
|
||||
// Calculate low limit of interval
|
||||
let lowLimitMs = 1; // 1 millisecond default low limit
|
||||
@@ -250,6 +319,10 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
|
||||
this.intervalMs = intervalMs;
|
||||
this.interval = interval;
|
||||
|
||||
// Get final buckets count after interval is adjusted
|
||||
// TODO is it needed?
|
||||
//this.xBucketsCount = Math.floor(rangeMs / intervalMs);
|
||||
}
|
||||
|
||||
issueQueries(datasource: any) {
|
||||
@@ -266,7 +339,6 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
* issue 11806.
|
||||
*/
|
||||
// 5.x before 5.4 doesn't have datasourcePromises.
|
||||
|
||||
if ("undefined" !== typeof(this.annotationsSrv.datasourcePromises)) {
|
||||
return this.annotationsSrv.datasourcePromises.then(r => {
|
||||
return this.issueQueriesWithInterval(datasource, this.interval);
|
||||
@@ -281,63 +353,91 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
// so we need to save-restore this.panel.interval.
|
||||
issueQueriesWithInterval(datasource: any, interval: any) {
|
||||
var origInterval = this.panel.interval;
|
||||
this.panel.interval = this.interval;
|
||||
this.panel.interval = interval;
|
||||
var res = super.issueQueries(datasource);
|
||||
this.panel.interval = origInterval;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
onDataReceived(dataList: any) {
|
||||
this.data = dataList;
|
||||
this.cardsData = this.convertToCards(this.data);
|
||||
this.data = dataList;
|
||||
// Quick workaround for 7.0+. There is no call to
|
||||
// calculateInterval when enter Edit mode.
|
||||
if (!this.intervalMs) {
|
||||
this.calculateInterval();
|
||||
}
|
||||
|
||||
console.log("OnDataReceived");
|
||||
let newBucketMatrix = this.convertDataToBuckets(dataList, this.range.from.valueOf(), this.range.to.valueOf(), this.intervalMs, true);
|
||||
|
||||
this.annotationsPromise.then(
|
||||
(result: { alertState: any; annotations: any }) => {
|
||||
this.loading = false;
|
||||
//this.alertState = result.alertState;
|
||||
if (result.annotations && result.annotations.length > 0) {
|
||||
this.annotations = result.annotations;
|
||||
} else {
|
||||
this.bucketMatrix = newBucketMatrix;
|
||||
this.bucketMatrixPager.bucketMatrix = newBucketMatrix;
|
||||
if (newBucketMatrix.targets.length !== this.bucketMatrix.targets.length) {
|
||||
this.bucketMatrixPager.setCurrent(0);
|
||||
}
|
||||
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
|
||||
if (this.annotationsPromise) {
|
||||
this.annotationsPromise.then(
|
||||
(result: { alertState: any; annotations: any }) => {
|
||||
this.loading = false;
|
||||
//this.alertState = result.alertState;
|
||||
if (result.annotations && result.annotations.length > 0) {
|
||||
this.annotations = result.annotations;
|
||||
} else {
|
||||
this.annotations = [];
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
() => {
|
||||
this.loading = false;
|
||||
this.annotations = [];
|
||||
this.render();
|
||||
}
|
||||
console.log("annotationsPromise result " + this.annotations.length + " annotations");
|
||||
this.render();
|
||||
},
|
||||
() => {
|
||||
this.loading = false;
|
||||
this.annotations = [];
|
||||
console.log("annotationsPromise onrejected");
|
||||
this.render();
|
||||
}
|
||||
);
|
||||
|
||||
//this.render();
|
||||
);
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.annotations = [];
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
onInitEditMode() {
|
||||
this.addEditorTab('Options', statusHeatmapOptionsEditor, 2);
|
||||
this.addEditorTab('Options', optionsEditorCtrl, 2);
|
||||
this.addEditorTab('Tooltip', tooltipEditorCtrl, 3);
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
|
||||
// onRender will be called before StatusmapRenderer.onRender.
|
||||
// Decide if warning should be displayed over cards.
|
||||
onRender() {
|
||||
if (!this.range || !this.data) { return; }
|
||||
if (!this.range || !this.data) {
|
||||
this.noDatapoints = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.multipleValues = false;
|
||||
if (!this.panel.useMax) {
|
||||
if (this.cardsData) {
|
||||
this.multipleValues = this.cardsData.multipleValues;
|
||||
if (this.bucketMatrix) {
|
||||
this.multipleValues = this.bucketMatrix.multipleValues;
|
||||
}
|
||||
}
|
||||
|
||||
this.noColorDefined = false;
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
this.discreteHelper.updateCardsValuesHasColorInfo();
|
||||
if (this.cardsData) {
|
||||
this.noColorDefined = this.cardsData.noColorDefined;
|
||||
if (this.panel.seriesFilterIndex == -1) {
|
||||
this.discreteExtraSeries.updateCardsValuesHasColorInfo();
|
||||
} else {
|
||||
this.discreteExtraSeries.updateCardsValuesHasColorInfoSingle();
|
||||
}
|
||||
if (this.bucketMatrix) {
|
||||
this.noColorDefined = this.bucketMatrix.noColorDefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.noDatapoints = false;
|
||||
if (this.bucketMatrix) {
|
||||
this.noDatapoints = this.bucketMatrix.noDatapoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,121 +456,227 @@ class StatusHeatmapCtrl extends MetricsPanelCtrl {
|
||||
this.noColorDefined = false;
|
||||
}
|
||||
|
||||
onEditorAddThreshold() {
|
||||
this.panel.color.thresholds.push({ color: this.panel.defaultColor });
|
||||
this.render();
|
||||
}
|
||||
|
||||
onEditorRemoveThreshold(index:number) {
|
||||
this.panel.color.thresholds.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
onEditorRemoveThresholds() {
|
||||
this.panel.color.thresholds = [];
|
||||
this.render();
|
||||
}
|
||||
|
||||
onEditorAddThreeLights() {
|
||||
this.panel.color.thresholds.push({color: "red", value: 2, tooltip: "error" });
|
||||
this.panel.color.thresholds.push({color: "yellow", value: 1, tooltip: "warning" });
|
||||
this.panel.color.thresholds.push({color: "green", value: 0, tooltip: "ok" });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* https://ethanschoonover.com/solarized/ */
|
||||
onEditorAddSolarized() {
|
||||
this.panel.color.thresholds.push({color: "#b58900", value: 0, tooltip: "yellow" });
|
||||
this.panel.color.thresholds.push({color: "#cb4b16", value: 1, tooltip: "orange" });
|
||||
this.panel.color.thresholds.push({color: "#dc322f", value: 2, tooltip: "red" });
|
||||
this.panel.color.thresholds.push({color: "#d33682", value: 3, tooltip: "magenta" });
|
||||
this.panel.color.thresholds.push({color: "#6c71c4", value: 4, tooltip: "violet" });
|
||||
this.panel.color.thresholds.push({color: "#268bd2", value: 5, tooltip: "blue" });
|
||||
this.panel.color.thresholds.push({color: "#2aa198", value: 6, tooltip: "cyan" });
|
||||
this.panel.color.thresholds.push({color: "#859900", value: 7, tooltip: "green" });
|
||||
this.render();
|
||||
}
|
||||
|
||||
link(scope, elem, attrs, ctrl) {
|
||||
rendering(scope, elem, attrs, ctrl);
|
||||
}
|
||||
|
||||
// group values into buckets by target
|
||||
convertToCards(data) {
|
||||
let cardsData = <CardsStorage> {
|
||||
cards: [],
|
||||
xBucketSize: 0,
|
||||
yBucketSize: 0,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
multipleValues: false,
|
||||
noColorDefined: false,
|
||||
targets: [], // array of available unique targets
|
||||
targetIndex: {} // indices in data array for each of available unique targets
|
||||
};
|
||||
// Compatible with Grafana 7.0 overrides feature.
|
||||
retrieveTimeVar() {
|
||||
var time = this.timeSrv.timeRangeForUrl();
|
||||
return 'from=' + time.from + '&to=' + time.to;
|
||||
}
|
||||
|
||||
if (!data || data.length == 0) { return cardsData;}
|
||||
// convertToBuckets groups values in data into buckets by target and timestamp.
|
||||
//
|
||||
// data is a result from datasource. It is an array of timeseries and tables:
|
||||
/* [
|
||||
// timeseries
|
||||
{
|
||||
target: "query alias",
|
||||
refId: "A",
|
||||
datapoints: [
|
||||
[0, 1582681239911],
|
||||
[3, 158....],
|
||||
...
|
||||
],
|
||||
tags?:{key: value,...}
|
||||
},
|
||||
// table
|
||||
{
|
||||
name: "table name",
|
||||
refId: "B",
|
||||
columns: [
|
||||
{text: "id"},
|
||||
{text: "info"},
|
||||
...
|
||||
],
|
||||
rows: [
|
||||
[1, "123"],
|
||||
[2, "44411"],
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
// Collect uniq timestamps from data and spread over targets and timestamps
|
||||
to and from — a time range of the panel.
|
||||
intervalMs — a calculated interval. It is used to split a time range.
|
||||
*/
|
||||
convertDataToBuckets(data:any, from:number, to:number, intervalMs: number, mostRecentBucket: boolean):BucketMatrix {
|
||||
let bucketMatrix = new BucketMatrix();
|
||||
bucketMatrix.rangeMs = to - from;
|
||||
bucketMatrix.intervalMs = intervalMs;
|
||||
|
||||
// collect uniq targets and their indices
|
||||
_.map(data, (d, i) => {
|
||||
cardsData.targetIndex[d.target] = _.concat(_.toArray(cardsData.targetIndex[d.target]), i)
|
||||
});
|
||||
|
||||
// TODO add some logic for targets heirarchy
|
||||
cardsData.targets = _.keys(cardsData.targetIndex);
|
||||
cardsData.yBucketSize = cardsData.targets.length;
|
||||
// Maximum number of buckets over x axis
|
||||
cardsData.xBucketSize = _.max(_.map(data, d => d.datapoints.length));
|
||||
|
||||
// Collect all values for each bucket from datapoints with similar target.
|
||||
// TODO aggregate values into buckets over datapoint[TIME_INDEX] not over datapoint index (j).
|
||||
for(let i = 0; i < cardsData.targets.length; i++) {
|
||||
let target = cardsData.targets[i];
|
||||
|
||||
for (let j = 0; j < cardsData.xBucketSize; j++) {
|
||||
let card = new Card();
|
||||
card.id = i*cardsData.xBucketSize + j;
|
||||
card.values = [];
|
||||
card.y = target;
|
||||
card.x = -1;
|
||||
|
||||
// collect values from all timeseries with target
|
||||
for (let si = 0; si < cardsData.targetIndex[target].length; si++) {
|
||||
let s = data[cardsData.targetIndex[target][si]];
|
||||
if (s.datapoints.length <= j) {
|
||||
continue;
|
||||
}
|
||||
let datapoint = s.datapoints[j];
|
||||
if (card.values.length === 0) {
|
||||
card.x = datapoint[TIME_INDEX];
|
||||
}
|
||||
card.values.push(datapoint[VALUE_INDEX]);
|
||||
}
|
||||
card.minValue = _.min(card.values);
|
||||
card.maxValue = _.max(card.values);
|
||||
if (card.values.length > 1) {
|
||||
cardsData.multipleValues = true;
|
||||
card.multipleValues = true;
|
||||
card.value = card.maxValue; // max value by default
|
||||
} else {
|
||||
card.value = card.maxValue; // max value by default
|
||||
}
|
||||
|
||||
if (cardsData.maxValue < card.maxValue)
|
||||
cardsData.maxValue = card.maxValue;
|
||||
|
||||
if (cardsData.minValue > card.minValue)
|
||||
cardsData.minValue = card.minValue;
|
||||
|
||||
if (card.x != -1) {
|
||||
cardsData.cards.push(card);
|
||||
}
|
||||
}
|
||||
if (!data || data.length == 0) {
|
||||
// Mimic heatmap and graph 'no data' labels.
|
||||
bucketMatrix.targets = [
|
||||
"1.0", "0.0", "-1.0"
|
||||
];
|
||||
bucketMatrix.buckets["1.0"] = [];
|
||||
bucketMatrix.buckets["0.0"] = [];
|
||||
bucketMatrix.buckets["-1.0"] = [];
|
||||
bucketMatrix.xBucketSize = 42;
|
||||
bucketMatrix.noDatapoints = true;
|
||||
return bucketMatrix;
|
||||
}
|
||||
|
||||
return cardsData;
|
||||
let targetIndex: {[target: string]: number[]} = {};
|
||||
|
||||
// Group indicies of elements in data by target (y label).
|
||||
|
||||
// lodash version:
|
||||
//_.map(data, (d, i) => {
|
||||
// targetIndex[d.target] = _.concat(_.toArray(targetIndex[d.target]), i);
|
||||
//});
|
||||
|
||||
data.map((queryResult: any, i: number) => {
|
||||
let yLabel = queryResult.target;
|
||||
if (!targetIndex.hasOwnProperty(yLabel)) {
|
||||
targetIndex[yLabel] = [];
|
||||
}
|
||||
targetIndex[yLabel].push(i);
|
||||
});
|
||||
|
||||
let targetKeys = _.keys(targetIndex);
|
||||
|
||||
//console.log ("targetIndex: ", targetIndex, "targetKeys: ", targetKeys);
|
||||
|
||||
let targetTimestampRanges: {[target: string]: {[timestamp: number]: number[]}} = {};
|
||||
|
||||
// Collect all timestamps for each target.
|
||||
// Make map timestamp => [from, to]. from == previous ts, to == ts from datapoint.
|
||||
targetKeys.map((target) => {
|
||||
let targetTimestamps: any[] = [];
|
||||
|
||||
for (let si = 0; si < targetIndex[target].length; si++) {
|
||||
let s = data[targetIndex[target][si]];
|
||||
_.map(s.datapoints, (datapoint, idx) => {
|
||||
targetTimestamps.push(datapoint[TIME_INDEX]-from);
|
||||
})
|
||||
}
|
||||
|
||||
//console.log("timestamps['"+target+"'] = ", targetTimestamps);
|
||||
|
||||
targetTimestamps = _.uniq(targetTimestamps);
|
||||
|
||||
//console.log("uniq timestamps['"+target+"'] = ", targetTimestamps);
|
||||
|
||||
targetTimestampRanges[target] = [];
|
||||
for (let i = targetTimestamps.length-1 ; i>=0; i-- ) {
|
||||
let tsTo = targetTimestamps[i];
|
||||
let tsFrom = 0;
|
||||
if (tsTo < 0) {
|
||||
tsFrom = tsTo - intervalMs;
|
||||
} else {
|
||||
if (i-1 >= 0) {
|
||||
// Set from to previous timestamp + 1ms;
|
||||
tsFrom = targetTimestamps[i-1]+1;
|
||||
// tfTo - tfFrom should not be more than intervalMs
|
||||
let minFrom = tsTo - intervalMs;
|
||||
if (tsFrom < minFrom) {
|
||||
tsFrom = minFrom;
|
||||
}
|
||||
}
|
||||
}
|
||||
targetTimestampRanges[target][tsTo] = [tsFrom, tsTo];
|
||||
}
|
||||
});
|
||||
|
||||
// console.log ("targetTimestampRanges: ", targetTimestampRanges);
|
||||
|
||||
// Create empty buckets using intervalMs to calculate ranges.
|
||||
// If mostRecentBucket is set, create a bucket with a range "to":"to"
|
||||
// to store most recent values.
|
||||
targetKeys.map((target) => {
|
||||
let targetEmptyBuckets: any[] = [];
|
||||
|
||||
let lastTs = to-from;
|
||||
|
||||
if (mostRecentBucket) {
|
||||
let topBucket = new Bucket();
|
||||
topBucket.yLabel = target;
|
||||
topBucket.relTo = lastTs;
|
||||
topBucket.relFrom = lastTs;
|
||||
topBucket.values = [];
|
||||
topBucket.mostRecent = true;
|
||||
if (targetTimestampRanges[target].hasOwnProperty(lastTs)) {
|
||||
topBucket.relFrom = targetTimestampRanges[target][lastTs][0];
|
||||
lastTs = topBucket.relFrom;
|
||||
}
|
||||
topBucket.to = topBucket.relTo+from;
|
||||
topBucket.from = topBucket.relFrom+from;
|
||||
targetEmptyBuckets.push(topBucket);
|
||||
}
|
||||
|
||||
let idx = 0;
|
||||
let bucketFrom: number = 0;
|
||||
while (bucketFrom >= 0) {
|
||||
let b = new Bucket();
|
||||
b.yLabel = target;
|
||||
b.relTo = lastTs - idx*intervalMs;
|
||||
b.relFrom = lastTs - ((idx+1) * intervalMs);
|
||||
b.to = b.relTo+from;
|
||||
b.from = b.relFrom+from;
|
||||
b.values = [];
|
||||
bucketFrom = b.relFrom;
|
||||
targetEmptyBuckets.push(b);
|
||||
idx++;
|
||||
}
|
||||
|
||||
targetEmptyBuckets.map((bucket, i) => {
|
||||
bucket.xid = i;
|
||||
});
|
||||
|
||||
bucketMatrix.buckets[target] = targetEmptyBuckets;
|
||||
});
|
||||
|
||||
//console.log ("bucketMatrix: ", bucketMatrix);
|
||||
|
||||
// Put values into buckets.
|
||||
bucketMatrix.minValue = Number.MAX_VALUE;
|
||||
bucketMatrix.maxValue = Number.MIN_SAFE_INTEGER;
|
||||
targetKeys.map((target) => {
|
||||
targetIndex[target].map((dataIndex) => {
|
||||
let s = data[dataIndex];
|
||||
s.datapoints.map((dp: any) => {
|
||||
for (let i = 0; i < bucketMatrix.buckets[target].length; i++) {
|
||||
if (bucketMatrix.buckets[target][i].belong(dp[TIME_INDEX])) {
|
||||
bucketMatrix.buckets[target][i].put(dp[VALUE_INDEX]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
bucketMatrix.buckets[target].map((bucket) => {
|
||||
bucket.minValue = _.min(bucket.values);
|
||||
bucket.maxValue = _.max(bucket.values);
|
||||
if (bucket.minValue < bucketMatrix.minValue) {
|
||||
bucketMatrix.minValue = bucket.minValue;
|
||||
}
|
||||
if (bucket.maxValue > bucketMatrix.maxValue) {
|
||||
bucketMatrix.maxValue = bucket.maxValue;
|
||||
}
|
||||
bucket.value = bucket.maxValue;
|
||||
if (bucket.values.length > 1) {
|
||||
bucketMatrix.multipleValues = true;
|
||||
bucket.multipleValues = true;
|
||||
|
||||
bucket.value = this.panel.seriesFilterIndex != -1 ? bucket.values[this.panel.seriesFilterIndex] : bucket.maxValue;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
bucketMatrix.xBucketSize = Number.MIN_SAFE_INTEGER;
|
||||
targetKeys.map((target) => {
|
||||
let bucketsLen: number = bucketMatrix.buckets[target].length;
|
||||
if (bucketsLen > bucketMatrix.xBucketSize) {
|
||||
bucketMatrix.xBucketSize = bucketsLen;
|
||||
}
|
||||
});
|
||||
|
||||
//console.log ("bucketMatrix with values: ", bucketMatrix);
|
||||
|
||||
bucketMatrix.targets = targetKeys;
|
||||
return bucketMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+48
-6
@@ -1,26 +1,68 @@
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { StatusHeatmapCtrl } from './module';
|
||||
|
||||
export class StatusHeatmapOptionsEditorCtrl {
|
||||
panel: any;
|
||||
panelCtrl: any;
|
||||
panelCtrl: StatusHeatmapCtrl;
|
||||
unitFormats: any;
|
||||
|
||||
constructor($scope) {
|
||||
/** @ngInject */
|
||||
constructor($scope: any) {
|
||||
$scope.editor = this;
|
||||
this.panelCtrl = $scope.ctrl;
|
||||
this.panelCtrl = $scope.ctrl as StatusHeatmapCtrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
|
||||
this.panelCtrl.render();
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
|
||||
setUnitFormat(subItem) {
|
||||
this.panel.data.unitFormat = subItem.value;
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
onAddThreshold() {
|
||||
this.panel.color.thresholds.push({ color: this.panel.defaultColor });
|
||||
this.render();
|
||||
}
|
||||
|
||||
onRemoveThreshold(index:number) {
|
||||
this.panel.color.thresholds.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
onRemoveThresholds() {
|
||||
this.panel.color.thresholds = [];
|
||||
this.render();
|
||||
}
|
||||
|
||||
onAddThreeLights() {
|
||||
this.panel.color.thresholds.push({color: "red", value: 2, tooltip: "error" });
|
||||
this.panel.color.thresholds.push({color: "yellow", value: 1, tooltip: "warning" });
|
||||
this.panel.color.thresholds.push({color: "green", value: 0, tooltip: "ok" });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* https://ethanschoonover.com/solarized/ */
|
||||
onAddSolarized() {
|
||||
this.panel.color.thresholds.push({color: "#b58900", value: 0, tooltip: "yellow" });
|
||||
this.panel.color.thresholds.push({color: "#cb4b16", value: 1, tooltip: "orange" });
|
||||
this.panel.color.thresholds.push({color: "#dc322f", value: 2, tooltip: "red" });
|
||||
this.panel.color.thresholds.push({color: "#d33682", value: 3, tooltip: "magenta" });
|
||||
this.panel.color.thresholds.push({color: "#6c71c4", value: 4, tooltip: "violet" });
|
||||
this.panel.color.thresholds.push({color: "#268bd2", value: 5, tooltip: "blue" });
|
||||
this.panel.color.thresholds.push({color: "#2aa198", value: 6, tooltip: "cyan" });
|
||||
this.panel.color.thresholds.push({color: "#859900", value: 7, tooltip: "green" });
|
||||
this.render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function statusHeatmapOptionsEditor() {
|
||||
/** @ngInject */
|
||||
export function optionsEditorCtrl() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
function migrate_V0_V1(panel: any) {
|
||||
// Remove unused fields.
|
||||
if (_.has(panel, "xAxis.labelFormat")) {
|
||||
delete panel.xAxis.labelFormat;
|
||||
}
|
||||
if (_.has(panel, "xAxis.minBucketWidthToShowWeekends")) {
|
||||
delete panel.xAxis.minBucketWidthToShowWeekends;
|
||||
}
|
||||
if (_.has(panel, "xAxis.showCrosshair")) {
|
||||
delete panel.xAxis.showCrosshair;
|
||||
}
|
||||
if (_.has(panel, "xAxis.showWeekends")) {
|
||||
delete panel.xAxis.showWeekends;
|
||||
}
|
||||
if (_.has(panel, "yAxis.showCrosshair")) {
|
||||
delete panel.yAxis.showCrosshair;
|
||||
}
|
||||
if (_.has(panel, "data.unitFormat")) {
|
||||
delete panel.data;
|
||||
}
|
||||
|
||||
// Migrate cardSpacing value. Seems rare (update from version 0.0.2).
|
||||
if (_.has(panel, "cards.cardSpacing")) {
|
||||
if (!_.has(panel, "cards.cardVSpacing")) {
|
||||
if (panel.cards.cardSpacing) {
|
||||
panel.cards.cardVSpacing = panel.cards.cardSpacing
|
||||
panel.cards.cardHSpacing = panel.cards.cardSpacing
|
||||
}
|
||||
}
|
||||
delete panel.cards.cardSpacing;
|
||||
}
|
||||
|
||||
// Migrate initial config for urls in tooltip (pull/86).
|
||||
// 'usingUrl' was used to show tooltip with urls on click or not.
|
||||
if (_.has(panel, "usingUrl")) {
|
||||
if (!_.has(panel, "tooltip.freezeOnClick")) {
|
||||
panel.tooltip.freezeOnClick = panel.usingUrl;
|
||||
}
|
||||
delete panel.usingUrl;
|
||||
}
|
||||
|
||||
// 'urls' array is now tooltip.items array. Also items are changed.
|
||||
if (_.has(panel, "urls")) {
|
||||
if (!_.has(panel, "tooltip.items")) {
|
||||
panel.tooltip.items = [];
|
||||
let hasRealItems = true;
|
||||
if (panel.urls.length == 0) {
|
||||
hasRealItems = false;
|
||||
}
|
||||
if (panel.urls.length == 1) {
|
||||
let url = panel.urls[0];
|
||||
if (url.base_url === '' && url.label === '') {
|
||||
hasRealItems = false;
|
||||
}
|
||||
}
|
||||
if (hasRealItems) {
|
||||
panel.tooltip.showItems = true;
|
||||
for (let url of panel.urls) {
|
||||
let item = {
|
||||
urlTemplate: _.toString(url.base_url),
|
||||
urlText: _.toString(url.label),
|
||||
urlIcon: _.toString(url.icon_fa),
|
||||
urlToLowerCase: url.forcelowercase,
|
||||
valueDateFormat: '',
|
||||
}
|
||||
// replace $vars with new ${__vars} if url template is not empty
|
||||
if (item.urlTemplate !== "") {
|
||||
// $time was a graph time with prepended &
|
||||
item.urlTemplate = _.replace(url.base_url, /\$time/g, "&${__url_time_range}");
|
||||
// $series_label was a y axis label
|
||||
item.urlTemplate = _.replace(item.urlTemplate, /\$series_label/, "${__y_label}");
|
||||
// $series_extra was a value from bucket. This value has format options and index.
|
||||
let valueVar = "__value";
|
||||
if (url.useExtraSeries === true) {
|
||||
// index?
|
||||
if (url.extraSeries.index > -1) {
|
||||
valueVar += "_" + url.extraSeries.index;
|
||||
}
|
||||
|
||||
let format = _.toString(url.extraSeries.format);
|
||||
if (format === 'YYYY/MM/DD/HH_mm_ss') {
|
||||
valueVar += '_date';
|
||||
item.valueDateFormat = format;
|
||||
}
|
||||
}
|
||||
|
||||
item.urlTemplate = _.replace(item.urlTemplate, /\$series_extra/, `\${${valueVar}}`);
|
||||
}
|
||||
panel.tooltip.items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete panel.urls;
|
||||
}
|
||||
|
||||
// create statusmap metadata
|
||||
panel.statusmap = {
|
||||
"ConfigVersion": "v1",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function migratePanelConfig(panel: any) {
|
||||
if (_.has(panel, "statusmap")) {
|
||||
if (panel.statusmap.ConfigVersion == "v1") {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
migrate_V0_V1(panel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.onEditorRemoveThreshold($index)">
|
||||
<i class="fa fa-trash"/>
|
||||
</a>
|
||||
<i class="fa fa-trash pointer" ng-click="editor.onRemoveThreshold($index)"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,12 +89,12 @@
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form"></div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.onEditorAddThreshold()">
|
||||
<button class="btn btn-inverse" ng-click="editor.onAddThreshold()">
|
||||
<i class="fa fa-plus"></i> Add new status
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.onEditorRemoveThresholds()">
|
||||
<button class="btn btn-inverse" ng-click="editor.onRemoveThresholds()">
|
||||
<i class="fa fa-minus"></i> Remove all
|
||||
</button>
|
||||
</div>
|
||||
@@ -108,12 +106,12 @@
|
||||
<label class="gf-form-label">Presets</label>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn" ng-click="ctrl.onEditorAddThreeLights()">
|
||||
<button class="btn" ng-click="editor.onAddThreeLights()">
|
||||
Red-Yellow-Green
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn" ng-click="ctrl.onEditorAddSolarized()">
|
||||
<button class="btn" ng-click="editor.onAddSolarized()">
|
||||
Solarized
|
||||
</button>
|
||||
</div>
|
||||
@@ -145,11 +143,24 @@
|
||||
checked="ctrl.panel.legend.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Show tooltip"
|
||||
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
|
||||
label="Show X axis"
|
||||
checked="ctrl.panel.xAxis.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Y axis sort</label>
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Show Y axis"
|
||||
checked="ctrl.panel.yAxis.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Min width</label>
|
||||
<input type="number" ng-model="ctrl.panel.yAxis.minWidth" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Max width</label>
|
||||
<input type="number" ng-model="ctrl.panel.yAxis.maxWidth" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.yAxis.show">
|
||||
<label class="gf-form-label width-8">Rows sort</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-8"
|
||||
ng-model="ctrl.panel.yAxisSort"
|
||||
@@ -157,46 +168,64 @@
|
||||
ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Bucket</h5>
|
||||
<h5 class="section-heading">Buckets</h5>
|
||||
|
||||
<gf-form-switch class="gf-form" label-class="width-9"
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Multiple values"
|
||||
checked="ctrl.panel.useMax" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form" ng-show="ctrl.panel.useMax">
|
||||
Color for bucket with multiple values is determined by color mode<br/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Values index</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="-1" data-placement="right"
|
||||
bs-tooltip="'Display only values with this index in a bucket. Use -1 to display all values'"
|
||||
ng-model="ctrl.panel.seriesFilterIndex" ng-change="ctrl.refresh()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Display nulls</label>
|
||||
<label class="gf-form-label width-8">Display nulls</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-9" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['as empty', 'as zero']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Min width for 1/1</label>
|
||||
<label class="gf-form-label width-8">Min width</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">V spacing</label>
|
||||
<label class="gf-form-label width-8">V spacing</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards vertical spacing in pixels'" ng-model="ctrl.panel.cards.cardVSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">H spacing</label>
|
||||
<label class="gf-form-label width-8">H spacing</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards horizontal spacing in pixels'" ng-model="ctrl.panel.cards.cardHSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Rounding</label>
|
||||
<label class="gf-form-label width-8">Rounding</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Cards rounding radius in pixels'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Pagination</h5>
|
||||
<div class="gf-form">
|
||||
<gf-form-switch class="gf-form" label="Enable pagination" label-class="width-12"
|
||||
checked="ctrl.panel.usingPagination" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.panel.usingPagination">
|
||||
<label class="gf-form-label width-10">Rows per page</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="top"
|
||||
bs-tooltip="'Number of rows to show by default'" ng-model="ctrl.panel.pageSize" ng-change="ctrl.changeDefaultPaginationSize(ctrl.panel.pageSize)"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Show tooltip"
|
||||
tooltip="'Show tooltip when mouse is over the card'"
|
||||
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Freeze on click"
|
||||
tooltip="'Freeze tooltip copy when card is clicked'"
|
||||
checked="ctrl.panel.tooltip.freezeOnClick" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-10"
|
||||
label="Show items"
|
||||
tooltip="'Show items (urls) in tooltip'"
|
||||
checked="ctrl.panel.tooltip.showItems" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Items</h5>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.panel.tooltip.items.length == 0">
|
||||
<label class="gf-form-label width-2">0</label>
|
||||
<label class="gf-form-input width-30">No items defined.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div ng-repeat="item in ctrl.panel.tooltip.items">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label width-6">
|
||||
URL
|
||||
<info-popover mode="left-normal">
|
||||
<p>Specify an URL (relative or absolute)</p>
|
||||
<span>
|
||||
Use special variables:
|
||||
<br>
|
||||
<em>${__y_label}</em> name of the time series (alias or legend in the query editor).
|
||||
<br>
|
||||
<em>${__url_time_range}</em> refers to graph range in the form of 'from=...&to=...'.
|
||||
<br>
|
||||
<em>${__value}</em> refers to current bucket value or max value if multiple values mode is on.
|
||||
<br>
|
||||
<em>${__value_N}</em> refers to Nth value in current bucket. Value indexes are started from 0. For instance,
|
||||
<em>${__value_1}</em> refers to second value.
|
||||
<br>
|
||||
<em>${__value_date}</em> refers to __value formatted as date.
|
||||
<em>${__value_N_date}</em> refers to Nth value in current bucket formatted as date.
|
||||
</span>
|
||||
</info-popover>
|
||||
</label>
|
||||
<input type="text" class="gf-form-input" placeholder="https://www.google.es" data-placement="top"
|
||||
bs-tooltip="'This is the url to be shown when the user clicks on it'" ng-model="item.urlTemplate"
|
||||
ng-change="ctrl.refresh()">
|
||||
<label class="gf-form-label">
|
||||
<i class="fa fa-trash pointer" ng-click="editor.onRemoveUrl($index)"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-4">Icon</label>
|
||||
<label class="gf-form-label width-2">
|
||||
<i class="fa fa-{{item.urlIcon}}"></i>
|
||||
</label>
|
||||
<input
|
||||
class="gf-form-input width-10"
|
||||
type="text"
|
||||
ng-model="item.urlIcon"
|
||||
bs-typeahead="editor.getFAIconClasses"
|
||||
bs-tooltip="'The icon for URL link'"
|
||||
data-placement="top"
|
||||
ng-blur="ctrl.render()"
|
||||
data-min-length="0"
|
||||
data-items="100"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-4">Label</label>
|
||||
<input type="text" class="gf-form-input width-16" placeholder="My URL" data-placement="top"
|
||||
bs-tooltip="'Text for URL link'"
|
||||
ng-model="item.urlText" ng-change="ctrl.refresh()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<gf-form-switch class="gf-form" label-class="width-8" label="Lowercase link" checked="item.urlToLowerCase"
|
||||
on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Date format</label>
|
||||
<input type="text" class="gf-form-input width-12" placeholder="YYYY/MM/DD/HH_mm_ss"
|
||||
data-placement="top" bs-tooltip="'Date format for __value_date'"
|
||||
bs-typeahead="editor.getValueDateFormats"
|
||||
data-min-length="0"
|
||||
data-items="100"
|
||||
ng-model="item.valueDateFormat"
|
||||
ng-blur="ctrl.render()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form"></div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="editor.onAddUrl()">
|
||||
<i class="fa fa-plus"></i> Add URL
|
||||
</button>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-inverse" ng-click="editor.onRemoveUrls()">
|
||||
<i class="fa fa-minus"></i> Remove all
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+5
-5
@@ -9,7 +9,7 @@
|
||||
"name": "Flant JSC",
|
||||
"url": "http://flant.com"
|
||||
},
|
||||
"keywords": ["status", "heatmap", "panel"],
|
||||
"keywords": ["status", "statusmap", "heatmap", "panel"],
|
||||
"logos": {
|
||||
"small": "img/logo.svg",
|
||||
"large": "img/logo.svg"
|
||||
@@ -17,7 +17,6 @@
|
||||
"links": [
|
||||
{"name": "Github", "url": "https://github.com/flant/grafana-statusmap"},
|
||||
{"name": "MIT Licence", "url": "https://github.com/flant/grafana-statusmap/blob/master/LICENSE"}
|
||||
|
||||
],
|
||||
"screenshots": [
|
||||
{"name": "Panel with light theme", "path": "img/flant-statusmap-panel.png"},
|
||||
@@ -25,12 +24,13 @@
|
||||
{"name": "Color mapping", "path": "img/color-mapping.png"},
|
||||
{"name": "Queries example", "path": "img/queries-example.png"}
|
||||
],
|
||||
"version": "0.2.0",
|
||||
"updated": "2019-12-03"
|
||||
"version": "0.3.2",
|
||||
"updated": "2020-08-13"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "5.1.x",
|
||||
"grafanaDependency": ">=5.4.3",
|
||||
"grafanaVersion": "5.4.x",
|
||||
"plugins": [ ]
|
||||
}
|
||||
}
|
||||
|
||||
+133
-82
@@ -3,11 +3,13 @@ import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import {appEvents, contextSrv} from 'app/core/core';
|
||||
import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks';
|
||||
import * as d3 from 'd3';
|
||||
import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
|
||||
import {StatusmapTooltip} from './tooltip';
|
||||
import {AnnotationTooltip} from './annotations';
|
||||
import { Bucket, BucketMatrix, BucketMatrixPager } from './statusmap_data';
|
||||
import { StatusHeatmapCtrl, renderComplete } from './module';
|
||||
import { CoreEvents, PanelEvents } from './libs/grafana/events/index';
|
||||
|
||||
let MIN_CARD_SIZE = 5,
|
||||
CARD_H_SPACING = 2,
|
||||
@@ -24,6 +26,18 @@ export default function rendering(scope: any, elem: any, attrs: any, ctrl: any)
|
||||
return new StatusmapRenderer(scope, elem, attrs, ctrl);
|
||||
}
|
||||
|
||||
class Statusmap {
|
||||
$svg: any;
|
||||
svg: any;
|
||||
bucketMatrix: BucketMatrix;
|
||||
|
||||
timeRange: {from: number, to: number} = {from:0, to:0};
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusmapRenderer {
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
@@ -45,8 +59,9 @@ export class StatusmapRenderer {
|
||||
mouseUpHandler: any;
|
||||
xGridSize: number = 0;
|
||||
yGridSize: number = 0;
|
||||
data: any;
|
||||
cardsData: any;
|
||||
|
||||
bucketMatrix: BucketMatrix;
|
||||
bucketMatrixPager: BucketMatrixPager;
|
||||
panel: any;
|
||||
$heatmap: any;
|
||||
tooltip: StatusmapTooltip;
|
||||
@@ -60,9 +75,9 @@ export class StatusmapRenderer {
|
||||
margin: any;
|
||||
dataRangeWidingFactor: number = DATA_RANGE_WIDING_FACTOR;
|
||||
|
||||
constructor(private scope: any, private elem: any, attrs: any, private ctrl: any) {
|
||||
constructor(private scope: any, private elem: any, attrs: any, private ctrl: StatusHeatmapCtrl) {
|
||||
// $heatmap is JQuery object, but heatmap is D3
|
||||
this.$heatmap = this.elem.find('.status-heatmap-panel');
|
||||
this.$heatmap = this.elem.find('.statusmap-panel');
|
||||
this.tooltip = new StatusmapTooltip(this.$heatmap, this.scope);
|
||||
this.annotationTooltip = new AnnotationTooltip(this.$heatmap, this.scope);
|
||||
|
||||
@@ -77,7 +92,7 @@ export class StatusmapRenderer {
|
||||
this.padding = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
this.margin = { left: 25, right: 15, top: 10, bottom: 20 };
|
||||
|
||||
this.ctrl.events.on('render', this.onRender.bind(this));
|
||||
this.ctrl.events.on(PanelEvents.render, this.onRender.bind(this));
|
||||
|
||||
this.ctrl.tickValueFormatter = this.tickValueFormatter.bind(this);
|
||||
|
||||
@@ -85,15 +100,17 @@ export class StatusmapRenderer {
|
||||
// Selection and crosshair //
|
||||
/////////////////////////////
|
||||
|
||||
// Shared crosshair and tooltip
|
||||
appEvents.on('graph-hover', this.onGraphHover.bind(this), this.scope);
|
||||
// Shared crosshair and tooltip this.empty = true;
|
||||
|
||||
appEvents.on('graph-hover-clear', this.onGraphHoverClear.bind(this), this.scope);
|
||||
appEvents.on( CoreEvents.graphHover, this.onGraphHover.bind(this), this.scope);
|
||||
|
||||
appEvents.on( CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), this.scope);
|
||||
|
||||
// Register selection listeners
|
||||
this.$heatmap.on('mousedown', this.onMouseDown.bind(this));
|
||||
this.$heatmap.on('mousemove', this.onMouseMove.bind(this));
|
||||
this.$heatmap.on('mouseleave', this.onMouseLeave.bind(this));
|
||||
this.$heatmap.on('click', this.onMouseClick.bind(this));
|
||||
}
|
||||
|
||||
onGraphHoverClear() {
|
||||
@@ -110,14 +127,21 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
|
||||
setElementHeight() {
|
||||
setElementHeight(): boolean {
|
||||
try {
|
||||
var height = this.ctrl.height || this.panel.height || this.ctrl.row.height;
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height -= this.panel.legend.show ? 32 : 10; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
if (this.panel.usingPagination) {
|
||||
// TODO get height of pagination controls.
|
||||
// reserve height for legend and for a row of pagination controls.
|
||||
height -= this.panel.legend.show ? 70 : 40; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
} else {
|
||||
// reserve height for legend
|
||||
height -= this.panel.legend.show ? 32 : 4; // bottom padding and space for legend. Change margin in .status-heatmap-color-legend !
|
||||
}
|
||||
|
||||
this.$heatmap.css('height', height + 'px');
|
||||
|
||||
@@ -127,7 +151,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
getYAxisWidth(elem: any) {
|
||||
getYAxisWidth(elem: any): number {
|
||||
const axisText = elem.selectAll(".axis-y text").nodes();
|
||||
const maxTextWidth = _.max(_.map(axisText, text => {
|
||||
// Use SVG getBBox method
|
||||
@@ -137,7 +161,7 @@ export class StatusmapRenderer {
|
||||
return Math.ceil(maxTextWidth);
|
||||
}
|
||||
|
||||
getXAxisHeight(elem: any) {
|
||||
getXAxisHeight(elem: any): number {
|
||||
let axisLine = elem.select(".axis-x line");
|
||||
if (!axisLine.empty()) {
|
||||
let axisLinePosition = parseFloat(elem.select(".axis-x line").attr("y2"));
|
||||
@@ -151,6 +175,10 @@ export class StatusmapRenderer {
|
||||
|
||||
addXAxis() {
|
||||
// Scale timestamps to cards centers
|
||||
//this.scope.xScale = this.xScale = d3.scaleTime()
|
||||
// .domain([this.timeRange.from, this.timeRange.to])
|
||||
// .range([this.xGridSize/2, this.chartWidth-this.xGridSize/2]);
|
||||
// Buckets without the most recent
|
||||
this.scope.xScale = this.xScale = d3.scaleTime()
|
||||
.domain([this.timeRange.from, this.timeRange.to])
|
||||
.range([this.xGridSize/2, this.chartWidth-this.xGridSize/2]);
|
||||
@@ -188,7 +216,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
// divide chart height by ticks for cards drawing
|
||||
getYScale(ticks) {
|
||||
getYScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
let step = this.chartHeight / ticks.length;
|
||||
// svg has y=0 on the top, so top card should have a minimal value in range
|
||||
@@ -202,7 +230,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
// divide chart height by ticks with offset for ticks drawing
|
||||
getYAxisScale(ticks) {
|
||||
getYAxisScale(ticks: any[]) {
|
||||
let range:any[] = [];
|
||||
let step = this.chartHeight / ticks.length;
|
||||
// svg has y=0 on the top, so top tick should have a minimal value in range
|
||||
@@ -216,13 +244,9 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
addYAxis() {
|
||||
let ticks = _.uniq(_.map(this.data, d => d.target));
|
||||
|
||||
// Set default Y min and max if no data
|
||||
if (_.isEmpty(this.data)) {
|
||||
ticks = [''];
|
||||
}
|
||||
let ticks = this.bucketMatrixPager.targets();
|
||||
|
||||
// TODO move sorting into bucketMatrixPager.
|
||||
if (this.panel.yAxisSort == 'a → z') {
|
||||
ticks.sort((a, b) => a.localeCompare(b, 'en', {ignorePunctuation: false, numeric: true}));
|
||||
} else if (this.panel.yAxisSort == 'z → a') {
|
||||
@@ -254,7 +278,7 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
// Wide Y values range and adjust to bucket size
|
||||
wideYAxisRange(min, max, tickInterval) {
|
||||
wideYAxisRange(min: number, max: number, tickInterval: number) {
|
||||
let y_widing = (max * (this.dataRangeWidingFactor - 1) - min * (this.dataRangeWidingFactor - 1)) / 2;
|
||||
let y_min, y_max;
|
||||
|
||||
@@ -284,7 +308,7 @@ export class StatusmapRenderer {
|
||||
|
||||
// Create svg element, add axes and
|
||||
// calculate sizes for cards drawing
|
||||
addHeatmapCanvas() {
|
||||
addStatusmapCanvas() {
|
||||
let heatmap_elem = this.$heatmap[0];
|
||||
|
||||
this.width = Math.floor(this.$heatmap.width()) - this.padding.right;
|
||||
@@ -294,8 +318,10 @@ export class StatusmapRenderer {
|
||||
this.heatmap.remove();
|
||||
}
|
||||
|
||||
// Insert svg as a first child into heatmap_elem
|
||||
// as the frozen tooltip moving logiс assumes that tooltip is the last child.
|
||||
this.heatmap = d3.select(heatmap_elem)
|
||||
.append("svg")
|
||||
.insert("svg",":first-child")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height);
|
||||
|
||||
@@ -308,7 +334,10 @@ export class StatusmapRenderer {
|
||||
this.cardRound = this.panel.cards.cardRound !== null ? this.panel.cards.cardRound : CARD_ROUND;
|
||||
|
||||
// calculate yOffset for YAxis
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.cardsData.yBucketSize);
|
||||
this.yGridSize = this.chartHeight;
|
||||
if (this.bucketMatrixPager.targets().length > 0) {
|
||||
this.yGridSize = Math.floor(this.chartHeight / this.bucketMatrixPager.targets().length);
|
||||
}
|
||||
this.cardHeight = this.yGridSize ? this.yGridSize - this.cardVSpacing : 0;
|
||||
this.yOffset = this.cardHeight / 2;
|
||||
|
||||
@@ -319,7 +348,7 @@ export class StatusmapRenderer {
|
||||
|
||||
// TODO allow per-y cardWidth!
|
||||
// we need to fill chartWidth with xBucketSize cards.
|
||||
this.xGridSize = this.chartWidth / (this.cardsData.xBucketSize+1);
|
||||
this.xGridSize = this.chartWidth / (this.bucketMatrix.xBucketSize+1);
|
||||
this.cardWidth = this.xGridSize - this.cardHSpacing;
|
||||
|
||||
this.addXAxis();
|
||||
@@ -334,36 +363,41 @@ export class StatusmapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
addHeatmap() {
|
||||
this.addHeatmapCanvas();
|
||||
|
||||
let maxValue = this.panel.color.max || this.cardsData.maxValue;
|
||||
let minValue = this.panel.color.min || this.cardsData.minValue;
|
||||
addStatusmap():void {
|
||||
let maxValue = this.panel.color.max || this.bucketMatrix.maxValue;
|
||||
let minValue = this.panel.color.min || this.bucketMatrix.minValue;
|
||||
|
||||
if (this.panel.color.mode !== 'discrete') {
|
||||
this.colorScale = this.getColorScale(maxValue, minValue);
|
||||
}
|
||||
this.setOpacityScale(maxValue);
|
||||
|
||||
let cards = this.heatmap.selectAll(".status-heatmap-card").data(this.cardsData.cards);
|
||||
cards.append("title");
|
||||
cards = cards.enter().append("rect")
|
||||
.attr("cardId", c => c.id)
|
||||
.attr("x", this.getCardX.bind(this))
|
||||
.attr("width", this.getCardWidth.bind(this))
|
||||
.attr("y", this.getCardY.bind(this))
|
||||
.attr("height", this.getCardHeight.bind(this))
|
||||
.attr("rx", this.cardRound)
|
||||
.attr("ry", this.cardRound)
|
||||
.attr("class", "bordered status-heatmap-card")
|
||||
.style("fill", this.getCardColor.bind(this))
|
||||
.style("stroke", this.getCardColor.bind(this))
|
||||
.style("stroke-width", 0)
|
||||
//.style("stroke-width", getCardStrokeWidth)
|
||||
//.style("stroke-dasharray", "3,3")
|
||||
.style("opacity", this.getCardOpacity.bind(this));
|
||||
// Draw cards from buckets.
|
||||
this.heatmap.selectAll(".statusmap-cards-row").data(this.bucketMatrixPager.targets())
|
||||
.enter()
|
||||
.selectAll(".statustmap-card")
|
||||
.data((target:string) => this.bucketMatrix.buckets[target])
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("cardId", (b:Bucket) => b.id)
|
||||
.attr("xid", (b:Bucket) => b.xid)
|
||||
.attr("yid", (b:Bucket) => b.yLabel)
|
||||
.attr("x", this.getCardX.bind(this))
|
||||
.attr("width", this.getCardWidth.bind(this))
|
||||
.attr("y", this.getCardY.bind(this))
|
||||
.attr("height", this.getCardHeight.bind(this))
|
||||
.attr("rx", this.cardRound)
|
||||
.attr("ry", this.cardRound)
|
||||
.attr("class", (b:Bucket) => b.isEmpty() ? "empty-card" : "bordered statusmap-card")
|
||||
.style("fill", this.getCardColor.bind(this))
|
||||
.style("stroke", this.getCardColor.bind(this))
|
||||
.style("stroke-width", 0)
|
||||
//.style("stroke-width", getCardStrokeWidth)
|
||||
//.style("stroke-dasharray", "3,3")
|
||||
.style("opacity", this.getCardOpacity.bind(this));
|
||||
|
||||
let $cards = this.$heatmap.find(".status-heatmap-card");
|
||||
// Set mouse events on cards.
|
||||
let $cards = this.$heatmap.find(".statusmap-card + .bordered");
|
||||
$cards
|
||||
.on("mouseenter", (event) => {
|
||||
this.tooltip.mouseOverBucket = true;
|
||||
@@ -376,7 +410,7 @@ export class StatusmapRenderer {
|
||||
|
||||
this._renderAnnotations();
|
||||
|
||||
this.ctrl.events.emit('render-complete', {
|
||||
this.ctrl.events.emit(renderComplete, {
|
||||
"chartWidth": this.chartWidth
|
||||
});
|
||||
}
|
||||
@@ -402,9 +436,6 @@ export class StatusmapRenderer {
|
||||
|
||||
getColorScale(maxValue, minValue = 0) {
|
||||
let colorScheme = _.find(this.ctrl.colorSchemes, {value: this.panel.color.colorScheme});
|
||||
// if (!colorScheme) {
|
||||
//
|
||||
// }
|
||||
let colorInterpolator = d3ScaleChromatic[colorScheme.value];
|
||||
let colorScaleInverted = colorScheme.invert === 'always' ||
|
||||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
|
||||
@@ -430,10 +461,12 @@ export class StatusmapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
getCardX(d) {
|
||||
getCardX(b: Bucket) {
|
||||
let x;
|
||||
// cx is the center of the card. Card should be placed to the left.
|
||||
let cx = this.xScale(d.x);
|
||||
//let cx = this.xScale(d.x);
|
||||
let rightX = (b.relTo / this.bucketMatrix.rangeMs) * this.chartWidth;
|
||||
let cx = rightX - this.cardWidth/2;
|
||||
|
||||
if (cx - this.cardWidth/2 < 0) {
|
||||
x = this.yAxisWidth + this.cardHSpacing/2;
|
||||
@@ -445,9 +478,13 @@ export class StatusmapRenderer {
|
||||
}
|
||||
|
||||
// xScale returns card center. Adjust cardWidth in case of overlaping.
|
||||
getCardWidth(d) {
|
||||
getCardWidth(b: Bucket) {
|
||||
//return 20;
|
||||
let w;
|
||||
let cx = this.xScale(d.x);
|
||||
|
||||
let rightX = (b.relTo / this.bucketMatrix.rangeMs) * this.chartWidth;
|
||||
let cx = rightX - this.cardWidth/2;
|
||||
//let cx = this.xScale(d.x);
|
||||
|
||||
if (cx < this.cardWidth/2) {
|
||||
// Center should not exceed half of card.
|
||||
@@ -471,12 +508,15 @@ export class StatusmapRenderer {
|
||||
return w;
|
||||
}
|
||||
|
||||
getCardY(d) {
|
||||
return this.yScale(d.y) + this.chartTop - this.cardHeight - this.cardVSpacing/2;
|
||||
// Top y for card.
|
||||
// yScale gives ???
|
||||
getCardY(b: Bucket) {
|
||||
return this.yScale(b.yLabel) + this.chartTop - this.cardHeight - this.cardVSpacing/2;
|
||||
}
|
||||
|
||||
getCardHeight(d) {
|
||||
let ys = this.yScale(d.y);
|
||||
getCardHeight(b: Bucket) {
|
||||
//return 20;
|
||||
let ys = this.yScale(b.yLabel);
|
||||
let y = ys + this.chartTop - this.cardHeight - this.cardVSpacing/2;
|
||||
let h = this.cardHeight;
|
||||
|
||||
@@ -501,28 +541,32 @@ export class StatusmapRenderer {
|
||||
return h;
|
||||
}
|
||||
|
||||
getCardColor(d) {
|
||||
getCardColor(bucket: Bucket) {
|
||||
if (this.panel.color.mode === 'opacity') {
|
||||
return this.panel.color.cardColor;
|
||||
} else if (this.panel.color.mode === 'spectrum') {
|
||||
return this.colorScale(d.value);
|
||||
return this.colorScale(bucket.value);
|
||||
} else if (this.panel.color.mode === 'discrete') {
|
||||
return this.ctrl.discreteHelper.getBucketColor(d.values);
|
||||
if (this.panel.seriesFilterIndex != null && this.panel.seriesFilterIndex != -1) {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColorSingle(bucket.values[this.panel.seriesFilterIndex]);
|
||||
} else {
|
||||
return this.ctrl.discreteExtraSeries.getBucketColor(bucket.values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCardOpacity(d) {
|
||||
if (this.panel.nullPointMode === 'as empty' && d.value == null ) {
|
||||
getCardOpacity(bucket: Bucket) {
|
||||
if (this.panel.nullPointMode === 'as empty' && bucket.value == null ) {
|
||||
return 0;
|
||||
}
|
||||
if (this.panel.color.mode === 'opacity') {
|
||||
return this.opacityScale(d.value);
|
||||
return this.opacityScale(bucket.value);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
getCardStrokeWidth(d) {
|
||||
getCardStrokeWidth(b: Bucket) {
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
return '1';
|
||||
}
|
||||
@@ -572,13 +616,13 @@ export class StatusmapRenderer {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
onMouseLeave() {
|
||||
appEvents.emit('graph-hover-clear');
|
||||
onMouseLeave(e) {
|
||||
appEvents.emit(CoreEvents.graphHoverClear);
|
||||
this.clearCrosshair();
|
||||
//annotationTooltip.destroy();
|
||||
this.annotationTooltip.destroy();
|
||||
}
|
||||
|
||||
onMouseMove(event) {
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (!this.heatmap) { return; }
|
||||
|
||||
const offset = this.getEventOffset(event);
|
||||
@@ -594,11 +638,19 @@ export class StatusmapRenderer {
|
||||
//const pos = this.getEventPos(event, offset);
|
||||
this.emitGraphHoverEvent(event);
|
||||
this.drawCrosshair(offset.x);
|
||||
this.tooltip.show(event); //, data); // pos, this.data
|
||||
this.tooltip.show(event);
|
||||
this.annotationTooltip.show(event);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO emit an event and move logic to panelCtrl
|
||||
public onMouseClick(e: MouseEvent) {
|
||||
if (this.ctrl.panel.tooltip.freezeOnClick) {
|
||||
this.tooltip.showFrozen(e);
|
||||
this.tooltip.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
getEventPos(event, offset) {
|
||||
const x = this.xScale.invert(offset.x - this.yAxisWidth).valueOf();
|
||||
const y = this.yScale.invert(offset.y - this.chartTop);
|
||||
@@ -631,7 +683,7 @@ export class StatusmapRenderer {
|
||||
pos.panelRelY = Math.max(event.offsetY / this.height, 0.001);
|
||||
|
||||
// broadcast to other graph panels that we are hovering
|
||||
appEvents.emit('graph-hover', {pos: pos, panel: this.panel});
|
||||
appEvents.emit(CoreEvents.graphHover, {pos: pos, panel: this.panel});
|
||||
}
|
||||
|
||||
limitSelection(x2) {
|
||||
@@ -701,24 +753,23 @@ export class StatusmapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
this.data = this.ctrl.data;
|
||||
this.panel = this.ctrl.panel;
|
||||
this.timeRange = this.ctrl.range;
|
||||
this.cardsData = this.ctrl.cardsData;
|
||||
this.bucketMatrix = this.ctrl.bucketMatrix;
|
||||
this.bucketMatrixPager = this.ctrl.bucketMatrixPager;
|
||||
|
||||
if (!this.data || !this.cardsData || !this.setElementHeight()) {
|
||||
if (!this.bucketMatrix || !this.setElementHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw default axes and return if no data
|
||||
if (_.isEmpty(this.cardsData.cards)) {
|
||||
this.addHeatmapCanvas();
|
||||
this.addStatusmapCanvas();
|
||||
if (this.bucketMatrix.noDatapoints) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addHeatmap();
|
||||
this.addStatusmap();
|
||||
this.scope.yAxisWidth = this.yAxisWidth;
|
||||
this.scope.xAxisHeight = this.xAxisHeight;
|
||||
this.scope.chartHeight = this.chartHeight;
|
||||
@@ -739,7 +790,7 @@ export class StatusmapRenderer {
|
||||
|
||||
let annoData = _.map(this.ctrl.annotations, (d,i) => ({"x": Math.floor(this.yAxisWidth + this.xScale(d.time)), "id":i, "anno": d.source}));
|
||||
|
||||
//console.log({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
|
||||
//({"ctrl_annotations": this.ctrl.annotations, "annoData": annoData});
|
||||
|
||||
let anno = this.heatmap
|
||||
.append("g")
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { migratePanelConfig } from '../panel_config_migration';
|
||||
|
||||
describe('when migrate from config with urls', () => {
|
||||
let panel: any;
|
||||
|
||||
describe('given full panel configuration', () => {
|
||||
const panelConfig = {
|
||||
"cards": {
|
||||
"cardHSpacing": 2,
|
||||
"cardMinWidth": 5,
|
||||
"cardRound": null,
|
||||
"cardVSpacing": 2
|
||||
},
|
||||
"color": {
|
||||
"cardColor": "#b4ff00",
|
||||
"colorScale": "sqrt",
|
||||
"colorScheme": "interpolateGnYlRd",
|
||||
"defaultColor": "#757575",
|
||||
"exponent": 0.5,
|
||||
"mode": "opacity",
|
||||
"thresholds": []
|
||||
},
|
||||
"data": {
|
||||
"decimals": null,
|
||||
"unitFormat": "short"
|
||||
},
|
||||
"datasource": "TestData DB",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"highlightCards": true,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"nullPointMode": "as empty",
|
||||
"seriesFilterIndex": -1,
|
||||
"targets": [
|
||||
{
|
||||
"aggregation": "Last",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1,3,3"
|
||||
},
|
||||
"decimals": 2,
|
||||
"displayAliasType": "Warning / Critical",
|
||||
"displayType": "Regular",
|
||||
"displayValueWithAlias": "Never",
|
||||
"refId": "A",
|
||||
"scenarioId": "predictable_csv_wave",
|
||||
"stringInput": "",
|
||||
"units": "none",
|
||||
"valueHandler": "Number Threshold"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Panel Title",
|
||||
"tooltip": {
|
||||
"show": true
|
||||
},
|
||||
"type": "flant-statusmap-panel",
|
||||
"urls": [
|
||||
{
|
||||
"base_url": "",
|
||||
"extraSeries": {
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "",
|
||||
"tooltip": "",
|
||||
"useExtraSeries": false,
|
||||
"useseriesname": true
|
||||
}
|
||||
],
|
||||
"useMax": true,
|
||||
"usingUrl": true,
|
||||
"xAxis": {
|
||||
"labelFormat": "%a %m/%d",
|
||||
"show": true
|
||||
},
|
||||
"yAxis": {
|
||||
"maxWidth": -1,
|
||||
"minWidth": -1,
|
||||
"show": true
|
||||
},
|
||||
"yAxisSort": "metrics"
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
panel = Object.assign({}, panelConfig);
|
||||
migratePanelConfig(panel);
|
||||
});
|
||||
|
||||
it('should migrate usingUrls to tooltip.freezeOnClick', () => {
|
||||
expect(panel.usingUrl).toBeUndefined();
|
||||
expect(panel.tooltip).toHaveProperty("freezeOnClick", true);
|
||||
expect(panel.tooltip.freezeOnClick).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have no urls', () => {
|
||||
expect(panel.urls).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have empty items in tooltip', () => {
|
||||
expect(panel.tooltip).toHaveProperty("items", []);
|
||||
expect(panel.tooltip.items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given configuration with non-empty base_url", () => {
|
||||
|
||||
describe("with variables", () => {
|
||||
const panelConfig = {
|
||||
"tooltip": {
|
||||
"show": true
|
||||
},
|
||||
"urls": [
|
||||
{
|
||||
"base_url": "https://google.com/$series_label$time",
|
||||
"extraSeries": {
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "google",
|
||||
"tooltip": "",
|
||||
"useExtraSeries": false,
|
||||
"useseriesname": true,
|
||||
},
|
||||
{
|
||||
"base_url": "example.com/$series_extra",
|
||||
"extraSeries": {
|
||||
"format": "YYYY/MM/DD/HH_mm_ss",
|
||||
"index": -1
|
||||
},
|
||||
"forcelowercase": true,
|
||||
"icon_fa": "external-link",
|
||||
"label": "DateLink",
|
||||
"useExtraSeries": true,
|
||||
}
|
||||
]
|
||||
};
|
||||
beforeEach(() => {
|
||||
panel = Object.assign({}, panelConfig);
|
||||
migratePanelConfig(panel);
|
||||
});
|
||||
|
||||
it("should have equal size of tooltip.items", () => {
|
||||
expect(panel.tooltip).toHaveProperty("items");
|
||||
expect(panel.tooltip.items).toHaveLength(panelConfig.urls.length);
|
||||
});
|
||||
it("should replace time variable with __url_time_range", () => {
|
||||
expect(panel.tooltip.items[0].urlTemplate).toContain("${__url_time_range}");
|
||||
});
|
||||
it("should replace series_label variable with __y_label", () => {
|
||||
expect(panel.tooltip.items[0].urlTemplate).toContain("${__y_label}");
|
||||
});
|
||||
it("should replace series_extra variable with __value_date", () => {
|
||||
expect(panel.tooltip.items[1].urlTemplate).toContain("${__value_date}");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+205
-35
@@ -1,45 +1,215 @@
|
||||
// A holder of a group of values
|
||||
class Bucket {
|
||||
// uniq id
|
||||
id: number = 0;
|
||||
// Array of values in this bucket
|
||||
values: any[] = [];
|
||||
columns: any[] = []; // From pr/86
|
||||
// a bucket has multiple values
|
||||
multipleValues: boolean = false;
|
||||
// a bucket has values that has no mapped color
|
||||
noColorDefined: boolean = false;
|
||||
// y label
|
||||
y: string = "";
|
||||
yLabel: string = "";
|
||||
// This value can be used to calculate a x coordinate on a graph
|
||||
x: number = 0;
|
||||
xid: number = 0;
|
||||
// a time range of this bucket
|
||||
from: number = 0;
|
||||
to: number = 0;
|
||||
// to and from relative to real "from"
|
||||
relFrom: number = 0;
|
||||
relTo: number = 0;
|
||||
|
||||
mostRecent: boolean = false;
|
||||
|
||||
// Saved minimum and maximum of values in this bucket
|
||||
minValue: number = 0;
|
||||
maxValue: number = 0;
|
||||
// A value if multiple values is not allowed
|
||||
value: number = 0;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
belong(ts: number): boolean {
|
||||
return ts >= this.from && ts <= this.to;
|
||||
}
|
||||
|
||||
put(value: any) {
|
||||
this.values.push(value);
|
||||
}
|
||||
|
||||
done() {
|
||||
// calculate min, max, value
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.values.length == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// A holder of values
|
||||
class Card {
|
||||
// uniq
|
||||
id: number = 0;
|
||||
// Array of values in this bucket
|
||||
values: any[] = [];
|
||||
// card has multiple values
|
||||
multipleValues: boolean = false;
|
||||
// card has values that has no color
|
||||
noColorDefined: boolean = false;
|
||||
//
|
||||
y: string = "";
|
||||
//
|
||||
x: number = 0;
|
||||
//
|
||||
minValue: number = 0;
|
||||
maxValue: number = 0;
|
||||
value: number = 0;
|
||||
class BucketMatrix {
|
||||
// buckets for each y label
|
||||
buckets: {[yLabel: string]: Bucket[]} = {};
|
||||
maxValue: number = 0;
|
||||
minValue: number = 0;
|
||||
multipleValues: boolean = false;
|
||||
noColorDefined: boolean = false;
|
||||
// a flag that indicate that buckets has stub values
|
||||
noDatapoints: boolean = false;
|
||||
|
||||
constructor() {
|
||||
// An array of row labels
|
||||
targets: string[] = [];
|
||||
rangeMs: number = 0;
|
||||
intervalMs: number = 0;
|
||||
|
||||
xBucketSize: number = 0; // TODO remove: a transition from CardsData
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
get(yid: string, xid: number): Bucket {
|
||||
if (yid in this.buckets) {
|
||||
if (xid in this.buckets[yid]) {
|
||||
return this.buckets[yid][xid];
|
||||
}
|
||||
}
|
||||
return new Bucket();
|
||||
}
|
||||
|
||||
hasData(): boolean {
|
||||
let hasData = false;
|
||||
if (this.targets.length > 0) {
|
||||
this.targets.map((target:string) => {
|
||||
if (this.buckets[target].length > 0) {
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return hasData;
|
||||
}
|
||||
}
|
||||
|
||||
export var pagerChanged:any = {name:'statusmap-pager-changed'};
|
||||
|
||||
class BucketMatrixPager {
|
||||
bucketMatrix: BucketMatrix;
|
||||
enable: boolean;
|
||||
defaultPageSize: number = -1;
|
||||
pageSize: number = -1;
|
||||
currentPage: number = 0;
|
||||
|
||||
constructor() {
|
||||
let m = new BucketMatrix();
|
||||
|
||||
this.bucketMatrix = m;
|
||||
}
|
||||
|
||||
// An array of row labels for current page.
|
||||
targets(): string[] {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.targets;
|
||||
}
|
||||
|
||||
return this.bucketMatrix.targets.slice(this.pageSize * this.currentPage, this.pageSize * (this.currentPage+1) );
|
||||
}
|
||||
|
||||
buckets(): {[yLabel: string]: Bucket[]} {
|
||||
if (!this.enable) {
|
||||
return this.bucketMatrix.buckets;
|
||||
}
|
||||
|
||||
let buckets: {[yLabel: string]: Bucket[]} = {}
|
||||
let me = this;
|
||||
|
||||
this.targets().map(function (rowLabel) {
|
||||
buckets[rowLabel] = me.bucketMatrix.buckets[rowLabel];
|
||||
})
|
||||
|
||||
return buckets;
|
||||
}
|
||||
|
||||
setEnable(enable: boolean) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
setCurrent(num: number): void {
|
||||
this.currentPage = num;
|
||||
}
|
||||
|
||||
setDefaultPageSize(num: number): void {
|
||||
this.defaultPageSize = num;
|
||||
}
|
||||
|
||||
setPageSize(num: number): void {
|
||||
this.pageSize = num;
|
||||
}
|
||||
|
||||
pages(): number {
|
||||
return Math.ceil(this.totalRows() / this.pageSize);
|
||||
}
|
||||
|
||||
totalRows(): number {
|
||||
return this.bucketMatrix.targets.length;
|
||||
}
|
||||
|
||||
pageStartRow(): number {
|
||||
if (!this.enable) {
|
||||
return 1;
|
||||
}
|
||||
return (this.pageSize * this.currentPage) + 1;
|
||||
}
|
||||
|
||||
pageEndRow(): number {
|
||||
if (!this.enable) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
let last = this.pageSize * (this.currentPage+1);
|
||||
if (last > this.totalRows()) {
|
||||
return this.totalRows();
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
hasNext(): boolean {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.currentPage+1)*this.pageSize >= this.totalRows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// currentPage >= ctrl.bucketMatrix.targets.length/ctrl.pageSizeViewer - 1 || ctrl.numberOfPages == 0
|
||||
}
|
||||
|
||||
hasPrev(): boolean {
|
||||
if (!this.enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.currentPage > 0;
|
||||
}
|
||||
|
||||
switchToNext() {
|
||||
if (this.hasNext()) {
|
||||
this.currentPage = this.currentPage + 1;
|
||||
}
|
||||
}
|
||||
|
||||
class CardsStorage {
|
||||
cards: Card[] = [];
|
||||
xBucketSize: number = 0;
|
||||
yBucketSize: number = 0;
|
||||
maxValue: number;
|
||||
minValue: number;
|
||||
multipleValues: boolean = false;
|
||||
noColorDefined: boolean = false;
|
||||
targets: string[] = [];
|
||||
targetIndex: any;
|
||||
|
||||
constructor() {
|
||||
this.maxValue = 0;
|
||||
this.minValue = 0;
|
||||
|
||||
switchToPrev() {
|
||||
if (this.hasPrev()) {
|
||||
this.currentPage = this.currentPage - 1;
|
||||
}
|
||||
}
|
||||
|
||||
export {CardsStorage, Card};
|
||||
}
|
||||
|
||||
export {Bucket, BucketMatrix, BucketMatrixPager };
|
||||
+198
-45
@@ -2,25 +2,34 @@ import d3 from 'd3';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { StatusHeatmapCtrl } from './module';
|
||||
|
||||
let TOOLTIP_PADDING_X = 30;
|
||||
let TOOLTIP_PADDING_Y = 5;
|
||||
|
||||
// TODO rename file to tooltip_ctrl.ts
|
||||
// TODO move DefaultValueDateFormat into tooltip.ts, as it used in several places (migration, partial and tooltip render)
|
||||
let DefaultValueDateFormat = 'YYYY/MM/DD/HH_mm_ss';
|
||||
|
||||
export class StatusmapTooltip {
|
||||
tooltip: any;
|
||||
scope: any;
|
||||
dashboard: any;
|
||||
panelCtrl: any;
|
||||
panelCtrl: StatusHeatmapCtrl;
|
||||
panel: any;
|
||||
heatmapPanel: any;
|
||||
panelElem: any;
|
||||
mouseOverBucket: any;
|
||||
originalFillColor: any;
|
||||
|
||||
tooltipWidth: number;
|
||||
tooltipFrozen: any;
|
||||
|
||||
constructor(elem: any, scope: any) {
|
||||
this.scope = scope;
|
||||
this.dashboard = scope.ctrl.dashboard;
|
||||
this.panelCtrl = scope.ctrl;
|
||||
this.panel = scope.ctrl.panel;
|
||||
this.heatmapPanel = elem;
|
||||
this.panelElem = elem;
|
||||
this.mouseOverBucket = false;
|
||||
this.originalFillColor = null;
|
||||
|
||||
@@ -33,7 +42,7 @@ export class StatusmapTooltip {
|
||||
|
||||
if (!this.tooltip) {
|
||||
this.add();
|
||||
this.move(e);
|
||||
this.move(e, this.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +53,13 @@ export class StatusmapTooltip {
|
||||
onMouseMove(e) {
|
||||
if (!this.panel.tooltip.show) { return; }
|
||||
|
||||
this.move(e);
|
||||
this.move(e, this.tooltip);
|
||||
}
|
||||
|
||||
add() {
|
||||
this.tooltip = d3.select("body")
|
||||
.append("div")
|
||||
.attr("class", "statusmap-tooltip graph-tooltip grafana-tooltip");
|
||||
.attr("class", "graph-tooltip statusmap-tooltip");
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -61,59 +70,96 @@ export class StatusmapTooltip {
|
||||
this.tooltip = null;
|
||||
}
|
||||
|
||||
show(pos) {
|
||||
removeFrozen() {
|
||||
if (this.tooltipFrozen) {
|
||||
this.tooltipFrozen.remove();
|
||||
this.tooltipFrozen = null
|
||||
}
|
||||
}
|
||||
|
||||
showFrozen(pos: any) {
|
||||
this.removeFrozen();
|
||||
this.tooltipFrozen = d3.select(this.panelElem[0])
|
||||
.append("div")
|
||||
.attr("class", "graph-tooltip statusmap-tooltip statusmap-tooltip-frozen");
|
||||
this.displayTooltip(pos, this.tooltipFrozen, true);
|
||||
this.moveRelative(pos, this.tooltipFrozen);
|
||||
}
|
||||
|
||||
show(pos: any) {
|
||||
if (!this.panel.tooltip.show || !this.tooltip) { return; }
|
||||
// shared tooltip mode
|
||||
|
||||
// TODO support for shared tooltip mode
|
||||
if (pos.panelRelY) {
|
||||
return;
|
||||
}
|
||||
let cardId = d3.select(pos.target).attr('cardId');
|
||||
if (!cardId) {
|
||||
|
||||
this.displayTooltip(pos, this.tooltip, false);
|
||||
|
||||
this.move(pos, this.tooltip);
|
||||
}
|
||||
|
||||
// Retrieve bucket and create html content inside tooltip’s div element.
|
||||
displayTooltip(pos: any, tooltip: any, frozen: boolean) {
|
||||
let cardEl = d3.select(pos.target);
|
||||
let yid = cardEl.attr('yid');
|
||||
let xid = cardEl.attr('xid');
|
||||
let bucket = this.panelCtrl.bucketMatrix.get(yid, xid); // TODO string-to-number conversion for xid
|
||||
if (!bucket || bucket.isEmpty()) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
let card = this.panelCtrl.cardsData.cards[cardId];
|
||||
if (!card) {
|
||||
this.destroy();
|
||||
return;
|
||||
let timestamp = bucket.to;
|
||||
let yLabel = bucket.yLabel;
|
||||
let value = bucket.value;
|
||||
let values = bucket.values;
|
||||
// TODO create option for this formatting.
|
||||
let tooltipTimeFormat:string = 'YYYY-MM-DD HH:mm:ss';
|
||||
let time: Date = this.dashboard.formatDate(+timestamp, tooltipTimeFormat);
|
||||
|
||||
// Close button for the frozen tooltip.
|
||||
let tooltipClose:string = ``;
|
||||
if (frozen) {
|
||||
tooltipClose = `
|
||||
<a class="pointer pull-right small tooltip-close">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
let x = card.x;
|
||||
let y = card.y;
|
||||
let value = card.value;
|
||||
let values = card.values;
|
||||
let tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
let time = this.dashboard.formatDate(+x, tooltipTimeFormat);
|
||||
|
||||
let tooltipHtml = `<div class="graph-tooltip-time">${time}</div>
|
||||
<div class="statusmap-histogram"></div>`;
|
||||
let tooltipHtml:string = `<div class="graph-tooltip-time">${time}${tooltipClose}</div>`;
|
||||
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
let statuses = this.panelCtrl.discreteHelper.convertValuesToTooltips(values);
|
||||
let statusesHtml = '';
|
||||
if (statuses.length === 1) {
|
||||
statusesHtml = "status:";
|
||||
} else if (statuses.length > 1) {
|
||||
statusesHtml = "statuses:";
|
||||
let statuses;
|
||||
if (this.panel.seriesFilterIndex >= 0) {
|
||||
statuses = this.panelCtrl.discreteExtraSeries.convertValueToTooltips(value);
|
||||
} else {
|
||||
statuses = this.panelCtrl.discreteExtraSeries.convertValuesToTooltips(values);
|
||||
}
|
||||
|
||||
let statusTitle:string = "status:";
|
||||
if (statuses.length > 1) {
|
||||
statusTitle = "statuses:";
|
||||
}
|
||||
tooltipHtml += `
|
||||
<div>
|
||||
name: <b>${y}</b> <br>
|
||||
${statusesHtml}
|
||||
name: <b>${yLabel}</b>
|
||||
<br>
|
||||
<span>${statusTitle}</span>
|
||||
<ul>
|
||||
${_.join(_.map(statuses, v => `<li style="background-color: ${v.color}" class="discrete-item">${v.tooltip}</li>`), "")}
|
||||
${_.join(_.map(statuses, v => `<li style="background-color: ${v.color}; text-align:center" class="discrete-item">${v.tooltip}</li>`), "")}
|
||||
</ul>
|
||||
</div>`;
|
||||
} else {
|
||||
if (values.length === 1) {
|
||||
tooltipHtml += `<div>
|
||||
name: <b>${y}</b> <br>
|
||||
name: <b>${name}</b> <br>
|
||||
value: <b>${value}</b> <br>
|
||||
</div>`;
|
||||
} else {
|
||||
tooltipHtml += `<div>
|
||||
name: <b>${y}</b> <br>
|
||||
name: <b>${name}</b> <br>
|
||||
values:
|
||||
<ul>
|
||||
${_.join(_.map(values, v => `<li>${v}</li>`), "")}
|
||||
@@ -122,35 +168,107 @@ export class StatusmapTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
// "Ambiguous bucket state: Multiple values!";
|
||||
if (!this.panel.useMax && card.multipleValues) {
|
||||
tooltipHtml += `<div class="statusmap-histogram"></div>`;
|
||||
|
||||
if (this.panel.tooltip.showItems) {
|
||||
// Additional information: urls, etc.
|
||||
// Clone additional items
|
||||
let items: any = JSON.parse(JSON.stringify(this.panel.tooltip.items));
|
||||
|
||||
let scopedVars = {};
|
||||
|
||||
let valueVar;
|
||||
for (let i = 0; i < bucket.values.length; i++) {
|
||||
valueVar = `__value_${i}`;
|
||||
scopedVars[valueVar] = {value: bucket.values[i] };
|
||||
}
|
||||
scopedVars[`__value`] = {value: bucket.value};
|
||||
scopedVars[`__y_label`] = {value: yLabel};
|
||||
// Grafana 7.0 compatible
|
||||
scopedVars[`__url_time_range`] = {value: this.panelCtrl.retrieveTimeVar()};
|
||||
|
||||
for (let item of items) {
|
||||
if (_.isEmpty(item.urlTemplate)) {
|
||||
item.link = "#";
|
||||
} else {
|
||||
let dateFormat = item.valueDateFormat;
|
||||
if (dateFormat == '') {
|
||||
dateFormat = DefaultValueDateFormat;
|
||||
}
|
||||
let valueDateVar;
|
||||
for (let i = 0; i < bucket.values.length; i++) {
|
||||
valueDateVar = `__value_${i}_date`;
|
||||
scopedVars[valueDateVar] = {value: this.dashboard.formatDate(+bucket.values[i], dateFormat)};
|
||||
}
|
||||
scopedVars[`__value_date`] = {value: this.dashboard.formatDate(+bucket.value, dateFormat)};
|
||||
|
||||
item.link = this.panelCtrl.templateSrv.replace(item.urlTemplate, scopedVars)
|
||||
|
||||
// Force lowercase for link
|
||||
if (item.urlToLowerCase) {
|
||||
item.link = item.link.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
item.label = item.urlText;
|
||||
if (_.isEmpty(item.label)) {
|
||||
item.label = _.isEmpty(item.urlTemplate) ? "Empty URL" : _.truncate(item.link);
|
||||
}
|
||||
}
|
||||
|
||||
tooltipHtml += _.join(_.map(items, v => `
|
||||
<div>
|
||||
<a href="${v.link}" target="_blank">
|
||||
<div class="dashlist-item">
|
||||
<p class="dashlist-link dashlist-link-dash-db">
|
||||
<span style="word-wrap: break-word;" class="dash-title">${v.label}</span><span class="dashlist-star">
|
||||
<i class="fa fa-${v.urlIcon}"></i>
|
||||
</span></p> </div></a><div>`), "\n");
|
||||
|
||||
}
|
||||
|
||||
// Ambiguous state: there multiple values in bucket!
|
||||
// TODO rename useMax to expectMultipleValues
|
||||
if (!this.panel.useMax && bucket.multipleValues) {
|
||||
tooltipHtml += `<div><b>Error:</b> ${this.panelCtrl.dataWarnings.multipleValues.title}</div>`;
|
||||
}
|
||||
|
||||
// Discrete mode errors
|
||||
if (this.panel.color.mode === 'discrete') {
|
||||
if (card.noColorDefined) {
|
||||
let badValues = this.panelCtrl.discreteHelper.getNotColoredValues(values);
|
||||
if (bucket.noColorDefined) {
|
||||
let badValues = this.panelCtrl.discreteExtraSeries.getNotColoredValues(values);
|
||||
tooltipHtml += `<div><b>Error:</b> ${this.panelCtrl.dataWarnings.noColorDefined.title}
|
||||
<br>not colored values:
|
||||
<ul>
|
||||
${_.join(_.map(badValues, v => `<li>${v}</li>`), "")}
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.tooltip.html(tooltipHtml);
|
||||
tooltip.html(tooltipHtml);
|
||||
|
||||
this.move(pos);
|
||||
// Assign mouse event handlers for "frozen" tooltip.
|
||||
if (frozen) {
|
||||
// Stop propagation mouse events up to parents to allow interaction with frozen tooltip’s elements.
|
||||
tooltip
|
||||
.on("click", function(){d3.event.stopPropagation(); })
|
||||
.on("mousedown", function(){d3.event.stopPropagation(); })
|
||||
.on("mouseup", function(){d3.event.stopPropagation(); });
|
||||
|
||||
// Activate close button
|
||||
tooltip.select("a.tooltip-close")
|
||||
.on("click", this.removeFrozen.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
move(pos) {
|
||||
if (!this.tooltip) { return; }
|
||||
// Move tooltip as absolute positioned element.
|
||||
move(pos, tooltip) {
|
||||
if (!tooltip) { return; }
|
||||
|
||||
let elem = $(this.tooltip.node())[0];
|
||||
let elem = $(tooltip.node())[0];
|
||||
let tooltipWidth = elem.clientWidth;
|
||||
this.tooltipWidth = tooltipWidth;
|
||||
let tooltipHeight = elem.clientHeight;
|
||||
|
||||
let left = pos.pageX + TOOLTIP_PADDING_X;
|
||||
@@ -164,8 +282,43 @@ export class StatusmapTooltip {
|
||||
top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y;
|
||||
}
|
||||
|
||||
return this.tooltip
|
||||
return tooltip
|
||||
.style("left", left + "px")
|
||||
.style("top", top + "px");
|
||||
}
|
||||
|
||||
// Move tooltip relative to svg element of panel.
|
||||
moveRelative(pos, tooltip) {
|
||||
if (!tooltip) { return; }
|
||||
|
||||
let panelX = pos.pageX - this.panelElem.offset().left;
|
||||
let panelY = pos.pageY - this.panelElem.offset().top;
|
||||
let panelWidth = this.panelElem.width();
|
||||
let panelHeight = this.panelElem.height();
|
||||
|
||||
// 'position: relative' sets tooltip’s width to 100% of panel element.
|
||||
// Restore width from floating tooltip and add more space for 'Close' button.
|
||||
let tooltipWidth = this.tooltipWidth + 25;
|
||||
|
||||
// Left property is clamped so tooltip stays inside panel bound box.
|
||||
let tooltipLeft = panelX + TOOLTIP_PADDING_X;
|
||||
if (tooltipLeft + tooltipWidth > panelWidth) {
|
||||
tooltipLeft = panelWidth - tooltipWidth;
|
||||
}
|
||||
if (tooltipLeft < 0 ) {
|
||||
tooltipLeft = 0;
|
||||
}
|
||||
|
||||
// Frozen tooltip’s root element is appended next to panel’s svg element,
|
||||
// so top property is adjusted to move tooltip’s root element
|
||||
// up to the mouse pointer position.
|
||||
let tooltipTop = -(panelHeight - panelY + TOOLTIP_PADDING_Y);
|
||||
|
||||
return tooltip
|
||||
.style("left", tooltipLeft + "px")
|
||||
.style("top", tooltipTop + "px")
|
||||
.style("width", tooltipWidth + "px");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { StatusHeatmapCtrl } from './module';
|
||||
|
||||
let emptyTooltipItem = {
|
||||
urlText: '',
|
||||
urlTemplate: '',
|
||||
urlIcon: 'external-link',
|
||||
urlToLowerCase: true,
|
||||
valueDateFormat: ''
|
||||
};
|
||||
|
||||
export class TooltipEditorCtrl {
|
||||
panel: any;
|
||||
panelCtrl: StatusHeatmapCtrl;
|
||||
|
||||
constructor($scope: any) {
|
||||
$scope.editor = this;
|
||||
this.panelCtrl = $scope.ctrl as StatusHeatmapCtrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
onAddUrl() {
|
||||
this.panel.tooltip.items.push(Object.assign({}, emptyTooltipItem));
|
||||
this.render();
|
||||
}
|
||||
|
||||
onRemoveUrl(index: number) {
|
||||
this.panel.tooltip.items.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
onRemoveUrls() {
|
||||
this.panel.tooltip.items = [];
|
||||
this.render();
|
||||
}
|
||||
|
||||
getFAIconClasses() {
|
||||
return ['external-link', 'plus', 'anchor', 'ban', 'globe', 'gear', 'cloud', 'download', 'cloud-download'];
|
||||
}
|
||||
|
||||
getValueDateFormats() {
|
||||
return [
|
||||
'YYYY/MM/DD/HH_mm_ss',
|
||||
'YYYYMMDDHHmmss',
|
||||
'YYYY-MM-DD-HH-mm-ss'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function tooltipEditorCtrl() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
templateUrl: 'public/plugins/flant-statusmap-panel/partials/tooltip_editor.html',
|
||||
controller: TooltipEditorCtrl,
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -30,12 +30,12 @@
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitUseStrict": false,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": false,
|
||||
"downlevelIteration": true,
|
||||
"noUnusedLocals": true,
|
||||
"pretty": true,
|
||||
"typeRoots": ["./node_modules/@types"] //,/* ,"src/types"*/
|
||||
"typeRoots": ["./node_modules/@types" ] // , "./src/types" ]
|
||||
//"types": [
|
||||
// "lodash"
|
||||
//]
|
||||
|
||||
Reference in New Issue
Block a user