Merge branch 'master' of github.com:torkelo/grafana-private into pro

This commit is contained in:
Torkel Ödegaard
2014-09-30 15:35:20 +02:00
79 changed files with 1394 additions and 796 deletions
+21
View File
@@ -0,0 +1,21 @@
{
"preset" : "default",
"lineBreak" : {
"before" : {
"VariableDeclarationWithoutInit" : 0,
},
"after": {
"AssignmentOperator": -1,
"ArgumentListArrayExpression": ">=1"
}
},
"whiteSpace" : {
"before" : {
},
"after" : {
}
}
}
+21 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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";
}
};
+2
View File
@@ -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'],
+9 -9
View File
@@ -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;
});
+1
View File
@@ -15,5 +15,6 @@ define([
'./opentsdbTargetCtrl',
'./annotationsEditorCtrl',
'./templateEditorCtrl',
'./sharePanelCtrl',
'./jsonEditorCtrl',
], function () {});
+6 -8
View File
@@ -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();
});
});
+8 -8
View File
@@ -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 -6
View File
@@ -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);
};
+11 -7
View File
@@ -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();
}
+6
View File
@@ -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) {
+3 -2
View File
@@ -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);
};
+90
View File
@@ -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 -2
View File
@@ -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) {
+2 -1
View File
@@ -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 {
+7 -5
View File
@@ -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);
});
};
};
+33 -59
View File
@@ -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({
+122
View File
@@ -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
};
});
+11 -29
View File
@@ -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 {
+130
View File
@@ -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);
}
};
});
});
+23
View File
@@ -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));
}
};
});
});
+12 -41
View File
@@ -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>
+1 -1
View File
@@ -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>
+3 -2
View File
@@ -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: [{}],
+17 -19
View File
@@ -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>
+1
View File
@@ -20,6 +20,7 @@ function (angular, app, _, require) {
// Set and populate defaults
var _d = {
title: 'default title',
mode : "markdown", // 'html', 'markdown', 'text'
content : "",
style: {},
+1 -4
View File
@@ -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>
+1 -1
View File
@@ -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
+1 -4
View File
@@ -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>
+10 -2
View File
@@ -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&#45;eye&#45;open"></i> <span>view</span></a> -->
<!-- <a class="pointer"><i class="icon&#45;cog"></i> <span>edit</span></a> -->
<!-- <a class="pointer"><i class="icon&#45;resize&#45;horizontal"></i> <span>span</span></a> -->
<!-- <a class="pointer"><i class="icon&#45;copy"></i> <span>duplicate</span></a> -->
<!-- <a class="pointer"><i class="icon&#45;share"></i> <span>share</span></a> -->
<!-- <a class="pointer"><i class="icon&#45;remove"></i> <span>remove</span></a> -->
</div>
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels"
+7 -15
View File
@@ -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>
+2
View File
@@ -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">
+8 -4
View File
@@ -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>
+2 -1
View File
@@ -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>
+2 -6
View File
@@ -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">
+33
View File
@@ -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>
-7
View File
@@ -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>
+6 -12
View File
@@ -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>
+3 -4
View File
@@ -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>
+23 -6
View File
@@ -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);
});
});
+1 -1
View File
@@ -52,7 +52,7 @@ function (angular, $, config, _) {
};
file_load($routeParams.jsonFile).then(function(result) {
$scope.emitAppEvent('setup-dashboard', result);
$scope.initDashboard(result, $scope);
});
});
+1 -1
View File
@@ -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);
});
});
+13 -1
View File
@@ -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
View File
@@ -1,5 +1,6 @@
define([
'./alertSrv',
'./utilSrv',
'./datasourceSrv',
'./timeSrv',
'./templateSrv',
+2 -1
View File
@@ -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 });
+1 -1
View File
@@ -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);
}
});
};
+34 -39
View File
@@ -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');
+1 -1
View File
@@ -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);
};
+31
View File
@@ -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
View File
@@ -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: [],
}
});
});
});
+1 -1
View File
@@ -57,7 +57,7 @@ hr {
.brand {
padding: 0px 15px;
color: @grayLighter;
color: @navbarBrandColor;
font-weight: normal;
text-shadow: none;
}
+28
View File
@@ -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;
}
+22 -4
View File
@@ -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
View File
@@ -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;
}
}
-11
View File
@@ -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
View File
@@ -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)");
}
+1 -1
View File
@@ -5,6 +5,6 @@
}
.annotation-disabled, .annotation-disabled a {
color: darken(@textColor, 25%);
color: @linkColorDisabled;
}
+4 -2
View File
@@ -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
// --------------------------------------------------
+4 -3
View File
@@ -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

+16
View File
@@ -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;
+17 -1
View File
@@ -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);
});
});
});
});
+56
View File
@@ -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');
});
});
});
+9 -3
View File
@@ -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);
});
+19 -64
View File
@@ -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() {
+71
View File
@@ -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');
});
});
});
});
+4
View File
@@ -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',
+2 -1
View File
@@ -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
View File
@@ -17,7 +17,7 @@
.modal-backdrop,
.modal-backdrop.fade.in {
.opacity(80);
.opacity(70);
}
// Base modal
+176
View File
@@ -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);
+2
View File
@@ -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.