Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts: src/test/test-main.js
This commit is contained in:
+16
-7
@@ -78,14 +78,23 @@ function (angular, $, _, appLevelRequire, config) {
|
||||
apps_deps.push(module_name);
|
||||
});
|
||||
|
||||
var preBootRequires = [
|
||||
'controllers/all',
|
||||
'directives/all',
|
||||
'filters/all',
|
||||
'components/partials',
|
||||
'routes/all',
|
||||
];
|
||||
|
||||
_.each(config.plugins.dependencies, function(dep) {
|
||||
preBootRequires.push('../plugins/' + dep);
|
||||
});
|
||||
|
||||
app.boot = function() {
|
||||
require([
|
||||
'controllers/all',
|
||||
'directives/all',
|
||||
'filters/all',
|
||||
'components/partials',
|
||||
'routes/all',
|
||||
], function () {
|
||||
require(preBootRequires, function () {
|
||||
|
||||
// disable tool tip animation
|
||||
$.fn.tooltip.defaults.animation = false;
|
||||
|
||||
// bootstrap the app
|
||||
angular
|
||||
|
||||
+44
-17
@@ -1,4 +1,8 @@
|
||||
define(['jquery','lodash','moment'],
|
||||
define([
|
||||
'jquery',
|
||||
'lodash',
|
||||
'moment'
|
||||
],
|
||||
function($, _, moment) {
|
||||
'use strict';
|
||||
|
||||
@@ -227,36 +231,36 @@ function($, _, moment) {
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('years',num);
|
||||
dateTime.add(num, 'years');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('years',num);
|
||||
dateTime.subtract(num, 'years');
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('months',num);
|
||||
dateTime.add(num, 'months');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('months',num);
|
||||
dateTime.subtract(num, 'months');
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('weeks',num);
|
||||
dateTime.add(num, 'weeks');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('weeks',num);
|
||||
dateTime.subtract(num, 'weeks');
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('days',num);
|
||||
dateTime.add(num, 'days');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('days',num);
|
||||
dateTime.subtract(num, 'days');
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
@@ -264,27 +268,27 @@ function($, _, moment) {
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('hours',num);
|
||||
dateTime.add(num, 'hours');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('hours',num);
|
||||
dateTime.subtract(num,'hours');
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('minutes',num);
|
||||
dateTime.add(num, 'minutes');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('minutes',num);
|
||||
dateTime.subtract(num, 'minutes');
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (type === 0) {
|
||||
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
|
||||
} else if (type === 1) {
|
||||
dateTime.add('seconds',num);
|
||||
dateTime.add(num, 'seconds');
|
||||
} else if (type === 2) {
|
||||
dateTime.subtract('seconds',num);
|
||||
dateTime.subtract(num, 'seconds');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -525,12 +529,35 @@ function($, _, moment) {
|
||||
return kbn.nanosFormat(val, decimals);
|
||||
};
|
||||
default:
|
||||
return function(val) {
|
||||
return val % 1 === 0 ? val : val.toFixed(decimals);
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kbn.noneFormat = function(value, decimals) {
|
||||
var factor = decimals ? Math.pow(10, decimals) : 1;
|
||||
var formatted = String(Math.round(value * factor) / factor);
|
||||
|
||||
// if exponent return directly
|
||||
if (formatted.indexOf('e') !== -1 || value === 0) {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (precision < decimals) {
|
||||
return (precision ? formatted : formatted + ".") + (String(factor)).substr(1, decimals - precision);
|
||||
}
|
||||
}
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
kbn.msFormat = function(size, decimals) {
|
||||
// Less than 1 milli, downscale to micro
|
||||
if (Math.abs(size) < 1) {
|
||||
|
||||
@@ -33,4 +33,4 @@ function () {
|
||||
});
|
||||
|
||||
return _;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ require.config({
|
||||
config: ['../config', '../config.sample'],
|
||||
settings: 'components/settings',
|
||||
kbn: 'components/kbn',
|
||||
store: 'components/store',
|
||||
|
||||
css: '../vendor/require/css',
|
||||
text: '../vendor/require/text',
|
||||
@@ -27,7 +28,7 @@ require.config({
|
||||
'lodash-src': '../vendor/lodash',
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
|
||||
jquery: '../vendor/jquery/jquery-1.8.0',
|
||||
jquery: '../vendor/jquery/jquery-2.1.1.min',
|
||||
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
|
||||
|
||||
'extend-jquery': 'components/extend-jquery',
|
||||
|
||||
@@ -19,7 +19,7 @@ function (_, crypto) {
|
||||
default_route : '/dashboard/file/default.json',
|
||||
playlist_timespan : "1m",
|
||||
unsaved_changes_warning : true,
|
||||
search : { max_results: 20 },
|
||||
search : { max_results: 16 },
|
||||
admin : {}
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ function (_, crypto) {
|
||||
|
||||
_.each(settings.datasources, function(datasource, key) {
|
||||
datasource.name = key;
|
||||
parseBasicAuth(datasource);
|
||||
if (datasource.url) { parseBasicAuth(datasource); }
|
||||
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
|
||||
});
|
||||
|
||||
@@ -78,6 +78,10 @@ function (_, crypto) {
|
||||
settings.panels = _.union(settings.panels, settings.plugins.panels);
|
||||
}
|
||||
|
||||
if (!settings.plugins.dependencies) {
|
||||
settings.plugins.dependencies = [];
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
define([], function() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
get: function(key) {
|
||||
return window.localStorage[key];
|
||||
},
|
||||
set: function(key, value) {
|
||||
window.localStorage[key] = value;
|
||||
},
|
||||
getBool: function(key) {
|
||||
return window.localStorage[key] === 'true' ? true : false;
|
||||
},
|
||||
delete: function(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
@@ -5,15 +5,57 @@ define([
|
||||
function (_, kbn) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
ts.ZeroFilled = function (opts) {
|
||||
function TimeSeries(opts) {
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
}
|
||||
|
||||
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
|
||||
if (!aliasOrRegex) { return false; }
|
||||
|
||||
if (aliasOrRegex[0] === '/') {
|
||||
var match = aliasOrRegex.match(new RegExp('^/(.*?)/(g?i?m?y?)$'));
|
||||
var regex = new RegExp(match[1], match[2]);
|
||||
return seriesAlias.match(regex) != null;
|
||||
}
|
||||
|
||||
return aliasOrRegex === seriesAlias;
|
||||
}
|
||||
|
||||
function translateFillOption(fill) {
|
||||
return fill === 0 ? 0.001 : fill/10;
|
||||
}
|
||||
|
||||
TimeSeries.prototype.applySeriesOverrides = function(overrides) {
|
||||
this.lines = {};
|
||||
this.points = {};
|
||||
this.bars = {};
|
||||
this.info.yaxis = 1;
|
||||
this.zindex = 0;
|
||||
delete this.stack;
|
||||
|
||||
for (var i = 0; i < overrides.length; i++) {
|
||||
var override = overrides[i];
|
||||
if (!matchSeriesOverride(override.alias, this.info.alias)) {
|
||||
continue;
|
||||
}
|
||||
if (override.lines !== void 0) { this.lines.show = override.lines; }
|
||||
if (override.points !== void 0) { this.points.show = override.points; }
|
||||
if (override.bars !== void 0) { this.bars.show = override.bars; }
|
||||
if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
|
||||
if (override.stack !== void 0) { this.stack = override.stack; }
|
||||
if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
|
||||
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
|
||||
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
|
||||
if (override.zindex !== void 0) { this.zindex = override.zindex; }
|
||||
if (override.yaxis !== void 0) {
|
||||
this.info.yaxis = override.yaxis;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
|
||||
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
|
||||
var result = [];
|
||||
|
||||
this.color = this.info.color;
|
||||
@@ -74,5 +116,6 @@ function (_, kbn) {
|
||||
return result;
|
||||
};
|
||||
|
||||
return ts;
|
||||
});
|
||||
return TimeSeries;
|
||||
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
define([
|
||||
'./grafanaCtrl',
|
||||
'./dash',
|
||||
'./dashLoader',
|
||||
'./dashboardCtrl',
|
||||
'./dashboardNavCtrl',
|
||||
'./row',
|
||||
'./submenuCtrl',
|
||||
'./pulldown',
|
||||
|
||||
@@ -2,12 +2,13 @@ define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'moment',
|
||||
'store'
|
||||
],
|
||||
function (angular, _, moment) {
|
||||
function (angular, _, moment, store) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
var consoleEnabled = window.localStorage && window.localStorage.grafanaConsole === 'true';
|
||||
var consoleEnabled = store.getBool('grafanaConsole');
|
||||
|
||||
if (!consoleEnabled) {
|
||||
return;
|
||||
@@ -15,20 +16,6 @@ function (angular, _, moment) {
|
||||
|
||||
var events = [];
|
||||
|
||||
var oldLog = console.log;
|
||||
console.log = function (message) {
|
||||
try {
|
||||
if (_.isObject(message)) {
|
||||
message = angular.toJson(message);
|
||||
if (message.length > 50) {
|
||||
message = message.substring(0, 50);
|
||||
}
|
||||
}
|
||||
events.push(new ConsoleEvent('log', message, {}));
|
||||
oldLog.apply(console, arguments);
|
||||
} catch (e) { }
|
||||
};
|
||||
|
||||
function ConsoleEvent(type, title, data) {
|
||||
this.type = type;
|
||||
this.title = title;
|
||||
|
||||
@@ -10,8 +10,10 @@ function (angular, $, config, _) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('DashCtrl', function(
|
||||
$scope, $rootScope, dashboardKeybindings, filterSrv, dashboardSrv, panelMoveSrv, timer) {
|
||||
module.controller('DashboardCtrl', function(
|
||||
$scope, $rootScope, dashboardKeybindings,
|
||||
filterSrv, dashboardSrv, dashboardViewStateSrv,
|
||||
panelMoveSrv, timer, $timeout) {
|
||||
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.panelNames = config.panels;
|
||||
@@ -19,14 +21,25 @@ function (angular, $, config, _) {
|
||||
$scope.init = function() {
|
||||
$scope.availablePanels = config.panels;
|
||||
$scope.onAppEvent('setup-dashboard', $scope.setupDashboard);
|
||||
|
||||
angular.element(window).bind('resize', function() {
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('render');
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.setupDashboard = function(event, dashboardData) {
|
||||
timer.cancel_all();
|
||||
|
||||
$rootScope.fullscreen = false;
|
||||
$rootScope.performance.dashboardLoadStart = new Date().getTime();
|
||||
$rootScope.performance.panelsInitialized = 0;
|
||||
$rootScope.performance.panelsRendered= 0;
|
||||
|
||||
$scope.dashboard = dashboardSrv.create(dashboardData);
|
||||
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
|
||||
|
||||
$scope.grafana.style = $scope.dashboard.style;
|
||||
|
||||
$scope.filter = filterSrv;
|
||||
@@ -78,10 +91,6 @@ function (angular, $, config, _) {
|
||||
};
|
||||
};
|
||||
|
||||
$scope.row_style = function(row) {
|
||||
return { 'min-height': row.collapse ? '5px' : row.height };
|
||||
};
|
||||
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
@@ -3,17 +3,19 @@ define([
|
||||
'lodash',
|
||||
'moment',
|
||||
'config',
|
||||
'store',
|
||||
'filesaver'
|
||||
],
|
||||
function (angular, _, moment, config) {
|
||||
function (angular, _, moment, config, store) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('dashLoader', function($scope, $rootScope, $http, alertSrv, $location, playlistSrv, datasourceSrv) {
|
||||
module.controller('DashboardNavCtrl', function($scope, $rootScope, alertSrv, $location, playlistSrv, datasourceSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.db = datasourceSrv.getGrafanaDB();
|
||||
|
||||
$scope.onAppEvent('save-dashboard', function() {
|
||||
$scope.saveDashboard();
|
||||
});
|
||||
@@ -21,19 +23,16 @@ function (angular, _, moment, config) {
|
||||
$scope.onAppEvent('zoom-out', function() {
|
||||
$scope.zoom(2);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.exitFullscreen = function() {
|
||||
$scope.emitAppEvent('panel-fullscreen-exit');
|
||||
};
|
||||
|
||||
$scope.set_default = function() {
|
||||
window.localStorage.grafanaDashboardDefault = $location.path();
|
||||
store.set('grafanaDashboardDefault', $location.path());
|
||||
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
|
||||
};
|
||||
|
||||
$scope.purge_default = function() {
|
||||
delete window.localStorage.grafanaDashboardDefault;
|
||||
store.delete('grafanaDashboardDefault');
|
||||
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000);
|
||||
};
|
||||
|
||||
@@ -78,6 +77,7 @@ function (angular, _, moment, config) {
|
||||
.then(function(result) {
|
||||
alertSrv.set('Dashboard Saved', 'Dashboard has been saved as "' + result.title + '"','success', 5000);
|
||||
|
||||
$location.search({});
|
||||
$location.path(result.url);
|
||||
|
||||
$rootScope.$emit('dashboard-saved', $scope.dashboard);
|
||||
@@ -87,7 +87,9 @@ function (angular, _, moment, config) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteDashboard = function(id) {
|
||||
$scope.deleteDashboard = function(id, $event) {
|
||||
$event.stopPropagation();
|
||||
|
||||
if (!confirm('Are you sure you want to delete dashboard?')) {
|
||||
return;
|
||||
}
|
||||
@@ -135,6 +137,7 @@ function (angular, _, moment, config) {
|
||||
|
||||
$scope.openSaveDropdown = function() {
|
||||
$scope.isFavorite = playlistSrv.isCurrentFavorite($scope.dashboard);
|
||||
$scope.saveDropdownOpened = true;
|
||||
};
|
||||
|
||||
$scope.markAsFavorite = function() {
|
||||
@@ -2,8 +2,10 @@ define([
|
||||
'angular',
|
||||
'config',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'store',
|
||||
],
|
||||
function (angular, config, _) {
|
||||
function (angular, config, _, $, store) {
|
||||
"use strict";
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
@@ -11,20 +13,23 @@ function (angular, config, _) {
|
||||
module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) {
|
||||
|
||||
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
|
||||
$scope.consoleEnabled = store.getBool('grafanaConsole');
|
||||
|
||||
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
|
||||
$rootScope.performance = { loadStart: new Date().getTime() };
|
||||
|
||||
$scope.init = function() {
|
||||
$scope._ = _;
|
||||
$scope.dashAlerts = alertSrv;
|
||||
$scope.grafana = {
|
||||
style: 'dark'
|
||||
};
|
||||
|
||||
$scope.consoleEnabled = (window.localStorage && window.localStorage.grafanaConsole === 'true');
|
||||
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
|
||||
|
||||
$scope.dashAlerts = alertSrv;
|
||||
$scope.grafana = { style: 'dark' };
|
||||
};
|
||||
|
||||
$scope.toggleConsole = function() {
|
||||
$scope.consoleEnabled = !$scope.consoleEnabled;
|
||||
window.localStorage.grafanaConsole = $scope.consoleEnabled ? 'true' : 'false';
|
||||
store.set('grafanaConsole', $scope.consoleEnabled);
|
||||
};
|
||||
|
||||
$rootScope.onAppEvent = function(name, callback) {
|
||||
@@ -46,6 +51,61 @@ function (angular, config, _) {
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
|
||||
];
|
||||
|
||||
$scope.getTotalWatcherCount = function() {
|
||||
var count = 0;
|
||||
var scopes = 0;
|
||||
var root = $(document.getElementsByTagName('body'));
|
||||
|
||||
var f = function (element) {
|
||||
if (element.data().hasOwnProperty('$scope')) {
|
||||
scopes++;
|
||||
angular.forEach(element.data().$scope.$$watchers, function () {
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
angular.forEach(element.children(), function (childElement) {
|
||||
f($(childElement));
|
||||
});
|
||||
};
|
||||
|
||||
f(root);
|
||||
$rootScope.performance.scopeCount = scopes;
|
||||
return count;
|
||||
};
|
||||
|
||||
$scope.initProfiling = function() {
|
||||
var count = 0;
|
||||
|
||||
$scope.$watch(function digestCounter() {
|
||||
count++;
|
||||
}, function() {
|
||||
});
|
||||
|
||||
$scope.onAppEvent('setup-dashboard', function() {
|
||||
count = 0;
|
||||
|
||||
setTimeout(function() {
|
||||
console.log("Dashboard::Performance Total Digests: " + count);
|
||||
console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount());
|
||||
console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount);
|
||||
|
||||
var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart;
|
||||
console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms");
|
||||
|
||||
// measure digest performance
|
||||
var rootDigestStart = window.performance.now();
|
||||
for (var i = 0; i < 30; i++) {
|
||||
$rootScope.$apply();
|
||||
}
|
||||
console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
|
||||
|
||||
}, 3000);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
@@ -289,7 +289,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
this.expandable = options.expandable;
|
||||
|
||||
if (options.type === 'template') {
|
||||
this.html = $sce.trustAsHtml("<span style='color: #ECEC09'>" + options.value + "</span>");
|
||||
this.html = $sce.trustAsHtml(options.value);
|
||||
}
|
||||
else {
|
||||
this.html = $sce.trustAsHtml(this.value);
|
||||
|
||||
+33
-59
@@ -32,36 +32,13 @@ function (angular, app, _) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.rowSpan = function(row) {
|
||||
return _.reduce(row.panels, function(p,v) {
|
||||
return p + v.span;
|
||||
},0);
|
||||
};
|
||||
|
||||
// This can be overridden by individual panels
|
||||
$scope.close_edit = function() {
|
||||
$scope.$broadcast('render');
|
||||
};
|
||||
|
||||
$scope.add_panel = function(panel) {
|
||||
var rowSpan = $scope.rowSpan($scope.row);
|
||||
var panelCount = $scope.row.panels.length;
|
||||
var space = (12 - rowSpan) - panel.span;
|
||||
|
||||
// try to make room of there is no space left
|
||||
if (space <= 0) {
|
||||
if (panelCount === 1) {
|
||||
$scope.row.panels[0].span = 6;
|
||||
panel.span = 6;
|
||||
}
|
||||
else if (panelCount === 2) {
|
||||
$scope.row.panels[0].span = 4;
|
||||
$scope.row.panels[1].span = 4;
|
||||
panel.span = 4;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.row.panels.push(panel);
|
||||
$scope.dashboard.add_panel(panel, $scope.row);
|
||||
};
|
||||
|
||||
$scope.delete_row = function() {
|
||||
@@ -100,45 +77,17 @@ function (angular, app, _) {
|
||||
};
|
||||
|
||||
$scope.duplicatePanel = function(panel, row) {
|
||||
row = row || $scope.row;
|
||||
var currentRowSpan = $scope.rowSpan(row);
|
||||
if (currentRowSpan <= 9) {
|
||||
row.panels.push(angular.copy(panel));
|
||||
}
|
||||
else {
|
||||
var rowsList = $scope.dashboard.rows;
|
||||
var rowIndex = _.indexOf(rowsList, row);
|
||||
if (rowIndex === rowsList.length - 1) {
|
||||
var newRow = angular.copy($scope.row);
|
||||
newRow.panels = [];
|
||||
$scope.dashboard.rows.push(newRow);
|
||||
$scope.duplicatePanel(panel, newRow);
|
||||
}
|
||||
else {
|
||||
$scope.duplicatePanel(panel, rowsList[rowIndex+1]);
|
||||
}
|
||||
}
|
||||
$scope.dashboard.duplicatePanel(panel, row || $scope.row);
|
||||
};
|
||||
|
||||
$scope.reset_panel = function(type) {
|
||||
var
|
||||
defaultSpan = 12,
|
||||
_as = 12-$scope.rowSpan($scope.row);
|
||||
var defaultSpan = 12;
|
||||
var _as = 12 - $scope.dashboard.rowSpan($scope.row);
|
||||
|
||||
$scope.panel = {
|
||||
error : false,
|
||||
/** @scratch /panels/1
|
||||
* span:: A number, 1-12, that describes the width of the panel.
|
||||
*/
|
||||
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
|
||||
/** @scratch /panels/1
|
||||
* editable:: Enable or disable the edit button the the panel
|
||||
*/
|
||||
editable: true,
|
||||
/** @scratch /panels/1
|
||||
* type:: The type of panel this object contains. Each panel type will require additional
|
||||
* properties. See the panel types list to the right.
|
||||
*/
|
||||
type : type
|
||||
};
|
||||
|
||||
@@ -155,12 +104,37 @@ function (angular, app, _) {
|
||||
$scope.row.height = fixRowHeight($scope.row.height);
|
||||
};
|
||||
|
||||
/** @scratch /panels/2
|
||||
* --
|
||||
*/
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
module.directive('rowHeight', function() {
|
||||
return function(scope, element) {
|
||||
scope.$watchGroup(['row.collapse', 'row.height'], function() {
|
||||
element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('panelWidth', function() {
|
||||
return function(scope, element) {
|
||||
scope.$watch('panel.span', function() {
|
||||
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('panelDropZone', function() {
|
||||
return function(scope, element) {
|
||||
scope.$watch('dashboard.$$panelDragging', function(newVal) {
|
||||
if (newVal && scope.dashboard.rowSpan(scope.row) < 10) {
|
||||
element.show();
|
||||
}
|
||||
else {
|
||||
element.hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ function (angular, _, config, $) {
|
||||
|
||||
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
|
||||
if (selectedDash) {
|
||||
$location.search({});
|
||||
$location.path("/dashboard/db/" + selectedDash.id);
|
||||
setTimeout(function() {
|
||||
$('body').click(); // hack to force dropdown to close;
|
||||
@@ -49,7 +50,12 @@ function (angular, _, config, $) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.shareDashboard = function(title, id) {
|
||||
$scope.goToDashboard = function(id) {
|
||||
$location.path("/dashboard/db/" + id);
|
||||
};
|
||||
|
||||
$scope.shareDashboard = function(title, id, $event) {
|
||||
$event.stopPropagation();
|
||||
var baseUrl = window.location.href.replace(window.location.hash,'');
|
||||
|
||||
$scope.share = {
|
||||
@@ -98,6 +104,7 @@ function (angular, _, config, $) {
|
||||
$element.next().find('.dropdown-toggle').dropdown('toggle');
|
||||
}
|
||||
|
||||
$scope.searchOpened = true;
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.query.query = 'title:';
|
||||
$scope.search();
|
||||
|
||||
@@ -97,4 +97,4 @@ function (angular, app, _, $, gfunc) {
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('addPanel', function($compile) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem) {
|
||||
|
||||
$scope.$on("$destroy",function() {
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
$scope.$watch('panel.type', function() {
|
||||
var _type = $scope.panel.type;
|
||||
$scope.reset_panel(_type);
|
||||
if(!_.isUndefined($scope.panel.type)) {
|
||||
$scope.panel.loadingEditor = true;
|
||||
$scope.require(['panels/'+$scope.panel.type.replace(".","/") +'/module'], function () {
|
||||
var template = '<div ng-controller="'+$scope.panel.type+'" ng-include="\'app/partials/paneladd.html\'"></div>';
|
||||
elem.html($compile(angular.element(template))($scope));
|
||||
$scope.panel.loadingEditor = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
define([
|
||||
'./addPanel',
|
||||
'./arrayJoin',
|
||||
'./dashUpload',
|
||||
'./grafanaPanel',
|
||||
@@ -17,4 +16,4 @@ define([
|
||||
'./graphiteFuncEditor',
|
||||
'./grafanaVersionCheck',
|
||||
'./influxdbFuncEditor'
|
||||
], function () {});
|
||||
], function () {});
|
||||
|
||||
@@ -15,7 +15,7 @@ function (angular, app, _) {
|
||||
var lastPulldownVal;
|
||||
var lastHideControlsVal;
|
||||
|
||||
$scope.$watch('dashboard.pulldowns', function() {
|
||||
$scope.$watchCollection('dashboard.pulldowns', function() {
|
||||
if (!$scope.dashboard) {
|
||||
return;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ function (angular, app, _) {
|
||||
elem.toggleClass('submenu-controls-visible', panelEnabled);
|
||||
lastPulldownVal = panelEnabled;
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
$scope.$watch('dashboard.hideControls', function() {
|
||||
if (!$scope.dashboard) {
|
||||
@@ -49,4 +49,4 @@ function (angular, app, _) {
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,4 +45,4 @@ function (angular, _, $) {
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,6 @@ function (angular, $, kbn, moment, _) {
|
||||
var legendSideLastValue = null;
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
@@ -39,15 +38,14 @@ function (angular, $, kbn, moment, _) {
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, renderData) {
|
||||
data = renderData || data;
|
||||
if (!data) {
|
||||
scope.get_data();
|
||||
return;
|
||||
}
|
||||
annotations = data.annotations || annotations;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = scope.height || scope.panel.height || scope.row.height;
|
||||
@@ -115,7 +113,7 @@ function (angular, $, kbn, moment, _) {
|
||||
lines: {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
|
||||
fill: translateFillOption(panel.fill),
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
@@ -151,11 +149,12 @@ function (angular, $, kbn, moment, _) {
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
data[i].data = _d;
|
||||
var series = data[i];
|
||||
series.applySeriesOverrides(panel.seriesOverrides);
|
||||
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
}
|
||||
|
||||
if (panel.bars && data.length && data[0].info.timeStep) {
|
||||
if (data.length && data[0].info.timeStep) {
|
||||
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
|
||||
}
|
||||
|
||||
@@ -164,21 +163,27 @@ function (angular, $, kbn, moment, _) {
|
||||
addAnnotations(options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
// if legend is to the right delay plot draw a few milliseconds
|
||||
// so the legend width calculation can be done
|
||||
if (shouldDelayDraw(panel)) {
|
||||
legendSideLastValue = panel.legend.rightSide;
|
||||
setTimeout(function() {
|
||||
plot = $.plot(elem, data, options);
|
||||
plot = $.plot(elem, sortedSeries, options);
|
||||
addAxisLabels();
|
||||
}, 50);
|
||||
}
|
||||
else {
|
||||
plot = $.plot(elem, data, options);
|
||||
plot = $.plot(elem, sortedSeries, options);
|
||||
addAxisLabels();
|
||||
}
|
||||
}
|
||||
|
||||
function translateFillOption(fill) {
|
||||
return fill === 0 ? 0.001 : fill/10;
|
||||
}
|
||||
|
||||
function shouldDelayDraw(panel) {
|
||||
if (panel.legend.rightSide) {
|
||||
return true;
|
||||
@@ -300,9 +305,7 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format !== 'none') {
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
}
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
}
|
||||
|
||||
function time_format(interval, ticks, min, max) {
|
||||
@@ -353,10 +356,7 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
value = kbn.getFormatFunction(format, 2)(value);
|
||||
|
||||
timestamp = dashboard.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ function (angular, $) {
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('grafanaPanel', function($compile) {
|
||||
.directive('grafanaPanel', function($compile, $parse) {
|
||||
|
||||
var container = '<div class="panel-container"></div>';
|
||||
var content = '<div class="panel-content"></div>';
|
||||
@@ -18,11 +18,13 @@ function (angular, $) {
|
||||
'<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-show="panel.error" data-placement="right" bs-tooltip="panel.error">' +
|
||||
'config-modal="app/partials/inspector.html" ng-if="panel.error">' +
|
||||
'<span data-placement="right" bs-tooltip="panel.error">' +
|
||||
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
|
||||
'</span>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading == true">' +
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
|
||||
@@ -36,7 +38,7 @@ function (angular, $) {
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="row.panels" ' +
|
||||
'}" ng-model="panel" ' +
|
||||
'>' +
|
||||
'{{panel.title || "No title"}}' +
|
||||
'</span>' +
|
||||
@@ -49,8 +51,7 @@ function (angular, $) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, elem, attr) {
|
||||
// once we have the template, scan it for controllers and
|
||||
// load the module.js if we have any
|
||||
var getter = $parse(attr.type), panelType = getter($scope);
|
||||
var newScope = $scope.$new();
|
||||
|
||||
$scope.kbnJqUiDraggableOptions = {
|
||||
@@ -68,6 +69,14 @@ function (angular, $) {
|
||||
/* jshint indent:false */
|
||||
$compile(elem.contents())(newScope);
|
||||
elem.removeClass("ng-cloak");
|
||||
|
||||
var panelCtrlElem = $(elem.children()[0]);
|
||||
var panelCtrlScope = panelCtrlElem.data().$scope;
|
||||
|
||||
panelCtrlScope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
|
||||
panelCtrlElem.css({ minHeight: panelCtrlScope.panel.height || panelCtrlScope.row.height });
|
||||
panelCtrlElem.toggleClass('panel-fullscreen', panelCtrlScope.fullscreen ? true : false);
|
||||
});
|
||||
}
|
||||
|
||||
newScope.$on('$destroy',function() {
|
||||
@@ -75,31 +84,17 @@ function (angular, $) {
|
||||
elem.remove();
|
||||
});
|
||||
|
||||
$scope.$watch(attr.type, function (name) {
|
||||
elem.addClass("ng-cloak");
|
||||
// load the panels module file, then render it in the dom.
|
||||
var nameAsPath = name.replace(".", "/");
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+nameAsPath+'/module.html'
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
// top level controllers
|
||||
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
|
||||
// add child controllers
|
||||
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
|
||||
elem.addClass('ng-cloak');
|
||||
|
||||
if ($controllers.length) {
|
||||
$controllers.first().prepend(panelHeader);
|
||||
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
|
||||
|
||||
$scope.require(['panels/' + nameAsPath + '/module'], function() {
|
||||
loadModule($module);
|
||||
});
|
||||
} else {
|
||||
loadModule($module);
|
||||
}
|
||||
});
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+panelType+'/module.html',
|
||||
'panels/' + panelType + "/module",
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
$module.prepend(panelHeader);
|
||||
$module.first().find('.panel-header').nextAll().wrapAll(content);
|
||||
loadModule($module);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, _) {
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
@@ -60,18 +59,8 @@ function (angular, _) {
|
||||
loadController(name);
|
||||
});
|
||||
|
||||
if(attr.panel) {
|
||||
$scope.$watch(attr.panel, function (panel) {
|
||||
// If the panel attribute is specified, create a new scope. This ruins configuration
|
||||
// so don't do it with anything that needs to use editor.html
|
||||
if(!_.isUndefined(panel)) {
|
||||
$scope = $scope.$new();
|
||||
$scope.panel = angular.fromJson(panel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ function (angular) {
|
||||
return;
|
||||
}
|
||||
|
||||
$http({ method: 'GET', url: 'http://grafanarel.s3.amazonaws.com/latest.json' })
|
||||
$http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
|
||||
.then(function(response) {
|
||||
if (!response.data || !response.data.version) {
|
||||
return;
|
||||
|
||||
@@ -7,13 +7,14 @@ function (angular) {
|
||||
.directive('ngModelOnblur', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 1,
|
||||
require: 'ngModel',
|
||||
link: function(scope, elm, attr, ngModelCtrl) {
|
||||
if (attr.type === 'radio' || attr.type === 'checkbox') {
|
||||
return;
|
||||
}
|
||||
|
||||
elm.unbind('input').unbind('keydown').unbind('change');
|
||||
elm.off('input keydown change');
|
||||
elm.bind('blur', function() {
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(elm.val());
|
||||
@@ -22,4 +23,4 @@ function (angular) {
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,12 +34,12 @@
|
||||
|
||||
<div class="editor-row small" style="padding-bottom: 0;">
|
||||
<label>Axis:</label>
|
||||
<button ng-click="toggleYAxis(series)"
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 1 }">
|
||||
Left
|
||||
</button>
|
||||
<button ng-click="toggleYAxis(series)"
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 2 }">
|
||||
Right
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<div ng-controller='GraphCtrl'
|
||||
style="min-height:{{panel.height || row.height}}"
|
||||
ng-class="{'panel-fullscreen': fullscreen}">
|
||||
<div ng-controller='GraphCtrl'>
|
||||
|
||||
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
|
||||
<div class="graph-canvas-wrapper">
|
||||
@@ -29,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
|
||||
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-if="editorTabs[editor.index] == tab.title">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/histogram.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/histogram/0
|
||||
* == Histogram
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
|
||||
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
|
||||
* fields.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
@@ -18,7 +5,8 @@ define([
|
||||
'lodash',
|
||||
'kbn',
|
||||
'moment',
|
||||
'./timeSeries',
|
||||
'components/timeSeries',
|
||||
'./seriesOverridesCtrl',
|
||||
'services/panelSrv',
|
||||
'services/annotationsSrv',
|
||||
'services/datasourceSrv',
|
||||
@@ -29,11 +17,10 @@ define([
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent'
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.graph', []);
|
||||
var module = angular.module('grafana.panels.graph');
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('GraphCtrl', function($scope, $rootScope, $timeout, panelSrv, annotationsSrv) {
|
||||
@@ -179,7 +166,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
targets: [{}],
|
||||
|
||||
aliasColors: {},
|
||||
aliasYAxis: {},
|
||||
|
||||
seriesOverrides: [],
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
@@ -188,13 +176,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
_.defaults($scope.panel.grid, _d.grid);
|
||||
_.defaults($scope.panel.legend, _d.legend);
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init($scope);
|
||||
$scope.hiddenSeries = {};
|
||||
if (!$scope.skipDataOnInit) {
|
||||
$scope.get_data();
|
||||
}
|
||||
};
|
||||
$scope.hiddenSeries = {};
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = $scope.filter.timeRange();
|
||||
@@ -210,10 +192,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
delete $scope.panel.error;
|
||||
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var metricsQuery = {
|
||||
@@ -253,7 +231,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
var data = _.map(results.data, $scope.seriesHandler);
|
||||
|
||||
$scope.datapointsWarning = $scope.datapointsCount || !$scope.datapointsOutside;
|
||||
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
|
||||
|
||||
$scope.annotationsPromise
|
||||
.then(function(annotations) {
|
||||
@@ -268,18 +246,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var datapoints = seriesData.datapoints;
|
||||
var alias = seriesData.target;
|
||||
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
|
||||
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
|
||||
|
||||
var seriesInfo = {
|
||||
alias: alias,
|
||||
color: color,
|
||||
enable: true,
|
||||
yaxis: yaxis
|
||||
};
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
|
||||
var series = new timeSeries.ZeroFilled({
|
||||
var series = new TimeSeries({
|
||||
datapoints: datapoints,
|
||||
info: seriesInfo,
|
||||
});
|
||||
@@ -297,10 +272,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
return series;
|
||||
};
|
||||
|
||||
$scope.otherPanelInFullscreenMode = function() {
|
||||
return $rootScope.fullscreen && !$scope.fullscreen;
|
||||
};
|
||||
|
||||
$scope.render = function(data) {
|
||||
$scope.$emit('render', data);
|
||||
};
|
||||
@@ -361,8 +332,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
};
|
||||
|
||||
$scope.toggleYAxis = function(info) {
|
||||
info.yaxis = info.yaxis === 2 ? 1 : 2;
|
||||
$scope.panel.aliasYAxis[info.alias] = info.yaxis;
|
||||
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
|
||||
if (!override) {
|
||||
override = { alias: info.alias };
|
||||
$scope.panel.seriesOverrides.push(override);
|
||||
}
|
||||
override.yaxis = info.yaxis === 2 ? 1 : 2;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
@@ -371,7 +346,16 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
$scope.addSeriesOverride = function() {
|
||||
$scope.panel.seriesOverrides.push({});
|
||||
};
|
||||
|
||||
$scope.removeSeriesOverride = function(override) {
|
||||
$scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
panelSrv.init($scope);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
], function(angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.graph', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('SeriesOverridesCtrl', function($scope) {
|
||||
$scope.overrideMenu = [];
|
||||
$scope.currentOverrides = [];
|
||||
$scope.override = $scope.override || {};
|
||||
|
||||
$scope.addOverrideOption = function(name, propertyName, values) {
|
||||
var option = {};
|
||||
option.text = name;
|
||||
option.propertyName = propertyName;
|
||||
option.index = $scope.overrideMenu.length;
|
||||
option.values = values;
|
||||
|
||||
option.submenu = _.map(values, function(value, index) {
|
||||
return {
|
||||
text: String(value),
|
||||
click: 'setOverride(' + option.index + ',' + index + ')'
|
||||
};
|
||||
});
|
||||
|
||||
$scope.overrideMenu.push(option);
|
||||
};
|
||||
|
||||
$scope.setOverride = function(optionIndex, valueIndex) {
|
||||
var option = $scope.overrideMenu[optionIndex];
|
||||
var value = option.values[valueIndex];
|
||||
$scope.override[option.propertyName] = value;
|
||||
$scope.updateCurrentOverrides();
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.removeOverride = function(option) {
|
||||
delete $scope.override[option.propertyName];
|
||||
$scope.updateCurrentOverrides();
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.getSeriesNames = function() {
|
||||
return _.map($scope.legend, function(info) {
|
||||
return info.alias;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateCurrentOverrides = function() {
|
||||
$scope.currentOverrides = [];
|
||||
_.each($scope.overrideMenu, function(option) {
|
||||
var value = $scope.override[option.propertyName];
|
||||
if (_.isUndefined(value)) { return; }
|
||||
$scope.currentOverrides.push({
|
||||
name: option.text,
|
||||
propertyName: option.propertyName,
|
||||
value: String(value)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addOverrideOption('Bars', 'bars', [true, false]);
|
||||
$scope.addOverrideOption('Lines', 'lines', [true, false]);
|
||||
$scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
|
||||
$scope.addOverrideOption('Points', 'points', [true, false]);
|
||||
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
|
||||
$scope.addOverrideOption('Stack', 'stack', [true, false]);
|
||||
$scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
|
||||
$scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
|
||||
$scope.updateCurrentOverrides();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
@@ -64,3 +62,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
|
||||
<div>
|
||||
<div class="grafana-target" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
|
||||
<div class="grafana-target-inner-wrapper">
|
||||
<div class="grafana-target-inner">
|
||||
|
||||
<ul class="grafana-target-controls-left">
|
||||
<li class="grafana-target-segment">
|
||||
<i class="icon-remove pointer" ng-click="removeSeriesOverride(override)"></i>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment">
|
||||
alias or regex
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
ng-model="override.alias"
|
||||
bs-typeahead="getSeriesNames"
|
||||
ng-blur="render()"
|
||||
data-min-length=0 data-items=100
|
||||
class="input-medium grafana-target-segment-input" >
|
||||
</li>
|
||||
<li class="grafana-target-segment" ng-repeat="option in currentOverrides">
|
||||
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
|
||||
{{option.name}}: {{option.value}}
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'">
|
||||
<i class="icon-plus"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success" style="margin-top: 20px" ng-click="addSeriesOverride()">Add series override rule</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div ng-controller='text' style="min-height:{{panel.height || row.height}}">
|
||||
<div ng-controller='text'>
|
||||
<p ng-bind-html="content">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
/** @scratch /panels/5
|
||||
* include::panels/text.asciidoc[]
|
||||
*/
|
||||
|
||||
/** @scratch /panels/text/0
|
||||
* == text
|
||||
* Status: *Stable*
|
||||
*
|
||||
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
|
||||
* text.
|
||||
*
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
@@ -23,6 +11,8 @@ function (angular, app, _, require) {
|
||||
var module = angular.module('grafana.panels.text', []);
|
||||
app.useModule(module);
|
||||
|
||||
var converter;
|
||||
|
||||
module.controller('text', function($scope, filterSrv, $sce, panelSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
@@ -68,15 +58,20 @@ function (angular, app, _, require) {
|
||||
};
|
||||
|
||||
$scope.renderMarkdown = function(content) {
|
||||
require(['./lib/showdown'], function (Showdown) {
|
||||
var converter = new Showdown.converter();
|
||||
var text = content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<');
|
||||
var text = content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<');
|
||||
|
||||
if (converter) {
|
||||
$scope.updateContent(converter.makeHtml(text));
|
||||
});
|
||||
}
|
||||
else {
|
||||
require(['./lib/showdown'], function (Showdown) {
|
||||
converter = new Showdown.converter();
|
||||
$scope.updateContent(converter.makeHtml(text));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.updateContent = function(html) {
|
||||
@@ -88,7 +83,7 @@ function (angular, app, _, require) {
|
||||
}
|
||||
|
||||
if(!$scope.$$phase) {
|
||||
$scope.$apply();
|
||||
$scope.$digest();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,15 +15,8 @@
|
||||
<ul class="nav nav-pills timepicker-dropdown">
|
||||
<li class="dropdown">
|
||||
|
||||
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.from.date ? (time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' <br>to<br>' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss') : 'Click to set a time filter'" data-placement="bottom" ng-click="dismiss();">
|
||||
|
||||
<span ng-show="filter.time">
|
||||
<span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
|
||||
<span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
|
||||
to
|
||||
<span class="pointer" ng-hide="panel.now" >{{time.to.date | date:'MMM d, y HH:mm:ss'}}</span>
|
||||
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
|
||||
</span>
|
||||
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
|
||||
<span ng-show="filter.time" ng-bind="time.rangeString"></span>
|
||||
<span ng-hide="filter.time">Time filter</span>
|
||||
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
|
||||
<i class="icon-caret-down"></i>
|
||||
|
||||
@@ -80,9 +80,14 @@ function (angular, app, _, moment, kbn) {
|
||||
$scope.temptime = cloneTime($scope.time);
|
||||
$scope.tempnow = $scope.panel.now;
|
||||
|
||||
$scope.temptime.from.date.setHours(0,0,0,0);
|
||||
$scope.temptime.to.date.setHours(0,0,0,0);
|
||||
|
||||
// Date picker needs the date to be at the start of the day
|
||||
$scope.temptime.from.date.setHours(1,0,0,0);
|
||||
$scope.temptime.to.date.setHours(1,0,0,0);
|
||||
if(new Date().getTimezoneOffset() < 0) {
|
||||
$scope.temptime.from.date = moment($scope.temptime.from.date).add('days',1).toDate();
|
||||
$scope.temptime.to.date = moment($scope.temptime.to.date).add('days',1).toDate();
|
||||
}
|
||||
|
||||
$q.when(customTimeModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
@@ -172,10 +177,28 @@ function (angular, app, _, moment, kbn) {
|
||||
};
|
||||
|
||||
var getScopeTimeObj = function(from,to) {
|
||||
return {
|
||||
from: getTimeObj(from),
|
||||
to: getTimeObj(to)
|
||||
};
|
||||
var model = { from: getTimeObj(from), to: getTimeObj(to), };
|
||||
|
||||
if (model.from.date) {
|
||||
model.tooltip = $scope.dashboard.formatDate(model.from.date) + ' <br>to<br>';
|
||||
model.tooltip += $scope.dashboard.formatDate(model.to.date);
|
||||
}
|
||||
else {
|
||||
model.tooltip = 'Click to set time filter';
|
||||
}
|
||||
|
||||
if ($scope.filter.time) {
|
||||
if ($scope.panel.now) {
|
||||
model.rangeString = moment(model.from.date).fromNow() + ' to ' +
|
||||
moment(model.to.date).fromNow();
|
||||
}
|
||||
else {
|
||||
model.rangeString = $scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' +
|
||||
$scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
};
|
||||
|
||||
var getTimeObj = function(date) {
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<style>
|
||||
.noarrow>a:after {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<li ng-show="fullscreen">
|
||||
<a ng-click="exitFullscreen()">
|
||||
Back to dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-zoom-out">
|
||||
<a class='small' ng-click='zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"><grafana-simple-panel type="pulldown.type" ng-cloak></grafana-simple-panel></li>
|
||||
|
||||
<li class="dropdown grafana-menu-save">
|
||||
<a href="#" bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
|
||||
<i class='icon-save'></i>
|
||||
</a>
|
||||
|
||||
<ul class="save-dashboard-dropdown dropdown-menu">
|
||||
|
||||
<li>
|
||||
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
|
||||
<input class='input-medium' ng-model="dashboard.title" type="text" />
|
||||
<button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="link" ng-click="set_default()">Save as Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link" ng-click="purge_default()">Reset Home</a>
|
||||
</li>
|
||||
<li ng-show="!isFavorite">
|
||||
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
|
||||
</li>
|
||||
<li ng-show="isFavorite">
|
||||
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
|
||||
</li>
|
||||
<li ng-show="db.saveTemp">
|
||||
<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
|
||||
Share temp copy
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown grafana-menu-load" ng-controller="SearchCtrl" ng-init="init()" ng-include="'app/partials/search.html'">
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='icon-home'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" config-modal="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-stop-playlist hide">
|
||||
<a class='small' ng-click='stopPlaylist(2)'>
|
||||
Stop playlist
|
||||
</a>
|
||||
</li>
|
||||
@@ -1,16 +1,9 @@
|
||||
<div ng-controller="DashCtrl" body-class>
|
||||
<div ng-controller="DashboardCtrl" body-class ng-class="{'dashboard-fullscreen': dashboardViewState.fullscreen}">
|
||||
|
||||
<div class="navbar navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<span class="brand"><img src="img/small.png" bs-tooltip="'Grafana'" data-placement="bottom"> {{dashboard.title}}</span>
|
||||
<ul class="nav pull-right" ng-controller='dashLoader' ng-init="init()" ng-include="'app/partials/dashLoader.html'">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include="'app/partials/dashboard_topnav.html'">
|
||||
</div>
|
||||
|
||||
<div class="submenu-controls">
|
||||
<div class="submenu-controls">
|
||||
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.pulldowns | filter:{ enable: true }">
|
||||
<div class="submenu-panel-title">
|
||||
<span class="small"><strong>{{pulldown.type}}:</strong></span>
|
||||
@@ -27,7 +20,7 @@
|
||||
<div>
|
||||
<div class="grafana-container container">
|
||||
<!-- Rows -->
|
||||
<div class="grafana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.rows" ng-style="row_style(row)">
|
||||
<div class="grafana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.rows" row-height>
|
||||
<div class="row-control">
|
||||
<div class="row-control-inner" style="padding:0px;margin:0px;position:relative;">
|
||||
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
|
||||
@@ -98,14 +91,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Panels -->
|
||||
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':(panel.span/1.2)*10+'%'}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}" ng-class="{'dragInProgress':dashboard.$$panelDragging}">
|
||||
<!-- Content Panel -->
|
||||
<div style="position:relative">
|
||||
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
|
||||
</div>
|
||||
<div ng-repeat="(name, panel) in row.panels"
|
||||
class="panel nospace"
|
||||
style="position:relative"
|
||||
data-drop="true"
|
||||
panel-width
|
||||
ng-model="panel"
|
||||
data-jqyoui-options
|
||||
jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}"
|
||||
ng-class="{'dragInProgress':dashboard.$$panelDragging}">
|
||||
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
|
||||
</div>
|
||||
|
||||
<div ng-show="rowSpan(row) < 10 && dashboard.$$panelDragging" class="panel" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-class="{'dragInProgress':dashboard.panelDragging}" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
<div panel-drop-zone class="panel dragInProgress" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<div class="navbar navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<span class="brand"><img src="img/small.png" bs-tooltip="'Grafana'" data-placement="bottom"> {{dashboard.title}}</span>
|
||||
<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
|
||||
|
||||
<li ng-show="dashboardViewState.fullscreen">
|
||||
<a ng-click="exitFullscreen()">
|
||||
Back to dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-zoom-out">
|
||||
<a class='small' ng-click='zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
|
||||
<grafana-simple-panel type="pulldown.type" ng-cloak>
|
||||
</grafana-simple-panel>
|
||||
</li>
|
||||
|
||||
<li class="dropdown grafana-menu-save">
|
||||
<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
|
||||
<i class='icon-save'></i>
|
||||
</a>
|
||||
|
||||
<ul class="save-dashboard-dropdown dropdown-menu" ng-if="saveDropdownOpened">
|
||||
<li>
|
||||
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
|
||||
<input class='input-medium' ng-model="dashboard.title" type="text" />
|
||||
<button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="link" ng-click="set_default()">Save as Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link" ng-click="purge_default()">Reset Home</a>
|
||||
</li>
|
||||
<li ng-show="!isFavorite">
|
||||
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
|
||||
</li>
|
||||
<li ng-show="isFavorite">
|
||||
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
|
||||
</li>
|
||||
<li ng-show="db.saveTemp">
|
||||
<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
|
||||
Share temp copy
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown grafana-menu-load" ng-controller="SearchCtrl" ng-init="init()" ng-include="'app/partials/search.html'">
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='icon-home'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" config-modal="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
|
||||
|
||||
<li class="grafana-menu-stop-playlist hide">
|
||||
<a class='small' ng-click='stopPlaylist(2)'>
|
||||
Stop playlist
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 2" ng-controller="dashLoader">
|
||||
<div ng-if="editor.index == 2">
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Feature toggles</h5>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<div style="margin-top:50px" ng-controller="dashcontrol">
|
||||
<strong>type: </strong>{{type}} <br>
|
||||
<strong>id: </strong>{{id}} <br>
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="pull-right editor-title">Row settings</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
<div ng-repeat="tab in ['General','Panels','Add Panel']" data-title="{{tab}}">
|
||||
<div ng-repeat="tab in ['General','Panels']" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,11 +27,10 @@
|
||||
<thead>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Span <span class="small">({{rowSpan(row)}}/12)</span></th>
|
||||
<th>Span <span class="small">({{dashboard.rowSpan(row)}}/12)</span></th>
|
||||
<th>Delete</th>
|
||||
<th>Move</th>
|
||||
<th></th>
|
||||
<th>Hide</th>
|
||||
</thead>
|
||||
<tr ng-repeat="panel in row.panels">
|
||||
<td>{{panel.title}}</td>
|
||||
@@ -40,24 +39,10 @@
|
||||
<td><i ng-click="row.panels = _.without(row.panels,panel)" class="pointer icon-remove"></i></td>
|
||||
<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
|
||||
<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
|
||||
<td><input type="checkbox" ng-model="panel.hide" ng-checked="panel.hide"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid" ng-if="editor.index == 2">
|
||||
<h4>Select Panel Type</h4>
|
||||
<form class="form-inline">
|
||||
<select class="input-medium" ng-model="panel.type" ng-options="panelType for panelType in availablePanels|stringSort"></select>
|
||||
<small ng-show="rowSpan(row) > 11">
|
||||
Note: This row is full, new panels will wrap to a new line. You should add another row.
|
||||
</small>
|
||||
</form>
|
||||
|
||||
<div ng-show="!(_.isUndefined(panel.type))">
|
||||
<div add-panel="{{panel.type}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-show="editor.index == 1" ng-click="editor.index = 2;" class="btn btn-success" ng-disabled="panel.loadingEditor">Add Panel</button>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<i class='icon-folder-open'></i>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" id="grafana-search">
|
||||
<ul class="dropdown-menu" id="grafana-search" ng-if="searchOpened">
|
||||
<li ng-if="!showImport">
|
||||
<div class="grafana-search-panel">
|
||||
<div class="search-field-wrapper">
|
||||
@@ -47,50 +47,46 @@
|
||||
|
||||
<h6 ng-hide="results.dashboards.length || results.metrics.length">No dashboards or metrics matching your query found</h6>
|
||||
|
||||
<table class="table table-condensed table-striped" ng-if="tagsOnly">
|
||||
<tr ng-repeat="tag in results.tags" ng-class="{'selected-tag': $index === selectedIndex }">
|
||||
<td>
|
||||
<a ng-click="filterByTag(tag.term, $event)" class="label label-tag">
|
||||
{{tag.term}} ({{tag.count}})
|
||||
</a>
|
||||
</td>
|
||||
<td style="width:100%;padding-left: 10px;font-weight: bold;">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="search-results-container" ng-if="tagsOnly">
|
||||
<div ng-repeat="tag in results.tags"
|
||||
class="search-result-item pointer"
|
||||
ng-class="{'selected': $index === selectedIndex }"
|
||||
ng-click="filterByTag(tag.term, $event)">
|
||||
<a class="search-result-link" >
|
||||
<i class="icon icon-tag"></i>
|
||||
<span class="label label-tag">{{tag.term}} ({{tag.count}})</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-condensed table-striped" ng-if="!tagsOnly">
|
||||
<tbody style="max-height: 570px; overflow: auto; display: block">
|
||||
<tr bindonce ng-repeat="row in results.metrics"
|
||||
class="grafana-search-metric-result"
|
||||
ng-class="{'selected': $index === selectedIndex }">
|
||||
<td><span class="label label-info">metric</span></td>
|
||||
<td class="grafana-search-metric-name">
|
||||
{{row.id}}
|
||||
</td>
|
||||
<td style="width:100%;">
|
||||
<div class="grafana-search-metric-actions">
|
||||
<a ng-click="addMetricToCurrentDashboard(row.id)"><i class="icon-plus-sign"></i> Add to dashboard</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="search-results-container" ng-if="!tagsOnly">
|
||||
<div class="search-result-item pointer"
|
||||
bindonce ng-repeat="row in results.dashboards"
|
||||
ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.id)">
|
||||
|
||||
<div class="search-result-actions">
|
||||
<a ng-click="shareDashboard(row.id, row.id, $event)" config-modal="app/partials/dashLoaderShare.html">
|
||||
<i class="icon-share"></i> share
|
||||
</a>
|
||||
<a ng-click="deleteDashboard(row.id, $event)">
|
||||
<i class="icon-remove"></i> delete
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="search-result-tags">
|
||||
<a ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" style="margin-right: 5px;" class="label label-tag">
|
||||
{{tag}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a class="search-result-link">
|
||||
<i class="icon icon-th-large"></i>
|
||||
<span bo-text="row.id"></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tr bindonce
|
||||
ng-repeat="row in results.dashboards"
|
||||
ng-class="{'selected': $index === selectedIndex }">
|
||||
<td><a ng-click="deleteDashboard(row.id)"><i class="icon-remove"></i></a></td>
|
||||
<td style="width:100%">
|
||||
<a href="#/dashboard/db/{{row.id}}" bo-text="row.id"></a>
|
||||
</td>
|
||||
<td style="white-space: nowrap; text-align: right;">
|
||||
<a ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" style="margin-right: 5px;" class="label label-tag">
|
||||
{{tag}}
|
||||
</a>
|
||||
</td>
|
||||
<td><a><i class="icon-share" ng-click="shareDashboard(row.id, row.id)" config-modal="app/partials/dashLoaderShare.html"></i></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
define([
|
||||
'angular',
|
||||
'config'
|
||||
'config',
|
||||
'store'
|
||||
],
|
||||
function (angular, config) {
|
||||
function (angular, config, store) {
|
||||
"use strict";
|
||||
|
||||
var module = angular.module('grafana.routes');
|
||||
@@ -11,12 +12,7 @@ function (angular, config) {
|
||||
$routeProvider
|
||||
.when('/', {
|
||||
redirectTo: function() {
|
||||
if (window.localStorage && window.localStorage.grafanaDashboardDefault) {
|
||||
return window.localStorage.grafanaDashboardDefault;
|
||||
}
|
||||
else {
|
||||
return config.default_route;
|
||||
}
|
||||
return store.get('grafanaDashboardDefault') || config.default_route;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ function (angular) {
|
||||
.when('/dashboard/db/:id', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
controller : 'DashFromDBProvider',
|
||||
reloadOnSearch: false,
|
||||
})
|
||||
.when('/dashboard/elasticsearch/:id', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
|
||||
@@ -11,5 +11,6 @@ define([
|
||||
'./unsavedChangesSrv',
|
||||
'./dashboard/dashboardKeyBindings',
|
||||
'./dashboard/dashboardSrv',
|
||||
'./dashboard/dashboardViewStateSrv',
|
||||
],
|
||||
function () {});
|
||||
|
||||
@@ -12,28 +12,14 @@ function(angular, $) {
|
||||
|
||||
this.shortcuts = function(scope) {
|
||||
|
||||
scope.onAppEvent('panel-fullscreen-enter', function() {
|
||||
$rootScope.fullscreen = true;
|
||||
});
|
||||
|
||||
scope.onAppEvent('panel-fullscreen-exit', function() {
|
||||
$rootScope.fullscreen = false;
|
||||
});
|
||||
|
||||
scope.onAppEvent('dashboard-saved', function() {
|
||||
if ($rootScope.fullscreen) {
|
||||
scope.emitAppEvent('panel-fullscreen-exit');
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
keyboardManager.unbind('ctrl+f');
|
||||
keyboardManager.unbind('ctrl+h');
|
||||
keyboardManager.unbind('ctrl+s');
|
||||
keyboardManager.unbind('ctrl+r');
|
||||
keyboardManager.unbind('ctrl+z');
|
||||
keyboardManager.unbind('esc');
|
||||
});
|
||||
keyboardManager.unbind('esc');
|
||||
|
||||
keyboardManager.bind('ctrl+f', function(evt) {
|
||||
scope.emitAppEvent('open-search', evt);
|
||||
@@ -67,7 +53,7 @@ function(angular, $) {
|
||||
modalData.$scope.dismiss();
|
||||
}
|
||||
|
||||
scope.emitAppEvent('panel-fullscreen-exit');
|
||||
scope.exitFullscreen();
|
||||
}, { inputDisabled: true });
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,14 +3,15 @@ define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
'lodash',
|
||||
'moment',
|
||||
'../timer',
|
||||
],
|
||||
function (angular, $, kbn, _) {
|
||||
function (angular, $, kbn, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('dashboardSrv', function(timer, $rootScope, $timeout) {
|
||||
module.factory('dashboardSrv', function(timer, $rootScope, $timeout) {
|
||||
|
||||
function DashboardModel (data) {
|
||||
|
||||
@@ -29,6 +30,8 @@ function (angular, $, kbn, _) {
|
||||
this.time = data.time || { from: 'now-6h', to: 'now' };
|
||||
this.templating = data.templating || { list: [] };
|
||||
this.refresh = data.refresh;
|
||||
this.version = data.version || 0;
|
||||
this.$state = data.$state;
|
||||
|
||||
if (this.nav.length === 0) {
|
||||
this.nav.push({ type: 'timepicker' });
|
||||
@@ -47,6 +50,73 @@ function (angular, $, kbn, _) {
|
||||
|
||||
var p = DashboardModel.prototype;
|
||||
|
||||
p.getNextPanelId = function() {
|
||||
var i, j, row, panel, max = 0;
|
||||
for (i = 0; i < this.rows.length; i++) {
|
||||
row = this.rows[i];
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
panel = row.panels[j];
|
||||
if (panel.id > max) { max = panel.id; }
|
||||
}
|
||||
}
|
||||
return max + 1;
|
||||
};
|
||||
|
||||
p.rowSpan = function(row) {
|
||||
return _.reduce(row.panels, function(p,v) {
|
||||
return p + v.span;
|
||||
},0);
|
||||
};
|
||||
|
||||
p.add_panel = function(panel, row) {
|
||||
var rowSpan = this.rowSpan(row);
|
||||
var panelCount = row.panels.length;
|
||||
var space = (12 - rowSpan) - panel.span;
|
||||
panel.id = this.getNextPanelId();
|
||||
|
||||
// try to make room of there is no space left
|
||||
if (space <= 0) {
|
||||
if (panelCount === 1) {
|
||||
row.panels[0].span = 6;
|
||||
panel.span = 6;
|
||||
}
|
||||
else if (panelCount === 2) {
|
||||
row.panels[0].span = 4;
|
||||
row.panels[1].span = 4;
|
||||
panel.span = 4;
|
||||
}
|
||||
}
|
||||
|
||||
row.panels.push(panel);
|
||||
};
|
||||
|
||||
p.duplicatePanel = function(panel, row) {
|
||||
var rowIndex = _.indexOf(this.rows, row);
|
||||
var newPanel = angular.copy(panel);
|
||||
newPanel.id = this.getNextPanelId();
|
||||
|
||||
while(rowIndex < this.rows.length) {
|
||||
var currentRow = this.rows[rowIndex];
|
||||
if (this.rowSpan(currentRow) <= 9) {
|
||||
currentRow.panels.push(newPanel);
|
||||
return;
|
||||
}
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
var newRow = angular.copy(row);
|
||||
newRow.panels = [newPanel];
|
||||
this.rows.push(newRow);
|
||||
};
|
||||
|
||||
p.formatDate = function(date, format) {
|
||||
format = format || 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
return this.timezone === 'browser' ?
|
||||
moment(date).format(format) :
|
||||
moment.utc(date).format(format);
|
||||
};
|
||||
|
||||
p.emit_refresh = function() {
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
@@ -74,61 +144,97 @@ function (angular, $, kbn, _) {
|
||||
};
|
||||
|
||||
p.updateSchema = function(old) {
|
||||
var i, j, row, panel;
|
||||
var isChanged = false;
|
||||
var oldVersion = this.version;
|
||||
var panelUpgrades = [];
|
||||
this.version = 4;
|
||||
|
||||
if (this.version === 2) {
|
||||
if (oldVersion === 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (old.services) {
|
||||
if (old.services.filter) {
|
||||
this.time = old.services.filter.time;
|
||||
this.templating.list = old.services.filter.list;
|
||||
}
|
||||
delete this.services;
|
||||
}
|
||||
// version 2 schema changes
|
||||
if (oldVersion < 2) {
|
||||
|
||||
for (i = 0; i < this.rows.length; i++) {
|
||||
row = this.rows[i];
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
panel = row.panels[j];
|
||||
if (old.services) {
|
||||
if (old.services.filter) {
|
||||
this.time = old.services.filter.time;
|
||||
this.templating.list = old.services.filter.list;
|
||||
}
|
||||
delete this.services;
|
||||
}
|
||||
|
||||
panelUpgrades.push(function(panel) {
|
||||
// rename panel type
|
||||
if (panel.type === 'graphite') {
|
||||
panel.type = 'graph';
|
||||
isChanged = true;
|
||||
}
|
||||
|
||||
if (panel.type === 'graph') {
|
||||
if (_.isBoolean(panel.legend)) {
|
||||
panel.legend = { show: panel.legend };
|
||||
if (panel.type !== 'graph') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; }
|
||||
|
||||
if (panel.grid) {
|
||||
if (panel.grid.min) {
|
||||
panel.grid.leftMin = panel.grid.min;
|
||||
delete panel.grid.min;
|
||||
}
|
||||
|
||||
if (panel.grid) {
|
||||
if (panel.grid.min) {
|
||||
panel.grid.leftMin = panel.grid.min;
|
||||
delete panel.grid.min;
|
||||
}
|
||||
|
||||
if (panel.grid.max) {
|
||||
panel.grid.leftMax = panel.grid.max;
|
||||
delete panel.grid.max;
|
||||
}
|
||||
if (panel.grid.max) {
|
||||
panel.grid.leftMax = panel.grid.max;
|
||||
delete panel.grid.max;
|
||||
}
|
||||
}
|
||||
|
||||
if (panel.y_format) {
|
||||
panel.y_formats[0] = panel.y_format;
|
||||
delete panel.y_format;
|
||||
}
|
||||
if (panel.y_format) {
|
||||
panel.y_formats[0] = panel.y_format;
|
||||
delete panel.y_format;
|
||||
}
|
||||
|
||||
if (panel.y2_format) {
|
||||
panel.y_formats[1] = panel.y2_format;
|
||||
delete panel.y2_format;
|
||||
}
|
||||
if (panel.y2_format) {
|
||||
panel.y_formats[1] = panel.y2_format;
|
||||
delete panel.y2_format;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// schema version 3 changes
|
||||
if (oldVersion < 3) {
|
||||
// ensure panel ids
|
||||
var maxId = this.getNextPanelId();
|
||||
panelUpgrades.push(function(panel) {
|
||||
if (!panel.id) {
|
||||
panel.id = maxId;
|
||||
maxId += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// schema version 4 changes
|
||||
if (oldVersion < 4) {
|
||||
// move aliasYAxis changes
|
||||
panelUpgrades.push(function(panel) {
|
||||
if (panel.type !== 'graph') { return; }
|
||||
_.each(panel.aliasYAxis, function(value, key) {
|
||||
panel.seriesOverrides = [{ alias: key, yaxis: value }];
|
||||
});
|
||||
delete panel.aliasYAxis;
|
||||
});
|
||||
}
|
||||
|
||||
if (panelUpgrades.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.rows.length; i++) {
|
||||
var row = this.rows[i];
|
||||
for (var j = 0; j < row.panels.length; j++) {
|
||||
for (var k = 0; k < panelUpgrades.length; k++) {
|
||||
panelUpgrades[k](row.panels[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.version = 2;
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('dashboardViewStateSrv', function($location, $timeout) {
|
||||
|
||||
// represents the transient view state
|
||||
// like fullscreen panel & edit
|
||||
function DashboardViewState($scope) {
|
||||
var self = this;
|
||||
|
||||
$scope.exitFullscreen = function() {
|
||||
self.update({ fullscreen: false });
|
||||
};
|
||||
|
||||
$scope.onAppEvent('dashboard-saved', function() {
|
||||
self.update({ fullscreen: false });
|
||||
});
|
||||
|
||||
$scope.onAppEvent('$routeUpdate', function() {
|
||||
var urlState = self.getQueryStringState();
|
||||
if (self.needsSync(urlState)) {
|
||||
self.update(urlState, true);
|
||||
}
|
||||
});
|
||||
|
||||
this.panelScopes = [];
|
||||
this.$scope = $scope;
|
||||
|
||||
this.update(this.getQueryStringState(), true);
|
||||
}
|
||||
|
||||
DashboardViewState.prototype.needsSync = function(urlState) {
|
||||
if (urlState.fullscreen !== this.fullscreen) { return true; }
|
||||
if (urlState.edit !== this.edit) { return true; }
|
||||
if (urlState.panelId !== this.panelId) { return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.getQueryStringState = function() {
|
||||
var queryParams = $location.search();
|
||||
return {
|
||||
panelId: parseInt(queryParams.panelId) || null,
|
||||
fullscreen: queryParams.fullscreen ? true : false,
|
||||
edit: queryParams.edit ? true : false
|
||||
};
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.update = function(state, skipUrlSync) {
|
||||
_.extend(this, state);
|
||||
|
||||
if (!this.fullscreen) {
|
||||
this.panelId = null;
|
||||
this.edit = false;
|
||||
}
|
||||
|
||||
if (!skipUrlSync) {
|
||||
$location.search({
|
||||
fullscreen: this.fullscreen ? true : null,
|
||||
panelId: this.panelId,
|
||||
edit: this.edit ? true : null
|
||||
});
|
||||
}
|
||||
|
||||
this.syncState();
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.syncState = function() {
|
||||
if (this.panelScopes.length === 0) { return; }
|
||||
|
||||
if (this.fullscreen) {
|
||||
if (this.fullscreenPanel) {
|
||||
this.leaveFullscreen(false);
|
||||
}
|
||||
var panelScope = this.getPanelScope(this.panelId);
|
||||
this.enterFullscreen(panelScope);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fullscreenPanel) {
|
||||
this.leaveFullscreen(true);
|
||||
}
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.getPanelScope = function(id) {
|
||||
return _.find(this.panelScopes, function(panelScope) {
|
||||
return panelScope.panel.id === id;
|
||||
});
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.leaveFullscreen = function(render) {
|
||||
var self = this;
|
||||
|
||||
self.fullscreenPanel.editMode = false;
|
||||
self.fullscreenPanel.fullscreen = false;
|
||||
delete self.fullscreenPanel.height;
|
||||
|
||||
if (!render) { return false;}
|
||||
|
||||
$timeout(function() {
|
||||
if (self.oldTimeRange !== self.fullscreenPanel.range) {
|
||||
self.$scope.dashboard.emit_refresh();
|
||||
}
|
||||
else {
|
||||
self.fullscreenPanel.$emit('render');
|
||||
}
|
||||
delete self.fullscreenPanel;
|
||||
});
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.enterFullscreen = function(panelScope) {
|
||||
var docHeight = $(window).height();
|
||||
var editHeight = Math.floor(docHeight * 0.3);
|
||||
var fullscreenHeight = Math.floor(docHeight * 0.7);
|
||||
this.oldTimeRange = panelScope.range;
|
||||
|
||||
panelScope.height = this.edit ? editHeight : fullscreenHeight;
|
||||
panelScope.editMode = this.edit;
|
||||
this.fullscreenPanel = panelScope;
|
||||
|
||||
$(window).scrollTop(0);
|
||||
|
||||
panelScope.fullscreen = true;
|
||||
|
||||
$timeout(function() {
|
||||
panelScope.$emit('render');
|
||||
});
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.registerPanel = function(panelScope) {
|
||||
var self = this;
|
||||
self.panelScopes.push(panelScope);
|
||||
|
||||
if (self.panelId === panelScope.panel.id) {
|
||||
self.enterFullscreen(panelScope);
|
||||
}
|
||||
|
||||
panelScope.$on('$destroy', function() {
|
||||
self.panelScopes = _.without(self.panelScopes, panelScope);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
create: function($scope) {
|
||||
return new DashboardViewState($scope);
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
@@ -68,6 +68,8 @@ function (angular, _, config) {
|
||||
case 'grafana':
|
||||
Datasource = $injector.get('GrafanaDatasource');
|
||||
break;
|
||||
default:
|
||||
Datasource = $injector.get(ds.type);
|
||||
}
|
||||
return new Datasource(ds);
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
|
||||
if (rounding === 'round-up') {
|
||||
if (date.get('s')) {
|
||||
date.add('m', 1);
|
||||
date.add(1, 'm');
|
||||
}
|
||||
}
|
||||
else if (rounding === 'round-down') {
|
||||
@@ -159,7 +159,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
// to guarantee that we get all the data that
|
||||
// exists for the specified range
|
||||
if (date.get('s')) {
|
||||
date.subtract('m', 1);
|
||||
date.subtract(1, 'm');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,9 @@ define([
|
||||
i === 45 || // -
|
||||
i === 42 || // *
|
||||
i === 58 || // :
|
||||
i === 91 || // templateStart [
|
||||
i === 93 || // templateEnd ]
|
||||
i === 63 || // ?
|
||||
i === 37 || // %
|
||||
i >= 97 && i <= 122; // a-z
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, _, $) {
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
@@ -22,12 +21,12 @@ function (angular, _, $) {
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
click: "toggleFullscreenEdit()",
|
||||
click: "toggleFullscreen(true)",
|
||||
condition: $scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: "Fullscreen",
|
||||
click: 'toggleFullscreen()',
|
||||
click: 'toggleFullscreen(false)',
|
||||
condition: $scope.panelMeta.fullscreenView
|
||||
},
|
||||
{
|
||||
@@ -71,46 +70,6 @@ function (angular, _, $) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.enterFullscreenMode = function(options) {
|
||||
var docHeight = $(window).height();
|
||||
var editHeight = Math.floor(docHeight * 0.3);
|
||||
var fullscreenHeight = Math.floor(docHeight * 0.7);
|
||||
var oldTimeRange = $scope.range;
|
||||
|
||||
$scope.height = options.edit ? editHeight : fullscreenHeight;
|
||||
$scope.editMode = options.edit;
|
||||
|
||||
if (!$scope.fullscreen) {
|
||||
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
|
||||
$scope.editMode = false;
|
||||
$scope.fullscreen = false;
|
||||
delete $scope.height;
|
||||
|
||||
closeEditMode();
|
||||
|
||||
$timeout(function() {
|
||||
if (oldTimeRange !== $scope.range) {
|
||||
$scope.dashboard.emit_refresh();
|
||||
}
|
||||
else {
|
||||
$scope.$emit('render');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(window).scrollTop(0);
|
||||
|
||||
$scope.fullscreen = true;
|
||||
|
||||
$rootScope.$emit('panel-fullscreen-enter');
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.addDataQuery = function() {
|
||||
$scope.panel.targets.push({target: ''});
|
||||
};
|
||||
@@ -135,22 +94,12 @@ function (angular, _, $) {
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.toggleFullscreenEdit = function() {
|
||||
if ($scope.editMode) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({edit: true});
|
||||
$scope.toggleFullscreen = function(edit) {
|
||||
$scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
|
||||
};
|
||||
|
||||
$scope.toggleFullscreen = function() {
|
||||
if ($scope.fullscreen && !$scope.editMode) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.enterFullscreenMode({ edit: false });
|
||||
$scope.otherPanelInFullscreenMode = function() {
|
||||
return $scope.dashboardViewState.fullscreen && !$scope.fullscreen;
|
||||
};
|
||||
|
||||
// Post init phase
|
||||
@@ -162,6 +111,31 @@ function (angular, _, $) {
|
||||
|
||||
$scope.datasources = datasourceSrv.getMetricSources();
|
||||
$scope.setDatasource($scope.panel.datasource);
|
||||
|
||||
$scope.dashboardViewState.registerPanel($scope);
|
||||
|
||||
if ($scope.get_data) {
|
||||
var panel_get_data = $scope.get_data;
|
||||
$scope.get_data = function() {
|
||||
if ($scope.otherPanelInFullscreenMode()) { return; }
|
||||
|
||||
delete $scope.panel.error;
|
||||
$scope.panelMeta.loading = true;
|
||||
|
||||
panel_get_data();
|
||||
};
|
||||
|
||||
if (!$scope.skipDataOnInit) {
|
||||
$scope.get_data();
|
||||
}
|
||||
}
|
||||
|
||||
if ($rootScope.profilingEnabled) {
|
||||
$rootScope.performance.panelsInitialized++;
|
||||
if ($rootScope.performance.panelsInitialized === $scope.dashboard.rows.length) {
|
||||
$rootScope.performance.allPanelsInitialized = new Date().getTime();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'kbn'
|
||||
'kbn',
|
||||
'store'
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
function (angular, _, kbn, store) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
@@ -13,14 +14,14 @@ function (angular, _, kbn) {
|
||||
var favorites = { dashboards: [] };
|
||||
|
||||
this.init = function() {
|
||||
var existingJson = window.localStorage["grafana-favorites"];
|
||||
var existingJson = store.get("grafana-favorites");
|
||||
if (existingJson) {
|
||||
favorites = angular.fromJson(existingJson);
|
||||
}
|
||||
};
|
||||
|
||||
this._save = function() {
|
||||
window.localStorage["grafana-favorites"] = angular.toJson(favorites);
|
||||
store.set('grafana-favorites', angular.toJson(favorites));
|
||||
};
|
||||
|
||||
this._find = function(title) {
|
||||
@@ -68,6 +69,7 @@ function (angular, _, kbn) {
|
||||
timerInstance = setInterval(function() {
|
||||
$rootScope.$apply(function() {
|
||||
angular.element(window).unbind('resize');
|
||||
$location.search({});
|
||||
$location.path(dashboards[index % dashboards.length].url);
|
||||
index++;
|
||||
});
|
||||
|
||||
@@ -28,10 +28,12 @@ function(angular, _, config) {
|
||||
$rootScope.$on("dashboard-saved", function(event, savedDashboard) {
|
||||
self.original = angular.copy(savedDashboard);
|
||||
self.current = savedDashboard;
|
||||
self.orignalPath = $location.path();
|
||||
});
|
||||
|
||||
$rootScope.$on("$routeChangeSuccess", function() {
|
||||
self.original = null;
|
||||
self.originalPath = $location.path();
|
||||
});
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
@@ -42,6 +44,10 @@ function(angular, _, config) {
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.$on("$locationChangeStart", function(event, next) {
|
||||
if (self.originalPath === $location.path()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.has_unsaved_changes()) {
|
||||
event.preventDefault();
|
||||
self.next = next;
|
||||
|
||||
@@ -96,7 +96,11 @@ function (Settings) {
|
||||
|
||||
// Add your own custom pannels
|
||||
plugins: {
|
||||
panels: []
|
||||
// list of plugin panels
|
||||
panels: [],
|
||||
// requirejs modules in plugins folder that should be loaded
|
||||
// for example custom datasources
|
||||
dependencies: [],
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Vendored
-9
File diff suppressed because one or more lines are too long
Vendored
-9
File diff suppressed because one or more lines are too long
+44
-12
@@ -35,9 +35,8 @@
|
||||
// Search
|
||||
|
||||
.grafana-search-panel {
|
||||
padding: 6px 10px;
|
||||
|
||||
.search-field-wrapper {
|
||||
padding: 6px 10px;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -50,25 +49,58 @@
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected td, tr.selected:nth-child(odd)>td {
|
||||
background: @blue;
|
||||
color: white;
|
||||
text-shadow: -1px -1px 1px rgba(0,0,0,0.3);
|
||||
a {
|
||||
color: white;
|
||||
.search-results-container {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
.search-result-item a {
|
||||
}
|
||||
|
||||
.search-result-item:hover, .search-result-item.selected {
|
||||
.search-result-link, .icon {
|
||||
color: @grafanaListHighlight;
|
||||
}
|
||||
.search-result-link .label {
|
||||
background-color: @blue;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-tag .label-tag {
|
||||
background-color: @blue;
|
||||
|
||||
.search-result-link {
|
||||
color: @grafanaListMainLinkColor;
|
||||
.icon {
|
||||
padding-right: 10px;
|
||||
color: @grafanaListHighlightContrast;
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-item:nth-child(odd) {
|
||||
background-color: @grafanaListAccent;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 6px 10px;
|
||||
white-space: nowrap;
|
||||
border-top: 1px solid @grafanaListBorderTop;
|
||||
border-bottom: 1px solid @grafanaListBorderBottom;
|
||||
}
|
||||
|
||||
.search-result-tags {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.search-result-actions {
|
||||
float: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-tagview-switch {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 263px;
|
||||
right: 266px;
|
||||
color: darken(@linkColor, 30%);
|
||||
&.active {
|
||||
color: @linkColor;
|
||||
@@ -155,7 +187,7 @@
|
||||
}
|
||||
|
||||
.panel-full-edit-tabs {
|
||||
margin-top: 10px;
|
||||
margin-top: 30px;
|
||||
min-height: 250px;
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
|
||||
+17
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
.graph-legend {
|
||||
margin: 0 20px;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
.graph-legend-value {
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
&.current:before {
|
||||
content: "Current: "
|
||||
}
|
||||
@@ -44,6 +45,7 @@
|
||||
|
||||
.graph-legend-series {
|
||||
padding-left: 10px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.graph-legend-value {
|
||||
@@ -152,3 +154,17 @@
|
||||
.annotation-tags {
|
||||
color: @purple;
|
||||
}
|
||||
|
||||
.graph-series-override {
|
||||
input {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.graph-series-override-option {
|
||||
float: left;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
.graph-series-override-selector {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
@linkColor: darken(@white,5%);
|
||||
@linkColor: darken(@white,11%);
|
||||
@linkColorHover: @white;
|
||||
|
||||
|
||||
@@ -93,6 +93,14 @@
|
||||
@borderRadiusLarge: 4px;
|
||||
@borderRadiusSmall: 2px;
|
||||
|
||||
// Lists
|
||||
@grafanaListBackground: transparent;
|
||||
@grafanaListAccent: #232323;
|
||||
@grafanaListBorderTop: #3E3E3E;
|
||||
@grafanaListBorderBottom: #1c1919;
|
||||
@grafanaListHighlight: @blue;
|
||||
@grafanaListHighlightContrast: #4F4F4F;
|
||||
@grafanaListMainLinkColor: @linkColor;
|
||||
|
||||
// Tables
|
||||
// -------------------------
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
// Accent colors
|
||||
// -------------------------
|
||||
@blue: #01A6E6;
|
||||
@blue: #007FFF;
|
||||
@blueDark: #75CAEB;
|
||||
@green: #28B62C;
|
||||
@red: #FF4136;
|
||||
@@ -97,6 +97,15 @@
|
||||
@borderRadiusLarge: 4px;
|
||||
@borderRadiusSmall: 2px;
|
||||
|
||||
// Lists
|
||||
@grafanaListBackground: transparent;
|
||||
@grafanaListAccent: #f9f9f9;
|
||||
@grafanaListBorderTop: #eee;
|
||||
@grafanaListBorderBottom: #efefef;
|
||||
@grafanaListHighlight: @blue;
|
||||
@grafanaListHighlightContrast: #ddd;
|
||||
@grafanaListMainLinkColor: @textColor;
|
||||
|
||||
|
||||
// Tables
|
||||
// -------------------------
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
<strong>{{alert.title}}</strong> <span ng-bind-html='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
|
||||
</div>
|
||||
|
||||
<div ng-view ng-class="{'dashboard-fullscreen': fullscreen}"></div>
|
||||
<div ng-view></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'kbn',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('CustomDatasource', function($q) {
|
||||
|
||||
// the datasource object passed to constructor
|
||||
// is the same defined in config.js
|
||||
function CustomDatasource(datasource) {
|
||||
this.name = datasource.name;
|
||||
this.supportMetrics = true;
|
||||
this.url = datasource.url;
|
||||
}
|
||||
|
||||
CustomDatasource.prototype.query = function(filterSrv, options) {
|
||||
// get from & to in seconds
|
||||
var from = kbn.parseDate(options.range.from).getTime() / 1000;
|
||||
var to = kbn.parseDate(options.range.to).getTime() / 1000;
|
||||
|
||||
var series = [];
|
||||
var stepInSeconds = (to - from) / options.maxDataPoints;
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var walker = Math.random() * 100;
|
||||
var time = from;
|
||||
var timeSeries = {
|
||||
target: "Series " + i,
|
||||
datapoints: []
|
||||
};
|
||||
|
||||
for (var j = 0; j < options.maxDataPoints; j++) {
|
||||
timeSeries.datapoints[j] = [walker, time];
|
||||
walker += Math.random() - 0.5;
|
||||
time += stepInSeconds;
|
||||
}
|
||||
|
||||
series.push(timeSeries);
|
||||
}
|
||||
|
||||
return $q.when({data: series });
|
||||
|
||||
};
|
||||
|
||||
return CustomDatasource;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -7,7 +7,6 @@ define([
|
||||
var model;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
|
||||
beforeEach(inject(function(dashboardSrv) {
|
||||
model = dashboardSrv.create({});
|
||||
}));
|
||||
@@ -24,12 +23,71 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('when getting next panel id', function() {
|
||||
var model;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(inject(function(dashboardSrv) {
|
||||
model = dashboardSrv.create({
|
||||
rows: [{ panels: [{ id: 5 }]}]
|
||||
});
|
||||
}));
|
||||
|
||||
it('should return max id + 1', function() {
|
||||
expect(model.getNextPanelId()).to.be(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('row and panel manipulation', function() {
|
||||
var dashboard;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(inject(function(dashboardSrv) {
|
||||
dashboard = dashboardSrv.create({});
|
||||
}));
|
||||
|
||||
it('row span should sum spans', function() {
|
||||
var spanLeft = dashboard.rowSpan({ panels: [{ span: 2 }, { span: 3 }] });
|
||||
expect(spanLeft).to.be(5);
|
||||
});
|
||||
|
||||
it('adding default should split span in half', function() {
|
||||
dashboard.rows = [{ panels: [{ span: 12, id: 7 }] }];
|
||||
dashboard.add_panel({span: 4}, dashboard.rows[0]);
|
||||
|
||||
expect(dashboard.rows[0].panels[0].span).to.be(6);
|
||||
expect(dashboard.rows[0].panels[1].span).to.be(6);
|
||||
expect(dashboard.rows[0].panels[1].id).to.be(8);
|
||||
});
|
||||
|
||||
it('duplicate panel should try to add it to same row', function() {
|
||||
var panel = { span: 4, attr: '123', id: 10 };
|
||||
dashboard.rows = [{ panels: [panel] }];
|
||||
dashboard.duplicatePanel(panel, dashboard.rows[0]);
|
||||
|
||||
expect(dashboard.rows[0].panels[0].span).to.be(4);
|
||||
expect(dashboard.rows[0].panels[1].span).to.be(4);
|
||||
expect(dashboard.rows[0].panels[1].attr).to.be('123');
|
||||
expect(dashboard.rows[0].panels[1].id).to.be(11);
|
||||
});
|
||||
|
||||
it('duplicate should add row if there is no space left', function() {
|
||||
var panel = { span: 12, attr: '123' };
|
||||
dashboard.rows = [{ panels: [panel] }];
|
||||
dashboard.duplicatePanel(panel, dashboard.rows[0]);
|
||||
|
||||
expect(dashboard.rows[0].panels[0].span).to.be(12);
|
||||
expect(dashboard.rows[0].panels.length).to.be(1);
|
||||
expect(dashboard.rows[1].panels[0].attr).to.be('123');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when creating dashboard with old schema', function() {
|
||||
var model;
|
||||
var graph;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
|
||||
beforeEach(inject(function(dashboardSrv) {
|
||||
model = dashboardSrv.create({
|
||||
services: { filter: { time: { from: 'now-1d', to: 'now'}, list: [1] }},
|
||||
@@ -39,6 +97,7 @@ define([
|
||||
{
|
||||
type: 'graphite',
|
||||
legend: true,
|
||||
aliasYAxis: { test: 2 },
|
||||
grid: { min: 1, max: 10 }
|
||||
}
|
||||
]
|
||||
@@ -54,6 +113,10 @@ define([
|
||||
expect(model.title).to.be('No Title');
|
||||
});
|
||||
|
||||
it('should have panel id', function() {
|
||||
expect(graph.id).to.be(1);
|
||||
});
|
||||
|
||||
it('should move time and filtering list', function() {
|
||||
expect(model.time.from).to.be('now-1d');
|
||||
expect(model.templating.list[0]).to.be(1);
|
||||
@@ -72,11 +135,15 @@ define([
|
||||
expect(graph.grid.leftMax).to.be(10);
|
||||
});
|
||||
|
||||
it('move aliasYAxis to series override', function() {
|
||||
expect(graph.seriesOverrides[0].alias).to.be("test");
|
||||
expect(graph.seriesOverrides[0].yaxis).to.be(2);
|
||||
});
|
||||
|
||||
it('dashboard schema version should be set to latest', function() {
|
||||
expect(model.version).to.be(2);
|
||||
expect(model.version).to.be(4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
define([
|
||||
'services/dashboard/dashboardViewStateSrv'
|
||||
], function() {
|
||||
'use strict';
|
||||
|
||||
describe('when updating view state', function() {
|
||||
var viewState, location;
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
|
||||
beforeEach(inject(function(dashboardViewStateSrv, $location, $rootScope) {
|
||||
$rootScope.onAppEvent = function(){};
|
||||
viewState = dashboardViewStateSrv.create($rootScope);
|
||||
location = $location;
|
||||
}));
|
||||
|
||||
describe('to fullscreen true and edit true', function() {
|
||||
it('should update querystring and view state', function() {
|
||||
var updateState = { fullscreen: true, edit: true, panelId: 1 };
|
||||
viewState.update(updateState);
|
||||
expect(location.search()).to.eql(updateState);
|
||||
expect(viewState.fullscreen).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('to fullscreen false', function() {
|
||||
it('should remove params from query string', function() {
|
||||
viewState.update({fullscreen: true, panelId: 1, edit: true});
|
||||
viewState.update({fullscreen: false});
|
||||
expect(location.search()).to.eql({});
|
||||
expect(viewState.fullscreen).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'angular',
|
||||
'jquery',
|
||||
'components/timeSeries',
|
||||
'directives/grafanaGraph'
|
||||
], function(helpers, angular, $, TimeSeries) {
|
||||
'use strict';
|
||||
|
||||
describe('grafanaGraph', function() {
|
||||
|
||||
beforeEach(module('grafana.directives'));
|
||||
|
||||
function graphScenario(desc, func) {
|
||||
describe(desc, function() {
|
||||
var ctx = {};
|
||||
ctx.setup = function (setupFunc) {
|
||||
beforeEach(inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
var element = angular.element("<div style='width:500px' grafana-graph><div>");
|
||||
|
||||
scope.height = '200px';
|
||||
scope.panel = {
|
||||
legend: {},
|
||||
grid: {},
|
||||
y_formats: [],
|
||||
seriesOverrides: []
|
||||
};
|
||||
scope.dashboard = { timezone: 'browser' };
|
||||
scope.range = {
|
||||
from: new Date('2014-08-09 10:00:00'),
|
||||
to: new Date('2014-09-09 13:00:00')
|
||||
};
|
||||
ctx.data = [];
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
info: { alias: 'series1', enable: true }
|
||||
}));
|
||||
ctx.data.push(new TimeSeries({
|
||||
datapoints: [[1,1],[2,2]],
|
||||
info: { alias: 'series2', enable: true }
|
||||
}));
|
||||
|
||||
setupFunc(scope, ctx.data);
|
||||
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
$.plot = ctx.plotSpy = sinon.spy();
|
||||
|
||||
scope.$emit('render', ctx.data);
|
||||
ctx.plotData = ctx.plotSpy.getCall(0).args[1];
|
||||
ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
|
||||
}));
|
||||
};
|
||||
|
||||
func(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
graphScenario('simple lines options', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.linewidth = 3;
|
||||
scope.panel.steppedLine = true;
|
||||
});
|
||||
|
||||
it('should configure plot with correct options', function() {
|
||||
expect(ctx.plotOptions.series.lines.show).to.be(true);
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
|
||||
expect(ctx.plotOptions.series.lines.steps).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('grid thresholds 100, 200', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 100,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 200,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(100);
|
||||
expect(markings[0].yaxis.to).to.be(200);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(200);
|
||||
expect(markings[1].yaxis.to).to.be(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('inverted grid thresholds 200, 100', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.grid = {
|
||||
threshold1: 200,
|
||||
threshold1Color: "#111",
|
||||
threshold2: 100,
|
||||
threshold2Color: "#222",
|
||||
};
|
||||
});
|
||||
|
||||
it('should add grid markings', function() {
|
||||
var markings = ctx.plotOptions.grid.markings;
|
||||
expect(markings[0].yaxis.from).to.be(200);
|
||||
expect(markings[0].yaxis.to).to.be(100);
|
||||
expect(markings[0].color).to.be('#111');
|
||||
expect(markings[1].yaxis.from).to.be(100);
|
||||
expect(markings[1].yaxis.to).to.be(-Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('series option overrides, fill & points', function(ctx) {
|
||||
ctx.setup(function(scope, data) {
|
||||
scope.panel.lines = true;
|
||||
scope.panel.fill = 5;
|
||||
scope.panel.seriesOverrides = [
|
||||
{ alias: 'test', fill: 0, points: true }
|
||||
];
|
||||
|
||||
data[1].info.alias = 'test';
|
||||
});
|
||||
|
||||
it('should match second series and fill zero, and enable points', function() {
|
||||
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
|
||||
expect(ctx.plotData[1].lines.fill).to.be(0.001);
|
||||
expect(ctx.plotData[1].points.show).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
graphScenario('should order series order according to zindex', function(ctx) {
|
||||
ctx.setup(function(scope) {
|
||||
scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
|
||||
});
|
||||
|
||||
it('should move zindex 2 last', function() {
|
||||
expect(ctx.plotData[0].info.alias).to.be('series2');
|
||||
expect(ctx.plotData[1].info.alias).to.be('series1');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,8 @@ define([
|
||||
self.scope.panel = {};
|
||||
self.scope.row = { panels:[] };
|
||||
self.scope.filter = new FilterSrvStub();
|
||||
self.scope.dashboard = {};
|
||||
self.scope.dashboardViewState = new DashboardViewStateStub();
|
||||
|
||||
$rootScope.colors = [];
|
||||
for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); }
|
||||
@@ -54,15 +56,20 @@ define([
|
||||
};
|
||||
}
|
||||
|
||||
function DashboardViewStateStub() {
|
||||
this.registerPanel = function() {
|
||||
};
|
||||
}
|
||||
|
||||
function FilterSrvStub() {
|
||||
this.time = { from:'now-1h', to: 'now'};
|
||||
this.timeRange = function(parse) {
|
||||
if (!parse) {
|
||||
if (parse === false) {
|
||||
return this.time;
|
||||
}
|
||||
return {
|
||||
from : kbn.parseDate(this.time.from),
|
||||
to : kbn.parseDate(this.time.to)
|
||||
to : kbn.parseDate(this.time.to)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,24 @@ define([
|
||||
|
||||
});
|
||||
|
||||
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 () {
|
||||
|
||||
@@ -71,10 +71,17 @@ define([
|
||||
it('should tokenize metric with template parameter', function() {
|
||||
var lexer = new Lexer("metric.[[server]].test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('templateStart');
|
||||
expect(tokens[3].type).to.be('identifier');
|
||||
expect(tokens[3].value).to.be('server');
|
||||
expect(tokens[4].type).to.be('templateEnd');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('[[server]]');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize metric with question mark', function() {
|
||||
var lexer = new Lexer("metric.server_??.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('server_??');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should handle error with unterminated string', function() {
|
||||
|
||||
@@ -106,8 +106,8 @@ define([
|
||||
|
||||
expect(rootNode.message).to.be(undefined);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].segments[1].type).to.be('template');
|
||||
expect(rootNode.params[0].segments[1].value).to.be('server');
|
||||
expect(rootNode.params[0].segments[1].type).to.be('segment');
|
||||
expect(rootNode.params[0].segments[1].value).to.be('[[server]]');
|
||||
});
|
||||
|
||||
it('invalid metric expression', function() {
|
||||
|
||||
@@ -12,50 +12,6 @@ define([
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('RowCtrl'));
|
||||
|
||||
describe('when getting rowSpan', function() {
|
||||
it('should return sum of panels spans', function() {
|
||||
var spanLeft = ctx.scope.rowSpan({ panels: [{ span: 2 }, { span: 3 }] });
|
||||
expect(spanLeft).to.be(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when adding panel to row with 12 span panel', function() {
|
||||
it('should split span in half and add panel with defaults', function() {
|
||||
ctx.scope.row = { panels: [{ span: 12 }] };
|
||||
ctx.scope.add_panel_default('graph');
|
||||
|
||||
expect(ctx.scope.row.panels[0].span).to.be(6);
|
||||
expect(ctx.scope.row.panels[1].span).to.be(6);
|
||||
expect(ctx.scope.row.panels[1].type).to.be('graph');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when duplicating panel', function() {
|
||||
it('should try to add it to same row', function() {
|
||||
var panel = { span: 4, attr: '123' };
|
||||
ctx.scope.row = { panels: [panel] };
|
||||
ctx.scope.duplicatePanel(panel, ctx.scope.row);
|
||||
|
||||
expect(ctx.scope.row.panels[0].span).to.be(4);
|
||||
expect(ctx.scope.row.panels[1].span).to.be(4);
|
||||
expect(ctx.scope.row.panels[1].attr).to.be('123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when duplicating panel', function() {
|
||||
it('should add row if there is no space left', function() {
|
||||
var panel = { span: 12, attr: '123' };
|
||||
ctx.scope.row = { panels: [panel] };
|
||||
ctx.scope.dashboard = { rows: [ctx.scope.row] };
|
||||
|
||||
ctx.scope.duplicatePanel(panel, ctx.scope.row);
|
||||
|
||||
expect(ctx.scope.row.panels[0].span).to.be(12);
|
||||
expect(ctx.scope.row.panels.length).to.be(1);
|
||||
expect(ctx.scope.dashboard.rows[1].panels[0].attr).to.be('123');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'panels/graph/seriesOverridesCtrl'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('SeriesOverridesCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(module('grafana.panels.graph'));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('SeriesOverridesCtrl'));
|
||||
beforeEach(function() {
|
||||
ctx.scope.render = function() {};
|
||||
});
|
||||
|
||||
describe('Controller should init overrideMenu', function() {
|
||||
it('click should include option and value index', function() {
|
||||
expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('setOverride(1,1)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When setting an override', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.setOverride(1, 0);
|
||||
});
|
||||
|
||||
it('should set override property', function() {
|
||||
expect(ctx.scope.override.lines).to.be(true);
|
||||
});
|
||||
|
||||
it('should update view model', function() {
|
||||
expect(ctx.scope.currentOverrides[0].name).to.be('Lines');
|
||||
expect(ctx.scope.currentOverrides[0].value).to.be('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When removing overide', function() {
|
||||
it('click should include option and value index', function() {
|
||||
ctx.scope.setOverride(1,0);
|
||||
ctx.scope.removeOverride({ propertyName: 'lines' });
|
||||
expect(ctx.scope.currentOverrides.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
define([
|
||||
'components/timeSeries'
|
||||
], function(TimeSeries) {
|
||||
'use strict';
|
||||
|
||||
describe("TimeSeries", function() {
|
||||
var points, series;
|
||||
var yAxisFormats = ['short', 'ms'];
|
||||
var testData = {
|
||||
info: { alias: 'test' },
|
||||
datapoints: [
|
||||
[1,2],[null,3],[10,4],[8,5]
|
||||
]
|
||||
};
|
||||
|
||||
describe('when getting flot pairs', function() {
|
||||
it('with connected style, should ignore nulls', function() {
|
||||
series = new TimeSeries(testData);
|
||||
points = series.getFlotPairs('connected', yAxisFormats);
|
||||
expect(points.length).to.be(3);
|
||||
});
|
||||
|
||||
it('with null as zero style, should replace nulls with zero', function() {
|
||||
series = new TimeSeries(testData);
|
||||
points = series.getFlotPairs('null as zero', yAxisFormats);
|
||||
expect(points.length).to.be(4);
|
||||
expect(points[1][1]).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('series overrides', function() {
|
||||
var series;
|
||||
beforeEach(function() {
|
||||
series = new TimeSeries(testData);
|
||||
});
|
||||
|
||||
describe('fill & points', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]);
|
||||
});
|
||||
|
||||
it('should set fill zero, and enable points', function() {
|
||||
expect(series.lines.fill).to.be(0.001);
|
||||
expect(series.points.show).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('series option overrides, bars, true & lines false', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]);
|
||||
});
|
||||
|
||||
it('should disable lines, and enable bars', function() {
|
||||
expect(series.lines.show).to.be(false);
|
||||
expect(series.bars.show).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('series option overrides, linewidth, stack', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]);
|
||||
});
|
||||
|
||||
it('should disable stack, and set lineWidth', function() {
|
||||
expect(series.stack).to.be(false);
|
||||
expect(series.lines.lineWidth).to.be(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('series option overrides, pointradius, steppedLine', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]);
|
||||
});
|
||||
|
||||
it('should set pointradius, and set steppedLine', function() {
|
||||
expect(series.points.radius).to.be(5);
|
||||
expect(series.lines.steps).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('override match on regex', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test_01';
|
||||
series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]);
|
||||
});
|
||||
|
||||
it('should match second series', function() {
|
||||
expect(series.lines.show).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('override series y-axis, and z-index', function() {
|
||||
beforeEach(function() {
|
||||
series.info.alias = 'test';
|
||||
series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]);
|
||||
});
|
||||
|
||||
it('should set yaxis', function() {
|
||||
expect(series.info.yaxis).to.be(2);
|
||||
});
|
||||
|
||||
it('should set zindex', function() {
|
||||
expect(series.zindex).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -6,6 +6,7 @@ require.config({
|
||||
mocks: '../test/mocks',
|
||||
config: '../config.sample',
|
||||
kbn: 'components/kbn',
|
||||
store: 'components/store',
|
||||
|
||||
settings: 'components/settings',
|
||||
lodash: 'components/lodash.extended',
|
||||
@@ -26,7 +27,7 @@ require.config({
|
||||
crypto: '../vendor/crypto.min',
|
||||
spectrum: '../vendor/spectrum',
|
||||
|
||||
jquery: '../vendor/jquery/jquery-1.8.0',
|
||||
jquery: '../vendor/jquery/jquery-2.1.1.min',
|
||||
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
|
||||
@@ -117,14 +118,18 @@ require([
|
||||
'specs/lexer-specs',
|
||||
'specs/parser-specs',
|
||||
'specs/gfunc-specs',
|
||||
'specs/timeSeries-specs',
|
||||
'specs/row-ctrl-specs',
|
||||
'specs/graphiteTargetCtrl-specs',
|
||||
'specs/influxdb-datasource-specs',
|
||||
'specs/graph-ctrl-specs',
|
||||
'specs/grafanaGraph-specs',
|
||||
'specs/seriesOverridesCtrl-specs',
|
||||
'specs/filterSrv-specs',
|
||||
'specs/kbn-format-specs',
|
||||
'specs/dashboardSrv-specs',
|
||||
'specs/influxSeries-specs',
|
||||
'specs/dashboardViewStateSrv-specs',
|
||||
'specs/overview-ctrl-specs',
|
||||
], function () {
|
||||
window.__karma__.start();
|
||||
|
||||
+8
-3
@@ -257,9 +257,11 @@ var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$ti
|
||||
require: '?jqyouiDroppable',
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
var dragSettings, zIndex;
|
||||
// grafana change, remove watcher after first evaluation
|
||||
var dragSettings, zIndex, removeWatcher;
|
||||
var updateDraggable = function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
removeWatcher();
|
||||
dragSettings = scope.$eval(element.attr('jqyoui-draggable')) || [];
|
||||
element
|
||||
.draggable({disabled: false})
|
||||
@@ -283,7 +285,7 @@ var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$ti
|
||||
element.draggable({disabled: true});
|
||||
}
|
||||
};
|
||||
scope.$watch(function() { return scope.$eval(attrs.drag); }, updateDraggable);
|
||||
removeWatcher = scope.$watch(function() { return scope.$eval(attrs.drag); }, updateDraggable);
|
||||
updateDraggable();
|
||||
}
|
||||
};
|
||||
@@ -292,8 +294,11 @@ var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$ti
|
||||
restrict: 'A',
|
||||
priority: 1,
|
||||
link: function(scope, element, attrs) {
|
||||
// grafana change, remove watcher after first evaluation
|
||||
var removeWatcher;
|
||||
var updateDroppable = function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
removeWatcher();
|
||||
element
|
||||
.droppable({disabled: false})
|
||||
.droppable(scope.$eval(attrs.jqyouiOptions) || {})
|
||||
@@ -319,7 +324,7 @@ var jqyoui = angular.module('ngDragDrop', []).service('ngDragDropService', ['$ti
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch(function() { return scope.$eval(attrs.drop); }, updateDroppable);
|
||||
removeWatcher = scope.$watch(function() { return scope.$eval(attrs.drop); }, updateDroppable);
|
||||
updateDroppable();
|
||||
}
|
||||
};
|
||||
|
||||
Vendored
+2
-1
@@ -783,7 +783,8 @@ angular.module('$strap.directives').directive('bsTooltip', [
|
||||
value = newValue;
|
||||
}
|
||||
});
|
||||
if (!!attrs.unique) {
|
||||
// Grafana change, always hide other tooltips
|
||||
if (true) {
|
||||
element.on('show', function (ev) {
|
||||
$('.tooltip.in').each(function () {
|
||||
var $this = $(this), tooltip = $this.data('tooltip');
|
||||
|
||||
Vendored
+3
-2
@@ -753,7 +753,8 @@
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href')
|
||||
selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
// grafana backport fix from bootstrap 3
|
||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
||||
}
|
||||
|
||||
$parent = selector && $(selector)
|
||||
@@ -2319,4 +2320,4 @@
|
||||
})
|
||||
|
||||
|
||||
}(window.jQuery);
|
||||
}(window.jQuery);
|
||||
|
||||
Vendored
-2
File diff suppressed because one or more lines are too long
+4
File diff suppressed because one or more lines are too long
Vendored
+869
-461
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user