Merge branch 'master' of github.com:torkelo/grafana-private into pro
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"preset" : "default",
|
||||
|
||||
"lineBreak" : {
|
||||
"before" : {
|
||||
"VariableDeclarationWithoutInit" : 0,
|
||||
},
|
||||
|
||||
"after": {
|
||||
"AssignmentOperator": -1,
|
||||
"ArgumentListArrayExpression": ">=1"
|
||||
}
|
||||
},
|
||||
|
||||
"whiteSpace" : {
|
||||
"before" : {
|
||||
},
|
||||
"after" : {
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -1,4 +1,24 @@
|
||||
# 1.8.0 (unreleased)
|
||||
# 1.9.0 (unreleased)
|
||||
|
||||
**UI Improvements*
|
||||
- [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu
|
||||
|
||||
- [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats
|
||||
- [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno
|
||||
|
||||
# 1.8.1 (unreleased)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #847](https://github.com/grafana/grafana/issues/847). Graph: Fix for series draw order not being the same after hiding/unhiding series
|
||||
- [Issue #851](https://github.com/grafana/grafana/issues/851). Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations
|
||||
- [Issue #846](https://github.com/grafana/grafana/issues/846). Edit panes: Issue when open row or json editor when scrolled down the page, unable to scroll and you did not see editor
|
||||
- [Issue #840](https://github.com/grafana/grafana/issues/840). Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard.
|
||||
- [Issue #859](https://github.com/grafana/grafana/issues/859). InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id
|
||||
- [Issue #852](https://github.com/grafana/grafana/issues/852). White theme: Fixes for hidden series legend text and disabled annotations color
|
||||
|
||||
# 1.8.0 (2014-09-22)
|
||||
|
||||
Read this [blog post](http://grafana.org/blog/2014/09/11/grafana-1-8-0-rc1-released.html) for an overview of all improvements.
|
||||
|
||||
**Fixes**
|
||||
- [Issue #802](https://github.com/grafana/grafana/issues/802). Annotations: Fix when using InfluxDB datasource
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "1.8.0-rc1",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.0-rc1"
|
||||
"version": "1.8.1",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.1.tar.gz"
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.8.0-rc1",
|
||||
"version": "1.8.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
|
||||
+50
-273
@@ -7,6 +7,7 @@ function($, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var kbn = {};
|
||||
kbn.valueFormats = {};
|
||||
|
||||
kbn.round_interval = function(interval) {
|
||||
switch (true) {
|
||||
@@ -309,240 +310,27 @@ function($, _, moment) {
|
||||
].join(';') + '"></div>';
|
||||
};
|
||||
|
||||
kbn.byteFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " B";
|
||||
break;
|
||||
case 1:
|
||||
ext = " KiB";
|
||||
break;
|
||||
case 2:
|
||||
ext = " MiB";
|
||||
break;
|
||||
case 3:
|
||||
ext = " GiB";
|
||||
break;
|
||||
case 4:
|
||||
ext = " TiB";
|
||||
break;
|
||||
case 5:
|
||||
ext = " PiB";
|
||||
break;
|
||||
case 6:
|
||||
ext = " EiB";
|
||||
break;
|
||||
case 7:
|
||||
ext = " ZiB";
|
||||
break;
|
||||
case 8:
|
||||
ext = " YiB";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
kbn.valueFormats.percent = function(size, decimals) {
|
||||
return kbn.toFixed(size, decimals) + '%';
|
||||
};
|
||||
|
||||
kbn.bitFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
kbn.formatFuncCreator = function(factor, extArray) {
|
||||
return function(size, decimals, scaledDecimals) {
|
||||
var steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
while (Math.abs(size) >= factor) {
|
||||
steps++;
|
||||
size /= factor;
|
||||
}
|
||||
if (steps > 0) {
|
||||
decimals = scaledDecimals + (3 * steps);
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " b";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kib";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mib";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gib";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tib";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pib";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Eib";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zib";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Yib";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
return kbn.toFixed(size, decimals) + extArray[steps];
|
||||
};
|
||||
};
|
||||
|
||||
kbn.bpsFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1000) {
|
||||
steps++;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " bps";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kbps";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mbps";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gbps";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tbps";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pbps";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Ebps";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zbps";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Ybps";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.shortFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1000) {
|
||||
steps++;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = "";
|
||||
break;
|
||||
case 1:
|
||||
ext = " K";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mil";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Bil";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tri";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Quadr";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Quint";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Sext";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Sept";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.getFormatFunction = function(formatName, decimals) {
|
||||
switch(formatName) {
|
||||
case 'short':
|
||||
return function(val) {
|
||||
return kbn.shortFormat(val, decimals);
|
||||
};
|
||||
case 'bytes':
|
||||
return function(val) {
|
||||
return kbn.byteFormat(val, decimals);
|
||||
};
|
||||
case 'bits':
|
||||
return function(val) {
|
||||
return kbn.bitFormat(val, decimals);
|
||||
};
|
||||
case 'bps':
|
||||
return function(val) {
|
||||
return kbn.bpsFormat(val, decimals);
|
||||
};
|
||||
case 's':
|
||||
return function(val) {
|
||||
return kbn.sFormat(val, decimals);
|
||||
};
|
||||
case 'ms':
|
||||
return function(val) {
|
||||
return kbn.msFormat(val, decimals);
|
||||
};
|
||||
case 'µs':
|
||||
return function(val) {
|
||||
return kbn.microsFormat(val, decimals);
|
||||
};
|
||||
case 'ns':
|
||||
return function(val) {
|
||||
return kbn.nanosFormat(val, decimals);
|
||||
};
|
||||
case 'percent':
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
|
||||
};
|
||||
default:
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kbn.noneFormat = function(value, decimals) {
|
||||
kbn.toFixed = function(value, decimals) {
|
||||
var factor = decimals ? Math.pow(10, decimals) : 1;
|
||||
var formatted = String(Math.round(value * factor) / factor);
|
||||
|
||||
@@ -553,7 +341,6 @@ function($, _, moment) {
|
||||
|
||||
// If tickDecimals was specified, ensure that we have exactly that
|
||||
// much precision; otherwise default to the value's own precision.
|
||||
|
||||
if (decimals != null) {
|
||||
var decimalPos = formatted.indexOf(".");
|
||||
var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
|
||||
@@ -565,97 +352,87 @@ function($, _, moment) {
|
||||
return formatted;
|
||||
};
|
||||
|
||||
kbn.msFormat = function(size, decimals) {
|
||||
// Less than 1 milli, downscale to micro
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.microsFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(decimals) + " ms";
|
||||
kbn.valueFormats.bits = kbn.formatFuncCreator(1024, [' b', ' Kib', ' Mib', ' Gib', ' Tib', ' Pib', ' Eib', ' Zib', ' Yib']);
|
||||
kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
|
||||
kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
|
||||
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
|
||||
kbn.valueFormats.none = kbn.toFixed;
|
||||
|
||||
kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " ms";
|
||||
}
|
||||
// Less than 1 min
|
||||
else if (Math.abs(size) < 60000) {
|
||||
return (size / 1000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (Math.abs(size) < 3600000) {
|
||||
return (size / 60000).toFixed(decimals) + " min";
|
||||
return kbn.toFixed(size / 60000, scaledDecimals + 5) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (Math.abs(size) < 86400000) {
|
||||
return (size / 3600000).toFixed(decimals) + " hour";
|
||||
return kbn.toFixed(size / 3600000, scaledDecimals + 7) + " hour";
|
||||
}
|
||||
// Less than one year, devide in days
|
||||
else if (Math.abs(size) < 31536000000) {
|
||||
return (size / 86400000).toFixed(decimals) + " day";
|
||||
return kbn.toFixed(size / 86400000, scaledDecimals + 8) + " day";
|
||||
}
|
||||
|
||||
return (size / 31536000000).toFixed(decimals) + " year";
|
||||
return kbn.toFixed(size / 31536000000, scaledDecimals + 10) + " year";
|
||||
};
|
||||
|
||||
kbn.sFormat = function(size, decimals) {
|
||||
// Less than 1 sec, downscale to milli
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.msFormat(size * 1000, decimals);
|
||||
}
|
||||
// Less than 10 min, use seconds
|
||||
else if (Math.abs(size) < 600) {
|
||||
return size.toFixed(decimals) + " s";
|
||||
kbn.valueFormats.s = function(size, decimals, scaledDecimals) {
|
||||
if (Math.abs(size) < 600) {
|
||||
return kbn.toFixed(size, decimals) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (Math.abs(size) < 3600) {
|
||||
return (size / 60).toFixed(decimals) + " min";
|
||||
return kbn.toFixed(size / 60, scaledDecimals + 1) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (Math.abs(size) < 86400) {
|
||||
return (size / 3600).toFixed(decimals) + " hour";
|
||||
return kbn.toFixed(size / 3600, scaledDecimals + 4) + " hour";
|
||||
}
|
||||
// Less than one week, devide in days
|
||||
else if (Math.abs(size) < 604800) {
|
||||
return (size / 86400).toFixed(decimals) + " day";
|
||||
return kbn.toFixed(size / 86400, scaledDecimals + 5) + " day";
|
||||
}
|
||||
// Less than one year, devide in week
|
||||
else if (Math.abs(size) < 31536000) {
|
||||
return (size / 604800).toFixed(decimals) + " week";
|
||||
return kbn.toFixed(size / 604800, scaledDecimals + 6) + " week";
|
||||
}
|
||||
|
||||
return (size / 3.15569e7).toFixed(decimals) + " year";
|
||||
return kbn.toFixed(size / 3.15569e7, scaledDecimals + 7) + " year";
|
||||
};
|
||||
|
||||
kbn.microsFormat = function(size, decimals) {
|
||||
// Less than 1 micro, downscale to nano
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.nanosFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(decimals) + " µs";
|
||||
kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) {
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " µs";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " ms";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " ms";
|
||||
}
|
||||
else {
|
||||
return (size / 1000000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " s";
|
||||
}
|
||||
};
|
||||
|
||||
kbn.nanosFormat = function(size, decimals) {
|
||||
if (Math.abs(size) < 1) {
|
||||
return size.toFixed(decimals) + " ns";
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(0) + " ns";
|
||||
kbn.valueFormats.ns = function(size, decimals, scaledDecimals) {
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " ns";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " µs";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " µs";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000000) {
|
||||
return (size / 1000000).toFixed(decimals) + " ms";
|
||||
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " ms";
|
||||
}
|
||||
else if (Math.abs(size) < 60000000000){
|
||||
return (size / 1000000000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000000000, scaledDecimals + 9) + " s";
|
||||
}
|
||||
else {
|
||||
return (size / 60000000000).toFixed(decimals) + " m";
|
||||
return kbn.toFixed(size / 60000000000, scaledDecimals + 12) + " m";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ require.config({
|
||||
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
|
||||
@@ -84,6 +85,7 @@ require.config({
|
||||
'jquery.flot.stack': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
|
||||
'angular-cookies': ['angular'],
|
||||
'angular-dragdrop': ['jquery','jquery-ui','angular'],
|
||||
'angular-loader': ['angular'],
|
||||
|
||||
@@ -54,7 +54,7 @@ function (_, kbn) {
|
||||
}
|
||||
};
|
||||
|
||||
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
|
||||
TimeSeries.prototype.getFlotPairs = function (fillStyle) {
|
||||
var result = [];
|
||||
|
||||
this.color = this.info.color;
|
||||
@@ -100,21 +100,21 @@ function (_, kbn) {
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
|
||||
this.info.avg = (this.info.total / result.length);
|
||||
this.info.current = result[result.length-1][1];
|
||||
|
||||
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
|
||||
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
|
||||
this.info.current = this.info.current != null ? formater(this.info.current) : null;
|
||||
this.info.min = this.info.min != null ? formater(this.info.min) : null;
|
||||
this.info.max = this.info.max != null ? formater(this.info.max) : null;
|
||||
this.info.total = this.info.total != null ? formater(this.info.total) : null;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) {
|
||||
this.info.avg = this.info.avg != null ? formater(this.info.avg, decimals, scaledDecimals) : null;
|
||||
this.info.current = this.info.current != null ? formater(this.info.current, decimals, scaledDecimals) : null;
|
||||
this.info.min = this.info.min != null ? formater(this.info.min, decimals, scaledDecimals) : null;
|
||||
this.info.max = this.info.max != null ? formater(this.info.max, decimals, scaledDecimals) : null;
|
||||
this.info.total = this.info.total != null ? formater(this.info.total, decimals, scaledDecimals) : null;
|
||||
};
|
||||
|
||||
return TimeSeries;
|
||||
|
||||
});
|
||||
|
||||
@@ -15,5 +15,6 @@ define([
|
||||
'./opentsdbTargetCtrl',
|
||||
'./annotationsEditorCtrl',
|
||||
'./templateEditorCtrl',
|
||||
'./sharePanelCtrl',
|
||||
'./jsonEditorCtrl',
|
||||
], function () {});
|
||||
|
||||
@@ -19,19 +19,18 @@ function (angular, $, config, _) {
|
||||
dashboardSrv,
|
||||
dashboardViewStateSrv,
|
||||
panelMoveSrv,
|
||||
timer,
|
||||
$timeout) {
|
||||
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.panelNames = config.panels;
|
||||
var resizeEventTimeout;
|
||||
|
||||
$scope.init = function() {
|
||||
this.init = function(dashboardData) {
|
||||
$scope.availablePanels = config.panels;
|
||||
$scope.onAppEvent('setup-dashboard', $scope.setupDashboard);
|
||||
$scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
|
||||
$scope.reset_row();
|
||||
$scope.registerWindowResizeEvent();
|
||||
$scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
|
||||
$scope.setupDashboard(dashboardData);
|
||||
};
|
||||
|
||||
$scope.registerWindowResizeEvent = function() {
|
||||
@@ -41,7 +40,7 @@ function (angular, $, config, _) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setupDashboard = function(event, dashboardData) {
|
||||
$scope.setupDashboard = function(dashboardData) {
|
||||
$rootScope.performance.dashboardLoadStart = new Date().getTime();
|
||||
$rootScope.performance.panelsInitialized = 0;
|
||||
$rootScope.performance.panelsRendered = 0;
|
||||
@@ -59,7 +58,7 @@ function (angular, $, config, _) {
|
||||
|
||||
$scope.setWindowTitleAndTheme();
|
||||
|
||||
$scope.emitAppEvent("dashboard-loaded", $scope.dashboard);
|
||||
$scope.appEvent("dashboard-loaded", $scope.dashboard);
|
||||
};
|
||||
|
||||
$scope.setWindowTitleAndTheme = function() {
|
||||
@@ -114,7 +113,7 @@ function (angular, $, config, _) {
|
||||
var editScope = $rootScope.$new();
|
||||
editScope.object = options.object;
|
||||
editScope.updateHandler = options.updateHandler;
|
||||
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
|
||||
};
|
||||
|
||||
$scope.checkFeatureToggles = function() {
|
||||
@@ -129,6 +128,5 @@ function (angular, $, config, _) {
|
||||
return $scope.editorTabs;
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ function (angular, _, moment, config, store) {
|
||||
};
|
||||
|
||||
$scope.openSearch = function() {
|
||||
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
};
|
||||
|
||||
$scope.saveDashboard = function() {
|
||||
@@ -78,7 +78,7 @@ function (angular, _, moment, config, store) {
|
||||
var clone = angular.copy($scope.dashboard);
|
||||
$scope.db.saveDashboard(clone)
|
||||
.then(function(result) {
|
||||
alertSrv.set('Dashboard Saved', 'Saved as "' + result.title + '"','success', 3000);
|
||||
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + result.title]);
|
||||
|
||||
if (result.url !== $location.path()) {
|
||||
$location.search({});
|
||||
@@ -88,12 +88,12 @@ function (angular, _, moment, config, store) {
|
||||
$rootScope.$emit('dashboard-saved', $scope.dashboard);
|
||||
|
||||
}, function(err) {
|
||||
alertSrv.set('Save failed', err, 'error', 5000);
|
||||
$scope.appEvent('alert-error', ['Save failed', err]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteDashboard = function(evt, options) {
|
||||
if (!confirm('Are you sure you want to delete dashboard?')) {
|
||||
if (!confirm('Do you want to delete dashboard ' + options.title + ' ?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ function (angular, _, moment, config, store) {
|
||||
|
||||
var id = options.id;
|
||||
$scope.db.deleteDashboard(id).then(function(id) {
|
||||
alertSrv.set('Dashboard Deleted', id + ' has been deleted', 'success', 5000);
|
||||
}, function() {
|
||||
alertSrv.set('Dashboard Not Deleted', 'An error occurred deleting the dashboard', 'error', 5000);
|
||||
$scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
|
||||
}, function(err) {
|
||||
$scope.appEvent('alert-error', ['Deleted failed', err]);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,7 +138,7 @@ function (angular, _, moment, config, store) {
|
||||
};
|
||||
|
||||
$scope.editJson = function() {
|
||||
$scope.emitAppEvent('show-json-editor', { object: $scope.dashboard });
|
||||
$scope.appEvent('show-json-editor', { object: $scope.dashboard });
|
||||
};
|
||||
|
||||
$scope.openSaveDropdown = function() {
|
||||
|
||||
@@ -10,19 +10,19 @@ function (angular, config, _, $, store) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) {
|
||||
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
|
||||
|
||||
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
|
||||
$scope.consoleEnabled = store.getBool('grafanaConsole');
|
||||
|
||||
$scope._ = _;
|
||||
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
|
||||
$rootScope.performance = { loadStart: new Date().getTime() };
|
||||
|
||||
$scope.init = function() {
|
||||
$scope._ = _;
|
||||
|
||||
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
|
||||
|
||||
alertSrv.init();
|
||||
utilSrv.init();
|
||||
|
||||
$scope.dashAlerts = alertSrv;
|
||||
$scope.grafana = { style: 'dark' };
|
||||
};
|
||||
@@ -32,12 +32,16 @@ function (angular, config, _, $, store) {
|
||||
store.set('grafanaConsole', $scope.consoleEnabled);
|
||||
};
|
||||
|
||||
$scope.initDashboard = function(dashboardData, viewScope) {
|
||||
$controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
|
||||
};
|
||||
|
||||
$rootScope.onAppEvent = function(name, callback) {
|
||||
var unbind = $rootScope.$on(name, callback);
|
||||
this.$on('$destroy', unbind);
|
||||
};
|
||||
|
||||
$rootScope.emitAppEvent = function(name, payload) {
|
||||
$rootScope.appEvent = function(name, payload) {
|
||||
$rootScope.$emit(name, payload);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash'
|
||||
'lodash',
|
||||
'kbn'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
function (angular, app, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv) {
|
||||
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, $location) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.datasources = datasourceSrv.getMetricSources();
|
||||
@@ -72,18 +73,19 @@ function (angular, app, _) {
|
||||
newDashboard.title = state.name;
|
||||
newDashboard.rows.push(currentRow);
|
||||
|
||||
_.each(state.graphs, function(graph) {
|
||||
_.each(state.graphs, function(graph, index) {
|
||||
if (currentRow.panels.length === graphsPerRow) {
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
newDashboard.rows.push(currentRow);
|
||||
}
|
||||
|
||||
panel = {
|
||||
type: 'graphite',
|
||||
type: 'graph',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: [],
|
||||
datasource: datasource
|
||||
datasource: datasource,
|
||||
id: index + 1
|
||||
};
|
||||
|
||||
_.each(graph[1].target, function(target) {
|
||||
@@ -95,7 +97,9 @@ function (angular, app, _) {
|
||||
currentRow.panels.push(panel);
|
||||
});
|
||||
|
||||
$scope.emitAppEvent('setup-dashboard', newDashboard);
|
||||
window.grafanaImportDashboard = newDashboard;
|
||||
$location.path('/dashboard/import/' + kbn.slugifyForUrl(newDashboard.title));
|
||||
|
||||
$scope.dismiss();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ function (angular, app, _) {
|
||||
title: "Row",
|
||||
height: "150px",
|
||||
collapse: false,
|
||||
editable: true,
|
||||
panels: [],
|
||||
};
|
||||
|
||||
@@ -22,6 +23,11 @@ function (angular, app, _) {
|
||||
$scope.reset_panel();
|
||||
};
|
||||
|
||||
$scope.togglePanelMenu = function(posX) {
|
||||
$scope.showPanelMenu = !$scope.showPanelMenu;
|
||||
$scope.panelMenuPos = posX;
|
||||
};
|
||||
|
||||
$scope.toggle_row = function(row) {
|
||||
row.collapse = row.collapse ? false : true;
|
||||
if (!row.collapse) {
|
||||
|
||||
@@ -29,7 +29,7 @@ function (angular, _, config, $) {
|
||||
|
||||
$scope.keyDown = function (evt) {
|
||||
if (evt.keyCode === 27) {
|
||||
$scope.emitAppEvent('hide-dash-editor');
|
||||
$scope.appEvent('hide-dash-editor');
|
||||
}
|
||||
if (evt.keyCode === 40) {
|
||||
$scope.moveSelection(1);
|
||||
@@ -62,6 +62,7 @@ function (angular, _, config, $) {
|
||||
};
|
||||
|
||||
$scope.goToDashboard = function(id) {
|
||||
$location.search({});
|
||||
$location.path("/dashboard/db/" + id);
|
||||
};
|
||||
|
||||
@@ -121,7 +122,7 @@ function (angular, _, config, $) {
|
||||
|
||||
$scope.deleteDashboard = function(dash, evt) {
|
||||
evt.stopPropagation();
|
||||
$scope.emitAppEvent('delete-dashboard', { id: dash.id });
|
||||
$scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
|
||||
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('SharePanelCtrl', function($scope, $location, $timeout, timeSrv, $element, templateSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.forCurrent = true;
|
||||
$scope.toPanel = true;
|
||||
$scope.includeTemplateVars = true;
|
||||
|
||||
$scope.buildUrl();
|
||||
};
|
||||
|
||||
$scope.buildUrl = function() {
|
||||
var baseUrl = $location.absUrl();
|
||||
var queryStart = baseUrl.indexOf('?');
|
||||
|
||||
if (queryStart !== -1) {
|
||||
baseUrl = baseUrl.substring(0, queryStart);
|
||||
}
|
||||
|
||||
var panelId = $scope.panel.id;
|
||||
var range = timeSrv.timeRange(false);
|
||||
var params = angular.copy($location.search());
|
||||
|
||||
if (_.isString(range.to) && range.to.indexOf('now')) {
|
||||
range = timeSrv.timeRange();
|
||||
}
|
||||
|
||||
params.from = range.from;
|
||||
params.to = range.to;
|
||||
|
||||
if (_.isDate(params.from)) { params.from = params.from.getTime(); }
|
||||
if (_.isDate(params.to)) { params.to = params.to.getTime(); }
|
||||
|
||||
if ($scope.includeTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
params['var-' + variable.name] = variable.current.text;
|
||||
});
|
||||
}
|
||||
else {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
delete params['var-' + variable.name];
|
||||
});
|
||||
}
|
||||
|
||||
if (!$scope.forCurrent) {
|
||||
delete params.from;
|
||||
delete params.to;
|
||||
}
|
||||
|
||||
if ($scope.toPanel) {
|
||||
params.panelId = panelId;
|
||||
params.fullscreen = true;
|
||||
} else {
|
||||
delete params.panelId;
|
||||
delete params.fullscreen;
|
||||
}
|
||||
|
||||
var paramsArray = [];
|
||||
_.each(params, function(value, key) {
|
||||
var str = key;
|
||||
if (value !== true) {
|
||||
str += '=' + encodeURIComponent(value);
|
||||
}
|
||||
paramsArray.push(str);
|
||||
});
|
||||
|
||||
$scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ;
|
||||
|
||||
$timeout(function() {
|
||||
var input = $element.find('[data-share-panel-url]');
|
||||
input.focus();
|
||||
input.select();
|
||||
}, 10);
|
||||
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
@@ -72,6 +72,9 @@ function (angular, _) {
|
||||
if ($scope.current.type === 'interval') {
|
||||
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
|
||||
}
|
||||
if ($scope.current.type === 'query') {
|
||||
$scope.current.query = '';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeVariable = function(variable) {
|
||||
|
||||
@@ -16,7 +16,7 @@ function (angular, $) {
|
||||
elem.bind('click',function() {
|
||||
$timeout(function() {
|
||||
var editorScope = attrs.editorScope === 'isolated' ? null : scope;
|
||||
scope.emitAppEvent('show-dash-editor', { src: partial, scope: editorScope });
|
||||
scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,7 @@ function (angular, $) {
|
||||
|
||||
function hideScrollbars(value) {
|
||||
if (value) {
|
||||
window.scrollTo(0,0);
|
||||
document.documentElement.style.overflow = 'hidden'; // firefox, chrome
|
||||
document.body.scroll = "no"; // ie only
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
define([
|
||||
'angular'
|
||||
'angular',
|
||||
'kbn'
|
||||
],
|
||||
function (angular) {
|
||||
function (angular, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('dashUpload', function(timer, alertSrv) {
|
||||
module.directive('dashUpload', function(timer, alertSrv, $location) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope) {
|
||||
@@ -14,9 +15,10 @@ function (angular) {
|
||||
var files = evt.target.files; // FileList object
|
||||
var readerOnload = function() {
|
||||
return function(e) {
|
||||
var dashboard = JSON.parse(e.target.result);
|
||||
scope.$apply(function() {
|
||||
scope.emitAppEvent('setup-dashboard', dashboard);
|
||||
window.grafanaImportDashboard = JSON.parse(e.target.result);
|
||||
var title = kbn.slugifyForUrl(window.grafanaImportDashboard.title);
|
||||
$location.path('/dashboard/import/' + title);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,9 +3,10 @@ define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'lodash'
|
||||
'lodash',
|
||||
'./grafanaGraph.tooltip'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
function (angular, $, kbn, moment, _, graphTooltip) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
@@ -15,23 +16,15 @@ function (angular, $, kbn, moment, _) {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, annotations;
|
||||
var hiddenData = {};
|
||||
var dashboard = scope.dashboard;
|
||||
var data, annotations;
|
||||
var legendSideLastValue = null;
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, series) {
|
||||
_.each(series, function(serie) {
|
||||
if (hiddenData[serie.alias]) {
|
||||
data.push(hiddenData[serie.alias]);
|
||||
delete hiddenData[serie.alias];
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
@@ -88,6 +81,18 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLegendValues(plot) {
|
||||
var yaxis = plot.getYAxes();
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
var axis = yaxis[series.yaxis - 1];
|
||||
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
|
||||
series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (shouldAbortRender()) {
|
||||
@@ -95,21 +100,11 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
var panel = scope.panel;
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
hooks: { draw: [updateLegendValues] },
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
@@ -132,7 +127,8 @@ function (angular, $, kbn, moment, _) {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.pointradius
|
||||
radius: panel.points ? panel.pointradius : 2
|
||||
// little points when highlight points
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
@@ -149,6 +145,9 @@ function (angular, $, kbn, moment, _) {
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
},
|
||||
crosshair: {
|
||||
mode: panel.tooltip.shared ? "x" : null
|
||||
}
|
||||
};
|
||||
|
||||
@@ -156,6 +155,11 @@ function (angular, $, kbn, moment, _) {
|
||||
var series = data[i];
|
||||
series.applySeriesOverrides(panel.seriesOverrides);
|
||||
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
// if hidden remove points and disable stack
|
||||
if (scope.hiddenSeries[series.info.alias]) {
|
||||
series.data = [];
|
||||
series.stack = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length && data[0].info.timeStep) {
|
||||
@@ -313,7 +317,9 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
axis.tickFormatter = function(val, axis) {
|
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
|
||||
};
|
||||
}
|
||||
|
||||
function time_format(interval, ticks, min, max) {
|
||||
@@ -338,40 +344,6 @@ function (angular, $, kbn, moment, _) {
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp, seriesInfo, format;
|
||||
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
seriesInfo.alias +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.getFormatFunction(format, 2)(value, item.series.yaxis);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
@@ -422,6 +394,8 @@ function (angular, $, kbn, moment, _) {
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
graphTooltip.register(elem, dashboard, scope);
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
],
|
||||
function ($, kbn) {
|
||||
'use strict';
|
||||
|
||||
function registerTooltipFeatures(elem, dashboard, scope) {
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
elem.mouseleave(function () {
|
||||
if(scope.panel.tooltip.shared) {
|
||||
var plot = elem.data().plot;
|
||||
$tooltip.detach();
|
||||
plot.clearCrosshair();
|
||||
plot.unhighlight();
|
||||
}
|
||||
});
|
||||
|
||||
function findHoverIndex(posX, series) {
|
||||
for (var j = 0; j < series.data.length; j++) {
|
||||
if (series.data[j][0] > posX) {
|
||||
return Math.max(j - 1, 0);
|
||||
}
|
||||
}
|
||||
return j - 1;
|
||||
}
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var plot = elem.data().plot;
|
||||
var data = plot.getData();
|
||||
var group, value, timestamp, seriesInfo, format, i, series, hoverIndex, seriesHtml;
|
||||
|
||||
if (scope.panel.tooltip.shared) {
|
||||
plot.unhighlight();
|
||||
|
||||
//check if all series has same length if so, only one x index will
|
||||
//be checked and only for exact timestamp values
|
||||
var pointCount = data[0].data.length;
|
||||
for (i = 1; i < data.length; i++) {
|
||||
if (data[i].data.length !== pointCount) {
|
||||
console.log('WARNING: tootltip shared can not be shown becouse of series points do not align, different point counts');
|
||||
$tooltip.detach();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
seriesHtml = '';
|
||||
series = data[0];
|
||||
hoverIndex = findHoverIndex(pos.x, series);
|
||||
|
||||
//now we know the current X (j) position for X and Y values
|
||||
timestamp = dashboard.formatDate(series.data[hoverIndex][0]);
|
||||
var last_value = 0; //needed for stacked values
|
||||
|
||||
for (i = data.length-1; i >= 0; --i) {
|
||||
//stacked values should be added in reverse order
|
||||
series = data[i];
|
||||
seriesInfo = series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = series.data[hoverIndex][1];
|
||||
} else {
|
||||
last_value += series.data[hoverIndex][1];
|
||||
value = last_value;
|
||||
}
|
||||
|
||||
value = kbn.valueFormats[format](value, series.yaxis.tickDecimals);
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<i class="icon-minus" style="color:' + series.color +';"></i> ' + seriesInfo.alias;
|
||||
} else {
|
||||
group = kbn.query_color_dot(series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
//pre-pending new values
|
||||
seriesHtml = group + ': <span class="graph-tooltip-value">' + value + '</span><br>' + seriesHtml;
|
||||
|
||||
plot.highlight(i, hoverIndex);
|
||||
}
|
||||
|
||||
$tooltip.html('<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ timestamp + '</div> ' + seriesHtml + '</div>')
|
||||
.place_tt(pos.pageX + 20, pos.pageY);
|
||||
return;
|
||||
}
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
seriesInfo.alias +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
register: registerTooltipFeatures
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'lodash',
|
||||
'./panelMenu',
|
||||
],
|
||||
function (angular, $) {
|
||||
'use strict';
|
||||
@@ -15,37 +15,19 @@ function (angular, $) {
|
||||
|
||||
var panelHeader =
|
||||
'<div class="panel-header">'+
|
||||
'<div class="row-fluid panel-extra">' +
|
||||
'<div class="panel-extra-container">' +
|
||||
'<span class="alert-error panel-error small pointer"' +
|
||||
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
|
||||
'<span data-placement="right" bs-tooltip="panelMeta.error">' +
|
||||
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
|
||||
'</span>' +
|
||||
'<span class="alert-error panel-error small pointer"' +
|
||||
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
|
||||
'<span data-placement="right" bs-tooltip="panelMeta.error">' +
|
||||
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
|
||||
'</span>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="dropdown">' +
|
||||
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
|
||||
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
|
||||
' jqyoui-draggable="'+
|
||||
'{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="panel" ' +
|
||||
'>' +
|
||||
'{{panel.title | interpolateTemplateVars}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
'</div>'+
|
||||
'</div>\n'+
|
||||
'<div class="panel-title-container" panel-menu></div>' +
|
||||
'</div>'+
|
||||
'</div>';
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, $, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('panelMenu', function($compile) {
|
||||
var linkTemplate = '<a class="panel-title">{{panel.title | interpolateTemplateVars}}</a>';
|
||||
var moveAttributes = ' data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
|
||||
' jqyoui-draggable="{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="panel" ';
|
||||
|
||||
function createMenuTemplate($scope) {
|
||||
var template = '<div class="panel-menu small">';
|
||||
template += '<div class="panel-menu-inner">';
|
||||
template += '<div class="panel-menu-row">';
|
||||
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(-1)"><i class="icon-minus"></i></a>';
|
||||
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(1)"><i class="icon-plus"></i></a>';
|
||||
template += '<a class="panel-menu-icon pull-right" ng-click="remove_panel_from_row(row, panel)"><i class="icon-remove"></i></a>';
|
||||
template += '<a class="panel-menu-icon pull-right" ' + moveAttributes + '><i class="icon-move"></i></a>';
|
||||
template += '<div class="clearfix"></div>';
|
||||
template += '</div>';
|
||||
|
||||
template += '<div class="panel-menu-row">';
|
||||
|
||||
_.each($scope.panelMeta.menu, function(item) {
|
||||
template += '<a class="panel-menu-link" ';
|
||||
if (item.click) { template += ' ng-click="' + item.click + '"'; }
|
||||
if (item.editorLink) { template += ' dash-editor-link="' + item.editorLink + '"'; }
|
||||
template += '>';
|
||||
template += item.text + '</a>';
|
||||
});
|
||||
|
||||
template += '</div>';
|
||||
template += '</div>';
|
||||
template += '</div>';
|
||||
return template;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem) {
|
||||
var $link = $(linkTemplate);
|
||||
var $panelContainer = elem.parents(".panel-container");
|
||||
var menuWidth = $scope.panelMeta.menu.length === 5 ? 246 : 201;
|
||||
var menuScope = null;
|
||||
var timeout = null;
|
||||
var $menu = null;
|
||||
|
||||
elem.append($link);
|
||||
|
||||
function dismiss(time) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
|
||||
if (time) {
|
||||
timeout = setTimeout(dismiss, time);
|
||||
return;
|
||||
}
|
||||
|
||||
// if hovering or draging pospone close
|
||||
if ($menu.is(':hover') || $scope.dashboard.$$panelDragging) {
|
||||
dismiss(2500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (menuScope) {
|
||||
$menu.unbind();
|
||||
$menu.remove();
|
||||
menuScope.$destroy();
|
||||
menuScope = null;
|
||||
$menu = null;
|
||||
$panelContainer.removeClass('panel-highlight');
|
||||
}
|
||||
}
|
||||
|
||||
var showMenu = function() {
|
||||
if ($menu) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
var windowWidth = $(window).width();
|
||||
var panelLeftPos = $(elem).offset().left;
|
||||
var panelWidth = $(elem).width();
|
||||
var menuLeftPos = (panelWidth / 2) - (menuWidth/2);
|
||||
var stickingOut = panelLeftPos + menuLeftPos + menuWidth - windowWidth;
|
||||
if (stickingOut > 0) {
|
||||
menuLeftPos -= stickingOut + 10;
|
||||
}
|
||||
if (panelLeftPos + menuLeftPos < 0) {
|
||||
menuLeftPos = 0;
|
||||
}
|
||||
|
||||
var menuTemplate = createMenuTemplate($scope);
|
||||
$menu = $(menuTemplate);
|
||||
$menu.css('left', menuLeftPos);
|
||||
$menu.mouseleave(function() {
|
||||
dismiss(1000);
|
||||
});
|
||||
|
||||
menuScope = $scope.$new();
|
||||
|
||||
$('.panel-menu').remove();
|
||||
elem.append($menu);
|
||||
$scope.$apply(function() {
|
||||
$compile($menu.contents())(menuScope);
|
||||
});
|
||||
|
||||
$(".panel-container").removeClass('panel-highlight');
|
||||
$panelContainer.toggleClass('panel-highlight');
|
||||
|
||||
dismiss(2500);
|
||||
};
|
||||
|
||||
elem.click(showMenu);
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -17,4 +17,27 @@ function (angular, kbn) {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('editorOptBool', function($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
|
||||
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
|
||||
var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
|
||||
|
||||
var template = '<div class="editor-option text-center"' + showIf + '>' +
|
||||
' <label for="' + attrs.model + '" class="small">' +
|
||||
attrs.text + tip + '</label>' +
|
||||
'<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +
|
||||
' ng-model="' + attrs.model + '"' + ngchange +
|
||||
' ng-checked="' + attrs.model + '"></input>' +
|
||||
' <label for="' + attrs.model + '" class="cr1"></label>';
|
||||
elem.replaceWith($compile(angular.element(template))(scope));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -40,42 +40,19 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Legend styles</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Align as table</label><input type="checkbox" ng-model="panel.legend.alignAsTable" ng-checked="panel.legend.alignAsTable">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right side</label><input type="checkbox" ng-model="panel.legend.rightSide" ng-change="render();" ng-checked="panel.legend.rightSide">
|
||||
</div>
|
||||
<editor-opt-bool text="Show legend" model="panel.legend.show" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Include values" model="panel.legend.values" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Align as table" model="panel.legend.alignAsTable" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Right side" model="panel.legend.rightSide" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-if="panel.legend.values">
|
||||
<h5>Legend values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Min" model="panel.legend.min" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Max" model="panel.legend.max" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Current" model="panel.legend.current" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Total" model="panel.legend.total" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Avg" model="panel.legend.avg" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -96,19 +73,13 @@
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
|
||||
</div>
|
||||
<editor-opt-bool text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Show Axes</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
<editor-opt-bool text="X-Axis" model="panel['x-axis']" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Y-axis" model="panel['y-axis']" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
</div>
|
||||
|
||||
<div grafana-graph class="pointer histogram-chart">
|
||||
<div grafana-graph class="histogram-chart">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,8 @@ define([
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent'
|
||||
'jquery.flot.stackpercent',
|
||||
'jquery.flot.crosshair'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
'use strict';
|
||||
@@ -160,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
query_as_alias: true
|
||||
shared: false,
|
||||
},
|
||||
|
||||
targets: [{}],
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
|
||||
</div>
|
||||
<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -30,19 +24,15 @@
|
||||
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
|
||||
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Multiple Series</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
|
||||
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
|
||||
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
@@ -61,8 +51,16 @@
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Tooltip</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">shared <tip> Show all series values on the same time in the same tooltip and a x croshair to help follow all series</tip> </label><input type="checkbox" ng-model="panel.tooltip.shared" ng-checked="panel.tooltip.shared" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
|
||||
|
||||
@@ -20,6 +20,7 @@ function (angular, app, _, require) {
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
title: 'default title',
|
||||
mode : "markdown", // 'html', 'markdown', 'text'
|
||||
content : "",
|
||||
style: {},
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<!-- This is a complete hack. The form actually exists in the modal, but due to transclusion
|
||||
$scope.input isn't available on the controller unless the form element is in this file -->
|
||||
<form name="input" style="margin:3px 0 0 0">
|
||||
<form name="input" style="margin:0">
|
||||
<ul class="nav nav-pills timepicker-dropdown">
|
||||
<li class="dropdown">
|
||||
|
||||
@@ -47,6 +45,5 @@
|
||||
<a ng-click="timeSrv.refreshDashboard()"><i class="icon-refresh"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,7 @@ function (angular, app, _, moment, kbn) {
|
||||
$scope.temptime.to.date = moment($scope.temptime.to.date).add('days',1).toDate();
|
||||
}
|
||||
|
||||
$scope.emitAppEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
|
||||
$scope.appEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
|
||||
};
|
||||
|
||||
// Constantly validate the input of the fields. This function does not change any date variables
|
||||
|
||||
@@ -61,10 +61,7 @@
|
||||
<label class="small">Icon size</label>
|
||||
<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Grid line</label>
|
||||
<input type="checkbox" ng-model="currentAnnotation.showLine" ng-checked="currentAnnotation.showLine">
|
||||
</div>
|
||||
<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line color</label>
|
||||
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div ng-controller="DashboardCtrl" body-class class="dashboard" ng-class="{'dashboard-fullscreen': dashboardViewState.fullscreen}">
|
||||
<div body-class class="dashboard" ng-class="{'dashboard-fullscreen': dashboardViewState.fullscreen}">
|
||||
|
||||
<div ng-include="'app/partials/pro/dashboard_topnav.html'">
|
||||
</div>
|
||||
@@ -74,9 +74,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding-top:0px" ng-if="!row.collapse">
|
||||
<div class="panels-wrapper" ng-if="!row.collapse">
|
||||
<div class="row-text pointer" ng-click="toggle_row(row)" ng-if="row.showTitle" ng-bind="row.title">
|
||||
</div>
|
||||
<div class="panel-menu-container" data-menu-container>
|
||||
<!-- <a class="pointer"><i class="icon-eye-open"></i> <span>view</span></a> -->
|
||||
<!-- <a class="pointer"><i class="icon-cog"></i> <span>edit</span></a> -->
|
||||
<!-- <a class="pointer"><i class="icon-resize-horizontal"></i> <span>span</span></a> -->
|
||||
<!-- <a class="pointer"><i class="icon-copy"></i> <span>duplicate</span></a> -->
|
||||
<!-- <a class="pointer"><i class="icon-share"></i> <span>share</span></a> -->
|
||||
<!-- <a class="pointer"><i class="icon-remove"></i> <span>remove</span></a> -->
|
||||
</div>
|
||||
|
||||
<!-- Panels -->
|
||||
<div ng-repeat="(name, panel) in row.panels"
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="dashboard.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Hide controls (CTRL+H)</label>
|
||||
<input type="checkbox" ng-model="dashboard.hideControls" ng-checked="dashboard.hideControls">
|
||||
</div>
|
||||
<editor-opt-bool text="Hide controls (CTRL+H)" model="dashboard.hideControls"></editor-opt-bool>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
@@ -42,7 +39,6 @@
|
||||
</bootstrap-tagsinput>
|
||||
<tip>Press enter to a add tag</tip>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,16 +67,12 @@
|
||||
<div ng-if="editor.index == 2">
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Templating</label>
|
||||
<input type="checkbox" ng-model="dashboard.templating.enable" ng-checked="dashboard.templating.enable" ng-change="checkFeatureToggles()"x >
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Annotations</label>
|
||||
<input type="checkbox" ng-model="dashboard.annotations.enable" ng-checked="dashboard.annotations.enable" ng-change="checkFeatureToggles()">
|
||||
</div>
|
||||
<div class="editor-option" ng-repeat="pulldown in dashboard.nav">
|
||||
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
|
||||
<editor-opt-bool text="Templating" model="dashboard.templating.enable"></editor-opt-bool>
|
||||
<editor-opt-bool text="Annotations" model="dashboard.annotations.enable"></editor-opt-bool>
|
||||
<div class="editor-option text-center" ng-repeat="pulldown in dashboard.nav">
|
||||
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label>
|
||||
<input class="cr1" id="pulldown{{pulldown.type}}" type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
|
||||
<label for="pulldown{{pulldown.type}}" class="cr1"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
</div>
|
||||
|
||||
<section class="grafana-metric-options">
|
||||
<div class="grafana-target">
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment grafana-target-segment-icon">
|
||||
@@ -125,6 +126,7 @@
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="editor-row">
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin-top: 10px;">
|
||||
<table class="table table-condensed table-striped">
|
||||
<div class="editor-row" style="margin-top: 10px;max-height: 400px; overflow-y: scroll;max-width: 500px;">
|
||||
<table class="grafana-options-table">
|
||||
<tr ng-repeat="dash in dashboards">
|
||||
<td style="padding-right: 20px;"><button class="btn btn-success" ng-click="import(dash.name)">Import</button>
|
||||
<td style="width: 100%; vertical-align: middle;">{{dash.name}}</td>
|
||||
<td style="">{{dash.name}}</td>
|
||||
<td style="padding-left: 20px;">
|
||||
<a class="pointer" ng-click="import(dash.name)">
|
||||
import
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
{{dashboard.title}}
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<input type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
|
||||
<input id="dash-{{$index}}" class="cr1" type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
|
||||
<label for="dash-{{$index}}" class="cr1"></label>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<i class="icon-remove pointer" ng-click="removeAsFavorite(dashboard)"></i>
|
||||
|
||||
@@ -20,12 +20,8 @@
|
||||
<div class="editor-option">
|
||||
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Show title </label><input type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle" />
|
||||
</div>
|
||||
<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
|
||||
<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
|
||||
</div>
|
||||
<div class="row-fluid" ng-if="editor.index == 1">
|
||||
<div class="span12">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<div ng-controller="SharePanelCtrl">
|
||||
<div class="modal-header">
|
||||
<div class="dashboard-editor-header">
|
||||
<div class="dashboard-editor-title">
|
||||
<i class="icon icon-share"></i>
|
||||
Share
|
||||
</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
|
||||
<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="editor-row">
|
||||
<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
|
||||
<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin-top: 20px;">
|
||||
<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success pull-right" ng-click="dismiss();">close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,9 +17,6 @@
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-segment-list" ng-if="dashboard.templating.enable">
|
||||
<li class="small grafana-target-segment">
|
||||
<strong>VARIABLES</strong>
|
||||
</li>
|
||||
<li ng-repeat-start="variable in variables" class="grafana-target-segment template-param-name">
|
||||
<span class="template-variable ">
|
||||
${{variable.name}}:
|
||||
@@ -31,10 +28,6 @@
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-segment-list" ng-if="dashboard.annotations.enable">
|
||||
<li class="small grafana-target-segment">
|
||||
<strong>ANNOTATIONS</strong>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="annotation in dashboard.annotations.list" class="grafana-target-segment annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
|
||||
<a ng-click="disableAnnotation(annotation)">
|
||||
<i class="annotation-color-icon icon-bolt"></i>
|
||||
|
||||
@@ -66,10 +66,10 @@
|
||||
<label class="small">Datasource</label>
|
||||
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
|
||||
</div>
|
||||
<div class="editor-option text-center" ng-show="current.type === 'query'">
|
||||
<label class="small">Refresh on load <tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time.</tip></label>
|
||||
<input type="checkbox" ng-model="current.refresh" ng-checked="current.refresh">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
|
||||
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
|
||||
model="current.refresh"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div ng-show="current.type === 'interval'">
|
||||
@@ -80,10 +80,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
<div class="editor-option text-center">
|
||||
<label class="small">Include auto interval</label>
|
||||
<input type="checkbox" ng-model="current.auto" ng-checked="current.auto" ng-change="runQuery()">
|
||||
</div>
|
||||
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
|
||||
<div class="editor-option" ng-show="current.auto">
|
||||
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
|
||||
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
|
||||
@@ -118,10 +115,7 @@
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin: 15px 0">
|
||||
<div class="editor-option text-center">
|
||||
<label class="small">All option</label>
|
||||
<input type="checkbox" ng-model="current.includeAll" ng-checked="current.includeAll" ng-change="runQuery()">
|
||||
</div>
|
||||
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
|
||||
<div class="editor-option" ng-show="current.includeAll">
|
||||
<label class="small">All format</label>
|
||||
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<div class="modal-header">
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<h3 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h3>
|
||||
<div class="modal-body">
|
||||
<h4 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h4>
|
||||
<div class="row-fluid">
|
||||
<span class="span3">
|
||||
{{changes}}
|
||||
|
||||
</span>
|
||||
<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
|
||||
<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
|
||||
<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
|
||||
<span class="span3"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,13 @@ function (angular) {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
controller : 'DashFromDBProvider',
|
||||
reloadOnSearch: false,
|
||||
})
|
||||
.when('/dashboard/import/:id', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
controller : 'DashFromImportCtrl',
|
||||
reloadOnSearch: false,
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv) {
|
||||
@@ -31,12 +37,23 @@ function (angular) {
|
||||
var isTemp = window.location.href.indexOf('dashboard/temp') !== -1;
|
||||
|
||||
db.getDashboard($routeParams.id, isTemp)
|
||||
.then(function(dashboard) {
|
||||
$scope.emitAppEvent('setup-dashboard', dashboard);
|
||||
}).then(null, function(error) {
|
||||
$scope.emitAppEvent('setup-dashboard', { title: 'Grafana'});
|
||||
alertSrv.set('Error', error, 'error');
|
||||
});
|
||||
.then(function(dashboard) {
|
||||
$scope.initDashboard(dashboard, $scope);
|
||||
}).then(null, function(error) {
|
||||
$scope.initDashboard({ title: 'Grafana'}, $scope);
|
||||
alertSrv.set('Error', error, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) {
|
||||
|
||||
if (!window.grafanaImportDashboard) {
|
||||
alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000);
|
||||
$location.path('');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.initDashboard(window.grafanaImportDashboard, $scope);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ function (angular, $, config, _) {
|
||||
};
|
||||
|
||||
file_load($routeParams.jsonFile).then(function(result) {
|
||||
$scope.emitAppEvent('setup-dashboard', result);
|
||||
$scope.initDashboard(result, $scope);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ function (angular, $, config, _, kbn, moment) {
|
||||
};
|
||||
|
||||
script_load($routeParams.jsFile).then(function(result) {
|
||||
$scope.emitAppEvent('setup-dashboard', result.data);
|
||||
$scope.initDashboard(result.data, $scope);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -7,9 +7,21 @@ function (angular, _) {
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('alertSrv', function($timeout, $sce) {
|
||||
module.service('alertSrv', function($timeout, $sce, $rootScope) {
|
||||
var self = this;
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.onAppEvent('alert-error', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'error');
|
||||
});
|
||||
$rootScope.onAppEvent('alert-warning', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'warning', 5000);
|
||||
});
|
||||
$rootScope.onAppEvent('alert-success', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'success', 3000);
|
||||
});
|
||||
};
|
||||
|
||||
// List of all alert objects
|
||||
this.list = [];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
define([
|
||||
'./alertSrv',
|
||||
'./utilSrv',
|
||||
'./datasourceSrv',
|
||||
'./timeSrv',
|
||||
'./templateSrv',
|
||||
|
||||
@@ -13,7 +13,8 @@ define([
|
||||
var timezone;
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.$on('refresh', this.clearCache);
|
||||
$rootScope.onAppEvent('refresh', this.clearCache);
|
||||
$rootScope.onAppEvent('setup-dashboard', this.clearCache);
|
||||
};
|
||||
|
||||
this.clearCache = function() {
|
||||
|
||||
@@ -22,7 +22,7 @@ function(angular, $) {
|
||||
});
|
||||
|
||||
keyboardManager.bind('ctrl+f', function() {
|
||||
scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+h', function() {
|
||||
@@ -31,7 +31,7 @@ function(angular, $) {
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+s', function(evt) {
|
||||
scope.emitAppEvent('save-dashboard', evt);
|
||||
scope.appEvent('save-dashboard', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+r', function() {
|
||||
@@ -39,7 +39,7 @@ function(angular, $) {
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+z', function(evt) {
|
||||
scope.emitAppEvent('zoom-out', evt);
|
||||
scope.appEvent('zoom-out', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('esc', function() {
|
||||
@@ -53,7 +53,7 @@ function(angular, $) {
|
||||
modalData.$scope.dismiss();
|
||||
}
|
||||
|
||||
scope.emitAppEvent('hide-dash-editor');
|
||||
scope.appEvent('hide-dash-editor');
|
||||
|
||||
scope.exitFullscreen();
|
||||
}, { inputDisabled: true });
|
||||
|
||||
@@ -25,7 +25,7 @@ function (angular, $, kbn, _, moment) {
|
||||
this.tags = data.tags || [];
|
||||
this.style = data.style || "dark";
|
||||
this.timezone = data.timezone || 'browser';
|
||||
this.editable = data.editable || true;
|
||||
this.editable = data.editable === false ? false : true;
|
||||
this.hideControls = data.hideControls || false;
|
||||
this.rows = data.rows || [];
|
||||
this.nav = data.nav || [];
|
||||
|
||||
@@ -94,6 +94,10 @@ function (angular, _, config, kbn, moment) {
|
||||
|
||||
for (var i = 0; i < fieldNames.length; i++) {
|
||||
fieldValue = fieldValue[fieldNames[i]];
|
||||
if (!fieldValue) {
|
||||
console.log('could not find field in annotatation: ', fieldName);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isArray(fieldValue)) {
|
||||
|
||||
@@ -203,7 +203,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
else {
|
||||
var self = this;
|
||||
return this._influxRequest('POST', '/series', data).then(function() {
|
||||
self._removeUnslugifiedDashboard(title, false);
|
||||
self._removeUnslugifiedDashboard(id, title, false);
|
||||
return { title: title, url: '/dashboard/db/' + id };
|
||||
}, function(err) {
|
||||
throw 'Failed to save dashboard to InfluxDB: ' + err.data;
|
||||
@@ -211,11 +211,13 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
}
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype._removeUnslugifiedDashboard = function(id, isTemp) {
|
||||
InfluxDatasource.prototype._removeUnslugifiedDashboard = function(id, title, isTemp) {
|
||||
if (id === title) { return; }
|
||||
|
||||
var self = this;
|
||||
self._getDashboardInternal(id, isTemp).then(function(dashboard) {
|
||||
self._getDashboardInternal(title, isTemp).then(function(dashboard) {
|
||||
if (dashboard !== null) {
|
||||
self.deleteDashboard(id);
|
||||
self.deleteDashboard(title);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,70 +10,65 @@ function (angular, _) {
|
||||
|
||||
this.init = function($scope) {
|
||||
if (!$scope.panel.span) { $scope.panel.span = 12; }
|
||||
if (!$scope.panel.title) { $scope.panel.title = 'No title'; }
|
||||
|
||||
var menu = [
|
||||
{
|
||||
text: 'Edit',
|
||||
configModal: "app/partials/paneleditor.html",
|
||||
condition: !$scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
click: "toggleFullscreen(true)",
|
||||
condition: $scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: "Fullscreen",
|
||||
text: "view",
|
||||
icon: "icon-eye-open",
|
||||
click: 'toggleFullscreen(false)',
|
||||
condition: $scope.panelMeta.fullscreenView
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
text: 'edit',
|
||||
icon: 'icon-cogs',
|
||||
click: 'editPanel()',
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
text: 'duplicate',
|
||||
icon: 'icon-copy',
|
||||
click: 'duplicatePanel(panel)',
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Span',
|
||||
submenu: [
|
||||
{ text: '1', click: 'updateColumnSpan(1)' },
|
||||
{ text: '2', click: 'updateColumnSpan(2)' },
|
||||
{ text: '3', click: 'updateColumnSpan(3)' },
|
||||
{ text: '4', click: 'updateColumnSpan(4)' },
|
||||
{ text: '5', click: 'updateColumnSpan(5)' },
|
||||
{ text: '6', click: 'updateColumnSpan(6)' },
|
||||
{ text: '7', click: 'updateColumnSpan(7)' },
|
||||
{ text: '8', click: 'updateColumnSpan(8)' },
|
||||
{ text: '9', click: 'updateColumnSpan(9)' },
|
||||
{ text: '10', click: 'updateColumnSpan(10)' },
|
||||
{ text: '11', click: 'updateColumnSpan(11)' },
|
||||
{ text: '12', click: 'updateColumnSpan(12)' },
|
||||
],
|
||||
text: 'json',
|
||||
icon: 'icon-code',
|
||||
click: 'editPanelJson()',
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
submenu: [
|
||||
{ text: 'Panel JSON', click: 'editPanelJson()' },
|
||||
],
|
||||
text: 'share',
|
||||
icon: 'icon-share',
|
||||
click: 'sharePanel()',
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
click: 'remove_panel_from_row(row, panel)',
|
||||
condition: true
|
||||
}
|
||||
];
|
||||
|
||||
$scope.inspector = {};
|
||||
$scope.panelMeta.menu = _.where(menu, { condition: true });
|
||||
|
||||
$scope.editPanel = function() {
|
||||
if ($scope.panelMeta.fullscreenEdit) {
|
||||
$scope.toggleFullscreen(true);
|
||||
}
|
||||
else {
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/paneleditor.html', scope: $scope });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.sharePanel = function() {
|
||||
$scope.appEvent('show-modal', {
|
||||
src: './app/partials/share-panel.html',
|
||||
scope: $scope.$new()
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editPanelJson = function() {
|
||||
$scope.emitAppEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
|
||||
$scope.appEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
|
||||
};
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
$scope.panel.span = Math.min(Math.max($scope.panel.span + span, 1), 12);
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
|
||||
@@ -91,7 +91,7 @@ define([
|
||||
this.old_refresh = null;
|
||||
}
|
||||
|
||||
$rootScope.emitAppEvent('time-range-changed', this.time);
|
||||
$rootScope.appEvent('time-range-changed', this.time);
|
||||
$timeout(this.refreshDashboard, 0);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
define([
|
||||
'angular',
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('utilSrv', function($rootScope, $modal, $q) {
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.onAppEvent('show-modal', this.showModal);
|
||||
};
|
||||
|
||||
this.showModal = function(e, options) {
|
||||
var modal = $modal({
|
||||
template: options.src,
|
||||
persist: false,
|
||||
show: false,
|
||||
scope: options.scope,
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
$q.when(modal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
+96
-94
@@ -2,108 +2,110 @@
|
||||
// config.js is where you will find the core Grafana configuration. This file contains parameter that
|
||||
// must be set before Grafana is run for the first time.
|
||||
|
||||
define(['settings'],
|
||||
function (Settings) {
|
||||
define(['settings'], function(Settings) {
|
||||
"use strict";
|
||||
|
||||
return new Settings({
|
||||
|
||||
/* Data sources
|
||||
* ========================================================
|
||||
* Datasources are used to fetch metrics, annotations, and serve as dashboard storage
|
||||
* - You can have multiple of the same type.
|
||||
* - grafanaDB: true marks it for use for dashboard storage
|
||||
* - default: true marks the datasource as the default metric source (if you have multiple)
|
||||
* - basic authentication: use url syntax http://username:password@domain:port
|
||||
*/
|
||||
/* Data sources
|
||||
* ========================================================
|
||||
* Datasources are used to fetch metrics, annotations, and serve as dashboard storage
|
||||
* - You can have multiple of the same type.
|
||||
* - grafanaDB: true marks it for use for dashboard storage
|
||||
* - default: true marks the datasource as the default metric source (if you have multiple)
|
||||
* - basic authentication: use url syntax http://username:password@domain:port
|
||||
*/
|
||||
|
||||
// InfluxDB example setup (the InfluxDB databases specified need to exist)
|
||||
/*
|
||||
datasources: {
|
||||
influxdb: {
|
||||
type: 'influxdb',
|
||||
url: "http://my_influxdb_server:8086/db/database_name",
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
// InfluxDB example setup (the InfluxDB databases specified need to exist)
|
||||
/*
|
||||
datasources: {
|
||||
influxdb: {
|
||||
type: 'influxdb',
|
||||
url: "http://my_influxdb_server:8086/db/database_name",
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
},
|
||||
grafana: {
|
||||
type: 'influxdb',
|
||||
url: "http://my_influxdb_server:8086/db/grafana",
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
grafanaDB: true
|
||||
},
|
||||
},
|
||||
grafana: {
|
||||
type: 'influxdb',
|
||||
url: "http://my_influxdb_server:8086/db/grafana",
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
grafanaDB: true
|
||||
},
|
||||
},
|
||||
*/
|
||||
*/
|
||||
|
||||
// Graphite & Elasticsearch example setup
|
||||
/*
|
||||
datasources: {
|
||||
graphite: {
|
||||
type: 'graphite',
|
||||
url: "http://my.graphite.server.com:8080",
|
||||
// Graphite & Elasticsearch example setup
|
||||
/*
|
||||
datasources: {
|
||||
graphite: {
|
||||
type: 'graphite',
|
||||
url: "http://my.graphite.server.com:8080",
|
||||
},
|
||||
elasticsearch: {
|
||||
type: 'elasticsearch',
|
||||
url: "http://my.elastic.server.com:9200",
|
||||
index: 'grafana-dash',
|
||||
grafanaDB: true,
|
||||
}
|
||||
},
|
||||
elasticsearch: {
|
||||
type: 'elasticsearch',
|
||||
url: "http://my.elastic.server.com:9200",
|
||||
index: 'grafana-dash',
|
||||
grafanaDB: true,
|
||||
*/
|
||||
|
||||
// OpenTSDB & Elasticsearch example setup
|
||||
/*
|
||||
datasources: {
|
||||
opentsdb: {
|
||||
type: 'opentsdb',
|
||||
url: "http://opentsdb.server:4242",
|
||||
},
|
||||
elasticsearch: {
|
||||
type: 'elasticsearch',
|
||||
url: "http://my.elastic.server.com:9200",
|
||||
index: 'grafana-dash',
|
||||
grafanaDB: true,
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
/* Global configuration options
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
// specify the limit for dashboard search results
|
||||
search: {
|
||||
max_results: 20
|
||||
},
|
||||
|
||||
// default home dashboard
|
||||
default_route: '/dashboard/file/default.json',
|
||||
|
||||
// set to false to disable unsaved changes warning
|
||||
unsaved_changes_warning: true,
|
||||
|
||||
// set the default timespan for the playlist feature
|
||||
// Example: "1m", "1h"
|
||||
playlist_timespan: "1m",
|
||||
|
||||
// If you want to specify password before saving, please specify it below
|
||||
// The purpose of this password is not security, but to stop some users from accidentally changing dashboards
|
||||
admin: {
|
||||
password: ''
|
||||
},
|
||||
|
||||
// Change window title prefix from 'Grafana - <dashboard title>'
|
||||
window_title_prefix: 'Grafana - ',
|
||||
|
||||
// Add your own custom panels
|
||||
plugins: {
|
||||
// list of plugin panels
|
||||
panels: [],
|
||||
// requirejs modules in plugins folder that should be loaded
|
||||
// for example custom datasources
|
||||
dependencies: [],
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
// OpenTSDB & Elasticsearch example setup
|
||||
/*
|
||||
datasources: {
|
||||
opentsdb: {
|
||||
type: 'opentsdb',
|
||||
url: "http://opentsdb.server:4242",
|
||||
},
|
||||
elasticsearch: {
|
||||
type: 'elasticsearch',
|
||||
url: "http://my.elastic.server.com:9200",
|
||||
index: 'grafana-dash',
|
||||
grafanaDB: true,
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
/* Global configuration options
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
// specify the limit for dashboard search results
|
||||
search: {
|
||||
max_results: 20
|
||||
},
|
||||
|
||||
// default home dashboard
|
||||
default_route: '/dashboard/file/default.json',
|
||||
|
||||
// set to false to disable unsaved changes warning
|
||||
unsaved_changes_warning: true,
|
||||
|
||||
// set the default timespan for the playlist feature
|
||||
// Example: "1m", "1h"
|
||||
playlist_timespan: "1m",
|
||||
|
||||
// If you want to specify password before saving, please specify it bellow
|
||||
// The purpose of this password is not security, but to stop some users from accidentally changing dashboards
|
||||
admin: {
|
||||
password: ''
|
||||
},
|
||||
|
||||
// Change window title prefix from 'Grafana - <dashboard title>'
|
||||
window_title_prefix: 'Grafana - ',
|
||||
|
||||
// Add your own custom panels
|
||||
plugins: {
|
||||
// list of plugin panels
|
||||
panels: [],
|
||||
// requirejs modules in plugins folder that should be loaded
|
||||
// for example custom datasources
|
||||
dependencies: [],
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ hr {
|
||||
|
||||
.brand {
|
||||
padding: 0px 15px;
|
||||
color: @grayLighter;
|
||||
color: @navbarBrandColor;
|
||||
font-weight: normal;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
input[type=text].input-fluid {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 14px;
|
||||
-moz-box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
input[type="checkbox"].cr1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"]+.cr1 {
|
||||
display: inline-block;
|
||||
height: 19px;
|
||||
clear: none;
|
||||
text-indent: 2px;
|
||||
margin-top: 4px;
|
||||
padding: 0 0 0 20px;
|
||||
vertical-align:middle;
|
||||
background: url(@checkboxImageUrl) left top no-repeat;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked+label {
|
||||
background: url(@checkboxImageUrl) 0px -18px no-repeat;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@import "tables_lists.less";
|
||||
@import "search.less";
|
||||
@import "panel.less";
|
||||
@import "forms.less";
|
||||
|
||||
.hide-controls {
|
||||
padding: 0;
|
||||
@@ -54,8 +55,12 @@
|
||||
}
|
||||
|
||||
.modal {
|
||||
margin: 5%;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
top: 200px;
|
||||
}
|
||||
|
||||
.grafana-search-metric-actions {
|
||||
@@ -118,8 +123,9 @@
|
||||
}
|
||||
|
||||
.dashboard-fullscreen {
|
||||
.row-control-inner {
|
||||
display: none;
|
||||
.main-view-container {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,6 +445,18 @@ select.grafana-target-segment-input {
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
overflow: hidden;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.grafana-tooltip hr {
|
||||
padding: 2px;
|
||||
color: #c8c8c8;
|
||||
margin: 0px;
|
||||
border-bottom:0px solid #c8c8c8;
|
||||
/*height:0px;
|
||||
background-color: rgb(58, 57, 57);*/
|
||||
}
|
||||
|
||||
.tooltip.in {
|
||||
|
||||
+17
-1
@@ -1,5 +1,6 @@
|
||||
.graph-canvas-wrapper {
|
||||
position: relative;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.graph-legend {
|
||||
@@ -124,7 +125,7 @@
|
||||
|
||||
.graph-legend-series-hidden {
|
||||
a {
|
||||
color: darken(@linkColor, 45%);
|
||||
color: @linkColorDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,3 +167,18 @@
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.graph-tooltip {
|
||||
.graph-tooltip-time {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.graph-tooltip-value {
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,11 +252,6 @@ form input.ng-invalid {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
top: 0px !important;
|
||||
}
|
||||
|
||||
.tiny {
|
||||
font-size: 50%;
|
||||
}
|
||||
@@ -567,12 +562,6 @@ div.flot-text {
|
||||
background-color: darken(@purple, 10%);
|
||||
}
|
||||
|
||||
.annotation-editor-table {
|
||||
td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Top menu
|
||||
.save-dashboard-dropdown {
|
||||
padding: 10px;
|
||||
|
||||
+43
-14
@@ -15,10 +15,17 @@
|
||||
padding: 0px 10px 5px 10px;
|
||||
}
|
||||
|
||||
.panel-title-container {
|
||||
min-height: 5px;
|
||||
cursor: context-menu;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
border: 0px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
font-size: 0.9em;
|
||||
cursor: context-menu;
|
||||
}
|
||||
|
||||
.panel-loading {
|
||||
@@ -28,23 +35,10 @@
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
.panel div.panel-extra div.panel-extra-container {
|
||||
margin-right: -10px;
|
||||
margin-top: 3px;
|
||||
.panel-header {
|
||||
text-align: center;
|
||||
ul {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.panel div.panel-extra {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.panel div.panel-extra .extra {
|
||||
float:right !important;
|
||||
}
|
||||
|
||||
.panel-error {
|
||||
color: @white;
|
||||
@@ -69,3 +63,38 @@
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.panel-menu {
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
background: @grafanaTargetFuncBackground;
|
||||
border: 1px solid black;
|
||||
top: -62px;
|
||||
|
||||
.panel-menu-row {
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid black;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-move {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.panel-menu-link, .panel-menu-icon {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.panel-menu-link {
|
||||
display: inline-block;
|
||||
border-right: 1px solid black;
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-highlight {
|
||||
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 5px rgba(82,168,236, 0.8)");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
}
|
||||
|
||||
.annotation-disabled, .annotation-disabled a {
|
||||
color: darken(@textColor, 25%);
|
||||
color: @linkColorDisabled;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: darken(@white,11%);
|
||||
@linkColorDisabled: darken(@linkColor,45%);
|
||||
@linkColorHover: @white;
|
||||
|
||||
|
||||
@@ -218,7 +219,7 @@
|
||||
@navbarLinkBackgroundHover: transparent;
|
||||
@navbarLinkBackgroundActive: @navbarBackground;
|
||||
|
||||
@navbarBrandColor: @navbarLinkColor;
|
||||
@navbarBrandColor: @linkColor;
|
||||
|
||||
// Inverted navbar
|
||||
@navbarInverseBackground: #252A30;
|
||||
@@ -289,7 +290,8 @@
|
||||
@popoverArrowOuterWidth: @popoverArrowWidth + 1;
|
||||
@popoverArrowOuterColor: rgba(0,0,0,.25);
|
||||
|
||||
|
||||
// images
|
||||
@checkboxImageUrl: '../img/checkbox.png';
|
||||
|
||||
// GRID
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -54,12 +54,13 @@
|
||||
// Scaffolding
|
||||
// -------------------------
|
||||
@bodyBackground: @grayLighter;
|
||||
@textColor: #555;
|
||||
@textColor: #666;
|
||||
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: @textColor;
|
||||
@linkColorDisabled: lighten(@linkColor,35%);
|
||||
@linkColorHover: @blue;
|
||||
|
||||
|
||||
@@ -298,12 +299,12 @@
|
||||
@popoverArrowOuterWidth: @popoverArrowWidth + 1;
|
||||
@popoverArrowOuterColor: rgba(0,0,0,.25);
|
||||
|
||||
|
||||
// images
|
||||
@checkboxImageUrl: '../img/checkbox_white.png';
|
||||
|
||||
// GRID
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
// Default 940px grid
|
||||
// -------------------------
|
||||
@gridColumns: 12;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 870 B |
Binary file not shown.
|
After Width: | Height: | Size: 960 B |
@@ -82,6 +82,22 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('when creating dashboard with editable false', function() {
|
||||
var model;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(inject(function(dashboardSrv) {
|
||||
model = dashboardSrv.create({
|
||||
editable: false
|
||||
});
|
||||
}));
|
||||
|
||||
it('should set editable false', function() {
|
||||
expect(model.editable).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when creating dashboard with old schema', function() {
|
||||
var model;
|
||||
var graph;
|
||||
|
||||
@@ -27,8 +27,12 @@ define([
|
||||
legend: {},
|
||||
grid: {},
|
||||
y_formats: [],
|
||||
seriesOverrides: []
|
||||
seriesOverrides: [],
|
||||
tooltip: {
|
||||
shared: true
|
||||
}
|
||||
};
|
||||
scope.hiddenSeries = {};
|
||||
scope.dashboard = { timezone: 'browser' };
|
||||
scope.range = {
|
||||
from: new Date('2014-08-09 10:00:00'),
|
||||
@@ -145,6 +149,18 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('when series is hidden', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.hiddenSeries = {'series2': true};
|
||||
});
|
||||
|
||||
it('should remove datapoints and disable stack', function() {
|
||||
expect(ctx.plotData[0].info.alias).to.be('series1');
|
||||
expect(ctx.plotData[1].data.length).to.be(0);
|
||||
expect(ctx.plotData[1].stack).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
define([
|
||||
'jquery',
|
||||
'directives/grafanaGraph.tooltip'
|
||||
], function($, tooltip) {
|
||||
'use strict';
|
||||
|
||||
describe('graph tooltip', function() {
|
||||
var elem = $('<div></div>');
|
||||
var dashboard = {
|
||||
formatDate: sinon.stub().returns('date'),
|
||||
};
|
||||
var scope = {
|
||||
panel: {
|
||||
tooltip: {
|
||||
shared: true
|
||||
},
|
||||
y_formats: ['ms', 'none'],
|
||||
}
|
||||
};
|
||||
|
||||
var data = [
|
||||
{
|
||||
data: [[10,10], [12,20]],
|
||||
info: { yaxis: 1 },
|
||||
yaxis: { tickDecimals: 2 },
|
||||
},
|
||||
{
|
||||
data: [[10,10], [12,20]],
|
||||
info: { yaxis: 1 },
|
||||
yaxis: { tickDecimals: 2 },
|
||||
}
|
||||
];
|
||||
|
||||
var plot = {
|
||||
getData: sinon.stub().returns(data),
|
||||
highlight: sinon.stub(),
|
||||
unhighlight: sinon.stub()
|
||||
};
|
||||
|
||||
elem.data('plot', plot);
|
||||
|
||||
beforeEach(function() {
|
||||
tooltip.register(elem, dashboard, scope);
|
||||
elem.trigger('plothover', [{}, {x: 13}, {}]);
|
||||
});
|
||||
|
||||
it('should add tooltip', function() {
|
||||
var tooltipHtml = $(".graph-tooltip").text();
|
||||
expect(tooltipHtml).to.be('date : 40.00 ms : 20.00 ms');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ define([
|
||||
var self = this;
|
||||
|
||||
this.datasource = {};
|
||||
this.$element = {};
|
||||
this.annotationsSrv = {};
|
||||
this.timeSrv = new TimeSrvStub();
|
||||
this.templateSrv = new TemplateSrvStub();
|
||||
@@ -16,18 +17,23 @@ define([
|
||||
get: function() { return self.datasource; }
|
||||
};
|
||||
|
||||
this.providePhase = function() {
|
||||
this.providePhase = function(mocks) {
|
||||
return module(function($provide) {
|
||||
$provide.value('datasourceSrv', self.datasourceSrv);
|
||||
$provide.value('annotationsSrv', self.annotationsSrv);
|
||||
$provide.value('timeSrv', self.timeSrv);
|
||||
$provide.value('templateSrv', self.templateSrv);
|
||||
$provide.value('$element', self.$element);
|
||||
_.each(mocks, function(key, value) {
|
||||
$provide.value(key, value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.createControllerPhase = function(controllerName) {
|
||||
return inject(function($controller, $rootScope, $q) {
|
||||
return inject(function($controller, $rootScope, $q, $location) {
|
||||
self.scope = $rootScope.$new();
|
||||
self.$location = $location;
|
||||
self.scope.panel = {};
|
||||
self.scope.row = { panels:[] };
|
||||
self.scope.dashboard = {};
|
||||
@@ -68,7 +74,7 @@ define([
|
||||
self.$httpBackend = $httpBackend;
|
||||
|
||||
self.$rootScope.onAppEvent = function() {};
|
||||
self.$rootScope.emitAppEvent = function() {};
|
||||
self.$rootScope.appEvent = function() {};
|
||||
|
||||
self.service = $injector.get(name);
|
||||
});
|
||||
|
||||
@@ -3,76 +3,31 @@ define([
|
||||
], function(kbn) {
|
||||
'use strict';
|
||||
|
||||
describe('millisecond formating', function() {
|
||||
function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
|
||||
|
||||
it('should translate 4378634603 as 1.67 years', function() {
|
||||
var str = kbn.msFormat(4378634603, 2);
|
||||
expect(str).to.be('50.68 day');
|
||||
describe('value format: ' + desc, function() {
|
||||
it('should translate ' + value + ' as ' + result, function() {
|
||||
var scaledDecimals = tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10);
|
||||
var str = kbn.valueFormats[desc](value, tickDecimals, scaledDecimals);
|
||||
expect(str).to.be(result);
|
||||
});
|
||||
});
|
||||
|
||||
it('should translate 3654454 as 1.02 hour', function() {
|
||||
var str = kbn.msFormat(3654454, 2);
|
||||
expect(str).to.be('1.02 hour');
|
||||
});
|
||||
}
|
||||
|
||||
it('should not downscale when value is zero', function() {
|
||||
var str = kbn.msFormat(0, 2);
|
||||
expect(str).to.be('0.00 ms');
|
||||
});
|
||||
describeValueFormat('ms', 0.0024, 0.0005, 4, '0.0024 ms');
|
||||
describeValueFormat('ms', 100, 1, 0, '100 ms');
|
||||
describeValueFormat('ms', 1250, 10, 0, '1.25 s');
|
||||
describeValueFormat('ms', 1250, 300, 0, '1.3 s');
|
||||
describeValueFormat('ms', 65150, 10000, 0, '1.1 min');
|
||||
describeValueFormat('ms', 6515000, 1500000, 0, '1.8 hour');
|
||||
describeValueFormat('ms', 651500000, 150000000, 0, '8 day');
|
||||
|
||||
describeValueFormat('none', 2.75e-10, 0, 10, '3e-10');
|
||||
describeValueFormat('none', 0, 0, 2, '0');
|
||||
|
||||
it('should translate 365445 as 6.09 min', function() {
|
||||
var str = kbn.msFormat(365445, 2);
|
||||
expect(str).to.be('6.09 min');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('high negative exponent, issue #696', function() {
|
||||
it('should ignore decimal correction if exponent', function() {
|
||||
var str = kbn.getFormatFunction('')(2.75e-10, { tickDecimals: 12 });
|
||||
expect(str).to.be('2.75e-10');
|
||||
});
|
||||
it('should format 0 correctly', function() {
|
||||
var str = kbn.getFormatFunction('')(0.0, { tickDecimals: 12 });
|
||||
expect(str).to.be('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('none format tests', function() {
|
||||
it('should translate 2 as 2.0000 if axis decimals is 4', function() {
|
||||
var str = kbn.getFormatFunction('')(2, { tickDecimals: 4 });
|
||||
expect(str).to.be('2.0000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nanosecond formatting', function () {
|
||||
it('should translate 25 to 25 ns', function () {
|
||||
var str = kbn.nanosFormat(25, 2);
|
||||
expect(str).to.be("25 ns");
|
||||
});
|
||||
|
||||
it('should translate 2558 to 2.56 µs', function () {
|
||||
var str = kbn.nanosFormat(2558, 2);
|
||||
expect(str).to.be("2.56 µs");
|
||||
});
|
||||
|
||||
it('should translate 2558000 to 2.56 ms', function () {
|
||||
var str = kbn.nanosFormat(2558000, 2);
|
||||
expect(str).to.be("2.56 ms");
|
||||
});
|
||||
|
||||
it('should translate 2019962000 to 2.02 s', function () {
|
||||
var str = kbn.nanosFormat(2049962000, 2);
|
||||
expect(str).to.be("2.05 s");
|
||||
});
|
||||
|
||||
it('should translate 95199620000 to 1.59 m', function () {
|
||||
var str = kbn.nanosFormat(95199620000, 2);
|
||||
expect(str).to.be("1.59 m");
|
||||
});
|
||||
|
||||
});
|
||||
describeValueFormat('ns', 25, 1, 0, '25 ns');
|
||||
describeValueFormat('ns', 2558, 50, 0, '2.56 µs');
|
||||
|
||||
describe('calculateInterval', function() {
|
||||
it('1h 100 resultion', function() {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'controllers/sharePanelCtrl'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('SharePanelCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.controllers'));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('SharePanelCtrl'));
|
||||
|
||||
describe('shareUrl with current time range and panel', function() {
|
||||
|
||||
it('should generate share url relative time', function() {
|
||||
ctx.$location.path('/test');
|
||||
ctx.scope.panel = { id: 22 };
|
||||
ctx.timeSrv.time = { from: 'now-1h', to: 'now' };
|
||||
|
||||
ctx.scope.buildUrl();
|
||||
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen');
|
||||
});
|
||||
|
||||
it('should generate share url absolute time', function() {
|
||||
ctx.$location.path('/test');
|
||||
ctx.scope.panel = { id: 22 };
|
||||
ctx.timeSrv.time = { from: new Date(2012,1,1), to: new Date(2014,3,5) };
|
||||
|
||||
ctx.scope.buildUrl();
|
||||
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen');
|
||||
});
|
||||
|
||||
it('should generate share url with time as JSON strings', function() {
|
||||
ctx.$location.path('/test');
|
||||
ctx.scope.panel = { id: 22 };
|
||||
ctx.timeSrv.time = { from: new Date(2012,1,1).toJSON(), to: new Date(2014,3,5).toJSON() };
|
||||
|
||||
ctx.scope.buildUrl();
|
||||
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen');
|
||||
});
|
||||
|
||||
it('should remove panel id when toPanel is false', function() {
|
||||
ctx.$location.path('/test');
|
||||
ctx.scope.panel = { id: 22 };
|
||||
ctx.scope.toPanel = false;
|
||||
ctx.timeSrv.time = { from: 'now-1h', to: 'now' };
|
||||
|
||||
ctx.scope.buildUrl();
|
||||
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now');
|
||||
});
|
||||
|
||||
it('should include template variables in url', function() {
|
||||
ctx.$location.path('/test');
|
||||
ctx.scope.panel = { id: 22 };
|
||||
ctx.scope.includeTemplateVars = true;
|
||||
ctx.scope.toPanel = false;
|
||||
ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}];
|
||||
ctx.timeSrv.time = { from: 'now-1h', to: 'now' };
|
||||
|
||||
ctx.scope.buildUrl();
|
||||
expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&var-app=mupp&var-server=srv-01');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ require.config({
|
||||
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
},
|
||||
@@ -77,6 +78,7 @@ require.config({
|
||||
'jquery.flot.stack': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
|
||||
|
||||
'angular-route': ['angular'],
|
||||
'angular-cookies': ['angular'],
|
||||
@@ -128,7 +130,9 @@ require([
|
||||
'specs/influxdb-datasource-specs',
|
||||
'specs/graph-ctrl-specs',
|
||||
'specs/grafanaGraph-specs',
|
||||
'specs/graph-tooltip-specs',
|
||||
'specs/seriesOverridesCtrl-specs',
|
||||
'specs/sharePanelCtrl-specs',
|
||||
'specs/timeSrv-specs',
|
||||
'specs/templateSrv-specs',
|
||||
'specs/templateValuesSrv-specs',
|
||||
|
||||
Vendored
+2
-1
@@ -435,7 +435,8 @@ angular.module('$strap.directives').factory('$modal', [
|
||||
return res.data;
|
||||
})).then(function onSuccess(template) {
|
||||
var id = templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id;
|
||||
var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).addClass('fade').html(template);
|
||||
// grafana change, removed fade
|
||||
var $modal = $('<div class="modal hide" tabindex="-1"></div>').attr('id', id).html(template);
|
||||
if (options.modalClass)
|
||||
$modal.addClass(options.modalClass);
|
||||
$('body').append($modal);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
.modal-backdrop,
|
||||
.modal-backdrop.fade.in {
|
||||
.opacity(80);
|
||||
.opacity(70);
|
||||
}
|
||||
|
||||
// Base modal
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
crosshair: {
|
||||
mode: null or "x" or "y" or "xy"
|
||||
color: color
|
||||
lineWidth: number
|
||||
}
|
||||
|
||||
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
|
||||
crosshair that lets you trace the values on the x axis, "y" enables a
|
||||
horizontal crosshair and "xy" enables them both. "color" is the color of the
|
||||
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
|
||||
the drawn lines (default is 1).
|
||||
|
||||
The plugin also adds four public methods:
|
||||
|
||||
- setCrosshair( pos )
|
||||
|
||||
Set the position of the crosshair. Note that this is cleared if the user
|
||||
moves the mouse. "pos" is in coordinates of the plot and should be on the
|
||||
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
|
||||
axes), which is coincidentally the same format as what you get from a
|
||||
"plothover" event. If "pos" is null, the crosshair is cleared.
|
||||
|
||||
- clearCrosshair()
|
||||
|
||||
Clear the crosshair.
|
||||
|
||||
- lockCrosshair(pos)
|
||||
|
||||
Cause the crosshair to lock to the current location, no longer updating if
|
||||
the user moves the mouse. Optionally supply a position (passed on to
|
||||
setCrosshair()) to move it to.
|
||||
|
||||
Example usage:
|
||||
|
||||
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
|
||||
$("#graph").bind( "plothover", function ( evt, position, item ) {
|
||||
if ( item ) {
|
||||
// Lock the crosshair to the data point being hovered
|
||||
myFlot.lockCrosshair({
|
||||
x: item.datapoint[ 0 ],
|
||||
y: item.datapoint[ 1 ]
|
||||
});
|
||||
} else {
|
||||
// Return normal crosshair operation
|
||||
myFlot.unlockCrosshair();
|
||||
}
|
||||
});
|
||||
|
||||
- unlockCrosshair()
|
||||
|
||||
Free the crosshair to move again after locking it.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
crosshair: {
|
||||
mode: null, // one of null, "x", "y" or "xy",
|
||||
color: "rgba(170, 0, 0, 0.80)",
|
||||
lineWidth: 1
|
||||
}
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
// position of crosshair in pixels
|
||||
var crosshair = { x: -1, y: -1, locked: false };
|
||||
|
||||
plot.setCrosshair = function setCrosshair(pos) {
|
||||
if (!pos)
|
||||
crosshair.x = -1;
|
||||
else {
|
||||
var o = plot.p2c(pos);
|
||||
crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
|
||||
crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
|
||||
}
|
||||
|
||||
plot.triggerRedrawOverlay();
|
||||
};
|
||||
|
||||
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
|
||||
|
||||
plot.lockCrosshair = function lockCrosshair(pos) {
|
||||
if (pos)
|
||||
plot.setCrosshair(pos);
|
||||
crosshair.locked = true;
|
||||
};
|
||||
|
||||
plot.unlockCrosshair = function unlockCrosshair() {
|
||||
crosshair.locked = false;
|
||||
};
|
||||
|
||||
function onMouseOut(e) {
|
||||
if (crosshair.locked)
|
||||
return;
|
||||
|
||||
if (crosshair.x != -1) {
|
||||
crosshair.x = -1;
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
if (crosshair.locked)
|
||||
return;
|
||||
|
||||
if (plot.getSelection && plot.getSelection()) {
|
||||
crosshair.x = -1; // hide the crosshair while selecting
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = plot.offset();
|
||||
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
|
||||
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
|
||||
plot.hooks.bindEvents.push(function (plot, eventHolder) {
|
||||
if (!plot.getOptions().crosshair.mode)
|
||||
return;
|
||||
|
||||
eventHolder.mouseout(onMouseOut);
|
||||
eventHolder.mousemove(onMouseMove);
|
||||
});
|
||||
|
||||
plot.hooks.drawOverlay.push(function (plot, ctx) {
|
||||
var c = plot.getOptions().crosshair;
|
||||
if (!c.mode)
|
||||
return;
|
||||
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
|
||||
if (crosshair.x != -1) {
|
||||
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
|
||||
|
||||
ctx.strokeStyle = c.color;
|
||||
ctx.lineWidth = c.lineWidth;
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
ctx.beginPath();
|
||||
if (c.mode.indexOf("x") != -1) {
|
||||
var drawX = Math.floor(crosshair.x) + adj;
|
||||
ctx.moveTo(drawX, 0);
|
||||
ctx.lineTo(drawX, plot.height());
|
||||
}
|
||||
if (c.mode.indexOf("y") != -1) {
|
||||
var drawY = Math.floor(crosshair.y) + adj;
|
||||
ctx.moveTo(0, drawY);
|
||||
ctx.lineTo(plot.width(), drawY);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
plot.hooks.shutdown.push(function (plot, eventHolder) {
|
||||
eventHolder.unbind("mouseout", onMouseOut);
|
||||
eventHolder.unbind("mousemove", onMouseMove);
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'crosshair',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
Vendored
+2
@@ -1728,6 +1728,8 @@ Licensed under the MIT license.
|
||||
axis.delta = delta;
|
||||
axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
|
||||
axis.tickSize = opts.tickSize || size;
|
||||
// grafana addition
|
||||
axis.scaledDecimals = axis.tickDecimals - Math.floor(Math.log(axis.tickSize) / Math.LN10);
|
||||
|
||||
// Time mode was moved to a plug-in in 0.8, and since so many people use it
|
||||
// we'll add an especially friendly reminder to make sure they included it.
|
||||
|
||||
Reference in New Issue
Block a user