Commit 31a4d920 by Torkel Ödegaard

SingleStatPanel: Added graph to single stat panel, #951

parent 69fdfd5c
......@@ -17,6 +17,7 @@ function (_, crypto) {
window_title_prefix : 'Grafana - ',
panels : {
'graph': { path: 'panels/graph' },
'stats': { path: 'panels/stats' },
'text': { path: 'panels/text' }
},
plugins : {},
......
......@@ -9,7 +9,7 @@ function (angular, $, _) {
angular
.module('grafana.directives')
.directive('panelMenu', function($compile) {
var linkTemplate = '<a class="panel-title drag-handle">{{panel.title | interpolateTemplateVars}}</a>';
var linkTemplate = '<span class="panel-title drag-handle pointer">{{panel.title | interpolateTemplateVars}}</span>';
function createMenuTemplate($scope) {
var template = '<div class="panel-menu small">';
......@@ -113,7 +113,7 @@ function (angular, $, _) {
dismiss(2500);
};
if ($scope.panelMeta.titlePos) {
if ($scope.panelMeta.titlePos && $scope.panel.title) {
elem.css('text-align', 'left');
$link.css('padding-left', '10px');
}
......
<div ng-controller='StatsCtrl'>
<div stats-panel></div>
<!-- <div class="stats&#45;panel&#45;value&#45;container"> -->
<!-- <span class="stats&#45;panel&#45;value">{{mainstat.value}}</span> -->
<!-- <span class="stats&#45;panel&#45;func">({{mainstat.func}})</span> -->
<!-- </div> -->
<!-- -->
<!-- <table class="stats&#45;panel&#45;table"> -->
<!-- <tr> -->
<!-- <th></th> -->
<!-- <th>avg</th> -->
<!-- <th>min</th> -->
<!-- <th>max</th> -->
<!-- <th>current</th> -->
<!-- <th>total</th> -->
<!-- </tr> -->
<!-- <tr class="stats&#45;series&#45;item" ng&#45;repeat="series in series"> -->
<!-- <td> -->
<!-- <i class='icon&#45;minus pointer' ng&#45;style="{color: series.color}"></i> -->
<!-- {{series.info.alias}} -->
<!-- </td> -->
<!-- <td>{{series.info.avg}}</td> -->
<!-- <td>{{series.info.min}}</td> -->
<!-- <td>{{series.info.max}}</td> -->
<!-- <td>{{series.info.current}}</td> -->
<!-- <td>{{series.info.total}}</td> -->
<!-- </tr> -->
<!-- </table> -->
<div class="stats-panel" stats-panel></div>
<div class="clearfix"></div>
<div style="margin-top: 30px" ng-if="editMode">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-bar-chart"></i>
Panel settings
<i class="icon icon-dashboard"></i>
Singlestat
</div>
<div ng-model="editor.index" bs-tabs>
......
......@@ -5,14 +5,15 @@ define([
'components/timeSeries',
'kbn',
'services/panelSrv',
'./statsDirective',
],
function (angular, app, _, TimeSeries, kbn) {
'use strict';
var module = angular.module('grafana.panels.stats', []);
var module = angular.module('grafana.panels.stats');
app.useModule(module);
module.controller('StatsCtrl', function($scope, panelSrv, timeSrv, $rootScope) {
module.controller('StatsCtrl', function($scope, panelSrv, timeSrv) {
$scope.panelMeta = {
titlePos: 'left',
......@@ -27,7 +28,7 @@ function (angular, app, _, TimeSeries, kbn) {
src:'app/partials/metrics.html'
},
{
title: 'Display Styles',
title: 'Options',
src:'app/panels/stats/statsEditor.html'
}
],
......@@ -39,25 +40,20 @@ function (angular, app, _, TimeSeries, kbn) {
targets: [{}],
cacheTimeout: null,
format: 'none',
stats: {
show: true,
avg: true,
template: '{{value}} {{func}}'
},
coloring: {
thresholds: '',
background: false,
value: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"]
},
table: {
show: true,
template: '{{avg}} !(avg)',
thresholds: '',
colorBackground: false,
colorValue: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
sparkline: {
show: false,
full: false,
lineColor: 'rgb(31, 120, 193)',
fillColor: 'rgb(31, 120, 193)',
}
};
_.defaults($scope.panel, _d);
_.defaults($scope.panel.stats, _d.stats);
_.defaults($scope.panel.coloring, _d.coloring);
$scope.init = function() {
panelSrv.init($scope);
......@@ -101,43 +97,33 @@ function (angular, app, _, TimeSeries, kbn) {
$scope.render();
};
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var color = $rootScope.colors[index];
var seriesInfo = {
alias: alias,
enable: true,
color: color
};
$scope.seriesHandler = function(seriesData) {
var series = new TimeSeries({
datapoints: datapoints,
info: seriesInfo,
datapoints: seriesData.datapoints,
info: { alias: seriesData.target },
});
series.points = series.getFlotPairs('connected');
series.data = series.getFlotPairs('connected');
return series;
};
$scope.setColoring = function(options) {
if (options.background) {
$scope.panel.coloring.value = false;
$scope.panel.coloring.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
$scope.panel.colorValue = false;
$scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
}
else {
$scope.panel.coloring.background = false;
$scope.panel.coloring.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
$scope.panel.colorBackground = false;
$scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
}
$scope.render();
};
$scope.invertColorOrder = function() {
var tmp = $scope.panel.coloring.colors[0];
$scope.panel.coloring.colors[0] = $scope.panel.coloring.colors[2];
$scope.panel.coloring.colors[2] = tmp;
var tmp = $scope.panel.colors[0];
$scope.panel.colors[0] = $scope.panel.colors[2];
$scope.panel.colors[2] = tmp;
$scope.render();
};
......@@ -153,11 +139,11 @@ function (angular, app, _, TimeSeries, kbn) {
series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7);
}
data.thresholds = $scope.panel.coloring.thresholds.split(',').map(function(strVale) {
data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
return Number(strVale.trim());
});
data.colorMap = $scope.panel.coloring.colors;
data.colorMap = $scope.panel.colors;
$scope.data = data;
$scope.$emit('render');
......@@ -165,112 +151,4 @@ function (angular, app, _, TimeSeries, kbn) {
$scope.init();
});
module.directive('statsPanel', function() {
return {
link: function(scope, elem) {
var data;
var valueRegex = /\{\{([a-zA-Z]+)\}\}/g;
var smallValueTextRegex = /!(\S+)/g;
scope.$on('render', function() {
data = scope.data;
data.mainValue = null;
if (!data || data.series.length === 0) {
elem.html('no data');
return;
}
render();
});
function applyColoringThresholds(value, valueString) {
if (!scope.panel.coloring.value) {
return valueString;
}
var color = getColorForValue(value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
return valueString;
}
function getColorForValue(value) {
for (var i = data.thresholds.length - 1; i >= 0 ; i--) {
if (value > data.thresholds[i]) {
return data.colorMap[i];
}
}
return null;
}
function valueTemplateReplaceFunc(match, statType) {
var stats = data.series[0].stats;
data.mainValue = stats[statType];
var valueFormated = scope.formatValue(data.mainValue);
return applyColoringThresholds(data.mainValue, valueFormated);
}
function smallValueTextReplaceFunc(match, text) {
return '<span class="stats-panel-value-small">' + text + '</span>';
}
function render() {
var panel = scope.panel;
var body = '';
var i, series;
if (panel.stats.show) {
body += '<div class="stats-panel-value-container">';
body += '<span class="stats-panel-value">';
var valueHtml = panel.stats.template.replace(valueRegex, valueTemplateReplaceFunc);
body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc);
body += '</div>';
body += '</div>';
}
if (panel.coloring.background && data.mainValue) {
var color = getColorForValue(data.mainValue);
if (color) {
elem.parents('.panel-container').css('background-color', color);
if (scope.fullscreen) {
elem.css('background-color', color);
} else {
elem.css('background-color', '');
}
}
} else {
elem.parents('.panel-container').css('background-color', '');
elem.css('background-color', '');
}
if (panel.table.show) {
body += '<table class="stats-panel-table">';
body += '<tr>';
body += '<th></th><th>avg</th><th>min</th><th>max</th><th>current</th><th>total</th>';
body += '</tr>';
for (i = 0; i < data.series.length; i++) {
series = data.series[i];
body += '<tr>';
body += '<td><i class="icon-minus pointer" style="color:' + series.color + '"></i> ';
body += series.info.alias + ' </td>';
body += '<td>' + series.info.avg + '</td>';
body += '<td>' + series.info.min + '</td>';
body += '<td>' + series.info.max + '</td>';
body += '<td>' + series.info.total + '</td>';
body += '<td>' + series.info.current + '</td>';
}
body += '</table>';
}
elem.html(body);
}
}
};
});
});
define([
'angular',
'app',
'lodash',
'kbn',
'jquery',
'jquery.flot',
'jquery.flot.time',
],
function (angular, app, _, kbn, $) {
'use strict';
var module = angular.module('grafana.panels.stats', []);
app.useModule(module);
module.directive('statsPanel', function() {
return {
link: function(scope, elem) {
var data;
var valueRegex = /\{\{([a-zA-Z]+)\}\}/g;
var smallValueTextRegex = /!(\S+)/g;
var $panelContainer = elem.parents('.panel-container');
scope.$on('render', function() {
data = scope.data;
data.mainValue = null;
if (!data || data.series.length === 0) {
elem.html('no data');
return;
}
render();
});
function setElementHeight() {
try {
var height = scope.height || scope.panel.height || scope.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
height -= scope.panel.title ? 24 : 9; // subtract panel title bar
elem.css('height', height + 'px');
return true;
} catch(e) { // IE throws errors sometimes
return false;
}
}
function applyColoringThresholds(value, valueString) {
if (!scope.panel.colorValue) {
return valueString;
}
var color = getColorForValue(value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
return valueString;
}
function getColorForValue(value) {
for (var i = data.thresholds.length - 1; i >= 0 ; i--) {
if (value > data.thresholds[i]) {
return data.colorMap[i];
}
}
return null;
}
function valueTemplateReplaceFunc(match, statType) {
var stats = data.series[0].stats;
data.mainValue = stats[statType];
var valueFormated = scope.formatValue(data.mainValue);
return applyColoringThresholds(data.mainValue, valueFormated);
}
function smallValueTextReplaceFunc(match, text) {
return '<span class="stats-panel-value-small">' + text + '</span>';
}
function render() {
setElementHeight();
var panel = scope.panel;
var body = '';
body += '<div class="stats-panel-value-container">';
body += '<span class="stats-panel-value">';
var valueHtml = panel.template.replace(valueRegex, valueTemplateReplaceFunc);
body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc);
body += '</div>';
body += '</div>';
if (panel.colorBackground && data.mainValue) {
var color = getColorForValue(data.mainValue);
if (color) {
$panelContainer.css('background-color', color);
if (scope.fullscreen) {
elem.css('background-color', color);
} else {
elem.css('background-color', '');
}
}
} else {
$panelContainer.css('background-color', '');
elem.css('background-color', '');
}
var width = elem.width() + 20;
var height = elem.height() || 100;
var plotCanvas = $('<div></div>');
var plotCss = {};
plotCss.position = 'absolute';
if (panel.sparkline.full) {
plotCss.bottom = '5px';
plotCss.left = '-5px';
plotCss.width = (width - 10) + 'px';
plotCss.height = (height - 45) + 'px';
}
else {
plotCss.bottom = "0px";
plotCss.left = "-5px";
plotCss.width = (width - 10) + 'px';
plotCss.height = Math.floor(height * 0.3) + "px";
}
plotCanvas.css(plotCss);
var options = {
legend: { show: false },
series: {
lines: {
show: true,
fill: 1,
lineWidth: 1,
fillColor: panel.sparkline.fillColor,
},
},
yaxes: { show: false },
xaxis: {
show: false,
mode: "time",
min: scope.range.from.getTime(),
max: scope.range.to.getTime(),
},
grid: { hoverable: false, show: false },
};
elem.html(body);
elem.append(plotCanvas);
data.series[0].color = panel.sparkline.lineColor;
setTimeout(function() {
$.plot(plotCanvas, [data.series[0]], options);
}, 200);
}
}
};
});
});
<div class="editor-row">
<div class="section">
<h5>Main options</h5>
<editor-opt-bool text="Show table" model="panel.table.show" change="render()"></editor-opt-bool>
<editor-opt-bool text="Show big values" model="panel.stats.show" change="render()"></editor-opt-bool>
</div>
<div class="section" ng-if="panel.stats">
<div class="section">
<h5>Big values</h5>
<div class="editor-option">
<label class="small">Template</label>
<input type="text" class="input-large" ng-model="panel.stats.template" ng-blur="render()"></input>
<input type="text" class="input-xlarge" ng-model="panel.template" ng-blur="render()"></input>
</div>
</div>
<div class="section">
......@@ -20,17 +15,17 @@
</div>
<div class="section">
<h5>Coloring</h5>
<editor-opt-bool text="Background" model="panel.coloring.background" change="setColoring({background: true})"></editor-opt-bool>
<editor-opt-bool text="Value" model="panel.coloring.value" change="setColoring({value: true})"></editor-opt-bool>
<editor-opt-bool text="Background" model="panel.colorBackground" change="setColoring({background: true})"></editor-opt-bool>
<editor-opt-bool text="Value" model="panel.colorValue" change="setColoring({value: true})"></editor-opt-bool>
<div class="editor-option">
<label class="small">Thresholds</label>
<input type="text" class="input-large" ng-model="panel.coloring.thresholds" ng-blur="render()"></input>
<input type="text" class="input-large" ng-model="panel.thresholds" ng-blur="render()"></input>
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.coloring.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.coloring.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.coloring.colors[2]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
</div>
</div>
......@@ -38,6 +33,19 @@
<div class="editor-row">
<div class="section">
<h5>Spark lines</h5>
</div>
<editor-opt-bool text="Spark line" model="panel.sparkline.show" change="render()"></editor-opt-bool>
<editor-opt-bool text="Background mode" model="panel.sparkline.full" change="render()"></editor-opt-bool>
<div class="editor-option">
<label class="small">Line color</label>
<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Series options</h5>
<div class="grafana-target" ng-repeat="series in data.series">
<div class="grafana-target-inner">
......
.stats-panel {
position: relative;
display: table;
width: 100%;
}
.stats-panel-value-container {
padding: 20px;
display: table-cell;
vertical-align: middle;
text-align: center;
position: relative;
z-index: 1;
}
.stats-panel-value {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment