Merge branch 'develop'

This commit is contained in:
Torkel Ödegaard
2014-07-28 12:41:51 +02:00
66 changed files with 1222 additions and 23522 deletions
+1
View File
@@ -7,6 +7,7 @@
- [Issue #525](https://github.com/grafana/grafana/issues/525). InfluxDB: Enhanced series aliasing (legend names) with pattern replacements
- [Issue #581](https://github.com/grafana/grafana/issues/581). InfluxDB: Add continuous query in series results (series typeahead).
- [Issue #584](https://github.com/grafana/grafana/issues/584). InfluxDB: Support for alias & alias patterns when using raw query mode
- [Issue #394](https://github.com/grafana/grafana/issues/394). InfluxDB: Annotation support
- [Issue #610](https://github.com/grafana/grafana/issues/610). InfluxDB: Support for InfluxdB v0.8 list series response schemea (series typeahead)
- [Issue #604](https://github.com/grafana/grafana/issues/604). Chart: New axis format, 'bps' (SI unit in steps of 1000) useful for network gear metics
+6 -36
View File
@@ -6,15 +6,15 @@ define([
'jquery',
'underscore',
'require',
'elasticjs',
'config',
'bootstrap',
'angular-sanitize',
'angular-strap',
'angular-dragdrop',
'extend-jquery',
'bindonce'
'bindonce',
],
function (angular, $, _, appLevelRequire) {
function (angular, $, _, appLevelRequire, config) {
"use strict";
@@ -48,38 +48,9 @@ function (angular, $, _, appLevelRequire) {
return module;
};
app.safeApply = function ($scope, fn) {
switch($scope.$$phase) {
case '$apply':
// $digest hasn't started, we should be good
$scope.$eval(fn);
break;
case '$digest':
// waiting to $apply the changes
setTimeout(function () { app.safeApply($scope, fn); }, 10);
break;
default:
// clear to begin an $apply $$phase
$scope.$apply(fn);
break;
}
};
app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider
.when('/dashboard', {
templateUrl: 'app/partials/dashboard.html',
})
.when('/dashboard/:kbnType/:kbnId', {
templateUrl: 'app/partials/dashboard.html',
})
.when('/dashboard/:kbnType/:kbnId/:params', {
templateUrl: 'app/partials/dashboard.html'
})
.otherwise({
redirectTo: 'dashboard'
});
$routeProvider.otherwise({ redirectTo: config.default_route });
// this is how the internet told me to dynamically add modules :/
register_fns.controller = $controllerProvider.register;
@@ -90,7 +61,6 @@ function (angular, $, _, appLevelRequire) {
});
var apps_deps = [
'elasticjs.service',
'$strap.directives',
'ngSanitize',
'ngDragDrop',
@@ -98,7 +68,7 @@ function (angular, $, _, appLevelRequire) {
'pasvaz.bindonce'
];
var module_types = ['controllers', 'directives', 'factories', 'services', 'services.dashboard', 'filters'];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
_.each(module_types, function (type) {
var module_name = 'kibana.'+type;
@@ -114,13 +84,13 @@ function (angular, $, _, appLevelRequire) {
'directives/all',
'filters/all',
'components/partials',
'routes/all',
], function () {
// bootstrap the app
angular
.element(document)
.ready(function() {
$('body').attr('ng-controller', 'DashCtrl');
angular.bootstrap(document, apps_deps)
.invoke(['$rootScope', function ($rootScope) {
_.each(pre_boot_modules, function (module) {
+3 -14
View File
@@ -10,18 +10,6 @@ function ($) {
$.fn.place_tt = (function () {
var defaults = {
offset: 5,
css: {
position : 'absolute',
top : -1000,
left : 0,
color : "#c8c8c8",
padding : '10px',
'font-size': '11pt',
'font-weight' : 200,
'background-color': '#1f1f1f',
'border-radius': '5px',
'z-index': 9999
}
};
return function (x, y, opts) {
@@ -29,7 +17,8 @@ function ($) {
return this.each(function () {
var $tooltip = $(this), width, height;
$tooltip.css(opts.css);
$tooltip.addClass('grafana-tooltip');
if (!$.contains(document.body, $tooltip[0])) {
$tooltip.appendTo(document.body);
}
@@ -44,4 +33,4 @@ function ($) {
})();
return $;
});
});
-3
View File
@@ -41,7 +41,6 @@ require.config({
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
modernizr: '../vendor/modernizr-2.6.1',
elasticjs: '../vendor/elasticjs/elastic-angular-client',
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
@@ -101,8 +100,6 @@ require.config({
timepicker: ['jquery', 'bootstrap'],
datepicker: ['jquery', 'bootstrap'],
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
'bootstrap-tagsinput': ['jquery'],
},
waitSeconds: 60,
-1
View File
@@ -25,7 +25,6 @@ function (_, crypto) {
default_route : '/dashboard/file/default.json',
grafana_index : 'grafana-dash',
elasticsearch_all_disabled : false,
timezoneOffset : null,
playlist_timespan : "1m",
unsaved_changes_warning : true
};
+1
View File
@@ -1,4 +1,5 @@
define([
'./grafanaCtrl',
'./dash',
'./dashLoader',
'./row',
+30 -47
View File
@@ -23,7 +23,6 @@ define([
'config',
'underscore',
'services/all',
'services/dashboard/all'
],
function (angular, $, config, _) {
"use strict";
@@ -31,50 +30,40 @@ function (angular, $, config, _) {
var module = angular.module('kibana.controllers');
module.controller('DashCtrl', function(
$scope, $rootScope, $timeout, ejsResource, dashboard, filterSrv, dashboardKeybindings,
alertSrv, panelMove, keyboardManager, grafanaVersion) {
$scope, $rootScope, dashboardKeybindings, filterSrv, dashboard, panelMoveSrv, timer) {
$scope.requiredElasticSearchVersion = ">=0.90.3";
$scope.editor = {
index: 0
};
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
// For moving stuff around the dashboard.
$scope.panelMoveDrop = panelMove.onDrop;
$scope.panelMoveStart = panelMove.onStart;
$scope.panelMoveStop = panelMove.onStop;
$scope.panelMoveOver = panelMove.onOver;
$scope.panelMoveOut = panelMove.onOut;
$scope.editor = { index: 0 };
$scope.init = function() {
$scope.config = config;
// Make stuff, including underscore.js available to views
$scope._ = _;
$scope.dashboard = dashboard;
$scope.dashAlerts = alertSrv;
$scope.filter = filterSrv;
$scope.filter.init(dashboard.current);
$rootScope.$on("dashboard-loaded", function(event, dashboard) {
$scope.filter.init(dashboard);
});
// Clear existing alerts
alertSrv.clearAll();
$scope.reset_row();
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
$scope.bindKeyboardShortcuts();
$scope.availablePanels = config.panels;
$scope.onAppEvent('setup-dashboard', $scope.setupDashboard);
};
$scope.bindKeyboardShortcuts = dashboardKeybindings.shortcuts;
$scope.setupDashboard = function(event, dashboardData) {
timer.cancel_all();
$rootScope.fullscreen = false;
$scope.dashboard = dashboard.create(dashboardData);
$scope.grafana.style = $scope.dashboard.style;
$scope.filter = filterSrv;
$scope.filter.init($scope.dashboard);
var panelMove = panelMoveSrv.create($scope.dashboard);
$scope.panelMoveDrop = panelMove.onDrop;
$scope.panelMoveStart = panelMove.onStart;
$scope.panelMoveStop = panelMove.onStop;
$scope.panelMoveOver = panelMove.onOver;
$scope.panelMoveOut = panelMove.onOut;
window.document.title = 'Grafana - ' + $scope.dashboard.title;
dashboardKeybindings.shortcuts($scope);
$scope.emitAppEvent("dashboard-loaded", $scope.dashboard);
};
$scope.isPanel = function(obj) {
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
@@ -91,7 +80,7 @@ function (angular, $, config, _) {
$scope.add_row_default = function() {
$scope.reset_row();
$scope.row.title = 'New row';
$scope.add_row(dashboard.current, $scope.row);
$scope.add_row($scope.dashboard, $scope.row);
};
$scope.reset_row = function() {
@@ -131,12 +120,6 @@ function (angular, $, config, _) {
return $scope.editorTabs;
};
// This is whoafully incomplete, but will do for now
$scope.parse_error = function(data) {
var _error = data.match("nested: (.*?);");
return _.isNull(_error) ? data : _error[1];
};
$scope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
+52 -77
View File
@@ -1,135 +1,106 @@
define([
'angular',
'underscore',
'moment'
'moment',
'filesaver'
],
function (angular, _, moment) {
'use strict';
var module = angular.module('kibana.controllers');
module.controller('dashLoader', function($scope, $rootScope, $http, dashboard, alertSrv, $location, playlistSrv) {
$scope.loader = dashboard.current.loader;
module.controller('dashLoader', function($scope, $rootScope, $http, alertSrv, $location, playlistSrv, elastic) {
$scope.init = function() {
$scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
$scope.gist = $scope.gist || {};
$scope.elasticsearch = $scope.elasticsearch || {};
$rootScope.$on('save-dashboard', function() {
$scope.elasticsearch_save('dashboard', false);
$scope.onAppEvent('save-dashboard', function() {
$scope.saveDashboard();
});
$rootScope.$on('zoom-out', function() {
$scope.onAppEvent('zoom-out', function() {
$scope.zoom(2);
});
};
$scope.exitFullscreen = function() {
$rootScope.$emit('panel-fullscreen-exit');
$scope.emitAppEvent('panel-fullscreen-exit');
};
$scope.showDropdown = function(type) {
if(_.isUndefined(dashboard.current.loader)) {
if(_.isUndefined($scope.dashboard)) {
return true;
}
var _l = dashboard.current.loader;
var _l = $scope.dashboard.loader;
if(type === 'load') {
return (_l.load_elasticsearch || _l.load_gist || _l.load_local);
return (_l.load_elasticsearch || _l.load_gist);
}
if(type === 'save') {
return (_l.save_elasticsearch || _l.save_gist || _l.save_local || _l.save_default);
}
if(type === 'share') {
return (_l.save_temp);
return (_l.save_elasticsearch || _l.save_gist);
}
return false;
};
$scope.set_default = function() {
if(dashboard.set_default($location.path())) {
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
} else {
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
}
window.localStorage.grafanaDashboardDefault = $location.path();
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
};
$scope.purge_default = function() {
if(dashboard.purge_default()) {
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default',
'success',5000);
} else {
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
}
delete window.localStorage.grafanaDashboardDefault;
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000);
};
$scope.elasticsearch_save = function(type,ttl) {
dashboard.elasticsearch_save(type, dashboard.current.title, ttl)
$scope.saveForSharing = function() {
elastic.saveForSharing($scope.dashboard)
.then(function(result) {
if(_.isUndefined(result._id)) {
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
return;
}
alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000);
if(type === 'temp') {
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
}
$scope.share = { url: result.url, title: result.title };
$rootScope.$emit('dashboard-saved', dashboard.current);
}, function(err) {
alertSrv.set('Save for sharing failed', err, 'error',5000);
});
};
$scope.elasticsearch_delete = function(id) {
$scope.saveDashboard = function() {
elastic.saveDashboard($scope.dashboard, $scope.dashboard.title)
.then(function(result) {
alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result.title + '"','success', 5000);
$location.path(result.url);
$rootScope.$emit('dashboard-saved', $scope.dashboard);
}, function(err) {
alertSrv.set('Save failed', err, 'error',5000);
});
};
$scope.deleteDashboard = function(id) {
if (!confirm('Are you sure you want to delete dashboard?')) {
return;
}
dashboard.elasticsearch_delete(id).then(
function(result) {
if(!_.isUndefined(result)) {
if(result.found) {
alertSrv.set('Dashboard Deleted',id+' has been deleted','success',5000);
// Find the deleted dashboard in the cached list and remove it
var toDelete = _.where($scope.elasticsearch.dashboards,{_id:id})[0];
$scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,toDelete);
} else {
alertSrv.set('Dashboard Not Found','Could not find '+id+' in Elasticsearch','warning',5000);
}
} else {
alertSrv.set('Dashboard Not Deleted','An error occurred deleting the dashboard','error',5000);
}
}
);
};
$scope.save_gist = function() {
dashboard.save_gist($scope.gist.title).then(function(link) {
if (!_.isUndefined(link)) {
$scope.gist.last = link;
alertSrv.set('Gist saved','You will be able to access your exported dashboard file at '+
'<a href="'+link+'">'+link+'</a> in a moment','success');
} else {
alertSrv.set('Save failed','Gist could not be saved','error',5000);
}
elastic.deleteDashboard(id).then(function(id) {
alertSrv.set('Dashboard Deleted', id + ' has been deleted', 'success', 5000);
}, function() {
alertSrv.set('Dashboard Not Deleted', 'An error occurred deleting the dashboard', 'error', 5000);
});
};
$scope.gist_dblist = function(id) {
dashboard.gist_list(id).then(function(files) {
if (files && files.length > 0) {
$scope.gist.files = files;
} else {
alertSrv.set('Gist Failed','Could not retrieve dashboard list from gist','error',5000);
}
});
$scope.exportDashboard = function() {
var blob = new Blob([angular.toJson($scope.dashboard, true)], { type: "application/json;charset=utf-8" });
window.saveAs(blob, $scope.dashboard.title + '-' + new Date().getTime());
};
// function $scope.zoom
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
$scope.zoom = function(factor) {
var _range = this.filter.timeRange();
var _range = $scope.filter.timeRange();
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
var _center = _range.to.valueOf() - _timespan/2;
@@ -143,23 +114,27 @@ function (angular, _, moment) {
_to = Date.now();
}
this.filter.setTime({
$scope.filter.setTime({
from:moment.utc(_from).toDate(),
to:moment.utc(_to).toDate(),
});
};
$scope.styleUpdated = function() {
$scope.grafana.style = $scope.dashboard.style;
};
$scope.openSaveDropdown = function() {
$scope.isFavorite = playlistSrv.isCurrentFavorite();
$scope.isFavorite = playlistSrv.isCurrentFavorite($scope.dashboard);
};
$scope.markAsFavorite = function() {
playlistSrv.markAsFavorite();
playlistSrv.markAsFavorite($scope.dashboard);
$scope.isFavorite = true;
};
$scope.removeAsFavorite = function() {
playlistSrv.removeAsFavorite(dashboard.current);
playlistSrv.removeAsFavorite($scope.dashboard);
$scope.isFavorite = false;
};
+35
View File
@@ -0,0 +1,35 @@
define([
'angular',
'config',
'underscore',
],
function (angular, config, _) {
"use strict";
var module = angular.module('kibana.controllers');
module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) {
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
$scope.init = function() {
$scope._ = _;
$scope.dashAlerts = alertSrv;
$scope.grafana = {
style: 'dark'
};
};
$rootScope.onAppEvent = function(name, callback) {
var unbind = $rootScope.$on(name, callback);
this.$on('$destroy', unbind);
};
$rootScope.emitAppEvent = function(name, payload) {
$rootScope.$emit(name, payload);
};
$scope.init();
});
});
+3 -3
View File
@@ -8,7 +8,7 @@ function (angular, app, _) {
var module = angular.module('kibana.controllers');
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, dashboard) {
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv) {
$scope.init = function() {
console.log('hej!');
@@ -68,7 +68,7 @@ function (angular, app, _) {
currentRow = angular.copy(rowTemplate);
var newDashboard = angular.copy(dashboard.current);
var newDashboard = angular.copy($scope.dashboard);
newDashboard.rows = [];
newDashboard.title = state.name;
newDashboard.rows.push(currentRow);
@@ -96,7 +96,7 @@ function (angular, app, _) {
currentRow.panels.push(panel);
});
dashboard.dash_load(newDashboard);
$scope.dashboard.dash_load(newDashboard);
}
});
+1 -1
View File
@@ -86,7 +86,7 @@ function (angular, _, $) {
$timeout(function() {
if (oldTimeRange !== $scope.range) {
$scope.dashboard.refresh();
$scope.dashboard.emit_refresh();
}
else {
$scope.$emit('render');
+4 -4
View File
@@ -76,12 +76,12 @@ function (angular, app, _) {
$scope.delete_row = function() {
if (confirm("Are you sure you want to delete this row?")) {
$scope.dashboard.current.rows = _.without($scope.dashboard.current.rows, $scope.row);
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
}
};
$scope.move_row = function(direction) {
var rowsList = $scope.dashboard.current.rows;
var rowsList = $scope.dashboard.rows;
var rowIndex = _.indexOf(rowsList, $scope.row);
var newIndex = rowIndex + direction;
if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
@@ -116,12 +116,12 @@ function (angular, app, _) {
row.panels.push(angular.copy(panel));
}
else {
var rowsList = $scope.dashboard.current.rows;
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.current.rows.push(newRow);
$scope.dashboard.rows.push(newRow);
$scope.duplicatePanel(panel, newRow);
}
else {
+28 -44
View File
@@ -9,14 +9,14 @@ function (angular, _, config, $) {
var module = angular.module('kibana.controllers');
module.controller('SearchCtrl', function($scope, $rootScope, dashboard, $element, $location) {
module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, elastic) {
$scope.init = function() {
$scope.giveSearchFocus = 0;
$scope.selectedIndex = -1;
$scope.results = {dashboards: [], tags: [], metrics: []};
$scope.query = { query: 'title:' };
$rootScope.$on('open-search', $scope.openSearch);
$scope.onAppEvent('open-search', $scope.openSearch);
};
$scope.keyDown = function (evt) {
@@ -48,30 +48,40 @@ function (angular, _, config, $) {
}
};
$scope.searchDasboards = function(query) {
var request = $scope.ejs.Request().indices(config.grafana_index).types('dashboard');
var tagsOnly = query.indexOf('tags!:') === 0;
$scope.shareDashboard = function(title, id) {
var baseUrl = window.location.href.replace(window.location.hash,'');
$scope.share = {
title: title,
url: baseUrl + '#dashboard/elasticsearch/' + encodeURIComponent(id)
};
};
$scope.searchDasboards = function(queryString) {
var tagsOnly = queryString.indexOf('tags!:') === 0;
if (tagsOnly) {
var tagsQuery = query.substring(6, query.length);
query = 'tags:' + tagsQuery + '*';
var tagsQuery = queryString.substring(6, queryString.length);
queryString = 'tags:' + tagsQuery + '*';
}
else {
if (query.length === 0) {
query = 'title:';
if (queryString.length === 0) {
queryString = 'title:';
}
if (query[query.length - 1] !== '*') {
query += '*';
if (queryString[queryString.length - 1] !== '*') {
queryString += '*';
}
}
return request
.query($scope.ejs.QueryStringQuery(query))
.sort('_uid')
.facet($scope.ejs.TermsFacet("tags").field("tags").order('term').size(50))
.size(20).doSearch()
.then(function(results) {
var query = {
query: { query_string: { query: queryString } },
facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
size: 20,
sort: ["_uid"]
};
return elastic.post('/dashboard/_search', query)
.then(function(results) {
if(_.isUndefined(results.hits)) {
$scope.results.dashboards = [];
$scope.results.tags = [];
@@ -114,32 +124,6 @@ function (angular, _, config, $) {
$scope.searchDasboards(queryStr);
return;
}
queryStr = queryStr.substring(2, queryStr.length);
var words = queryStr.split(' ');
var query = $scope.ejs.BoolQuery();
var terms = _.map(words, function(word) {
return $scope.ejs.MatchQuery('metricPath_ng', word).boost(1.2);
});
var ngramQuery = $scope.ejs.BoolQuery();
ngramQuery.must(terms);
var fieldMatchQuery = $scope.ejs.FieldQuery('metricPath', queryStr + "*").boost(1.2);
query.should([ngramQuery, fieldMatchQuery]);
var request = $scope.ejs.Request().indices(config.grafana_index).types('metricKey');
var results = request.query(query).size(20).doSearch();
results.then(function(results) {
if (results && results.hits && results.hits.hits.length > 0) {
$scope.results.metrics = { metrics: results.hits.hits };
}
else {
$scope.results.metrics = { metric: [] };
}
});
};
$scope.openSearch = function (evt) {
@@ -153,7 +137,7 @@ function (angular, _, config, $) {
};
$scope.addMetricToCurrentDashboard = function (metricId) {
dashboard.current.rows.push({
$scope.dashboard.rows.push({
title: '',
height: '250px',
editable: true,
-4
View File
@@ -92,7 +92,6 @@
],
"editable": true,
"failover": false,
"panel_hints": true,
"style": "dark",
"pulldowns": [
{
@@ -138,15 +137,12 @@
"loader": {
"save_gist": false,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
"save_temp_ttl": "30d",
"load_gist": false,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": false,
"hide": false
},
"refresh": false
-2
View File
@@ -68,7 +68,6 @@
"loader": {
"save_gist": false,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
@@ -76,7 +75,6 @@
"load_gist": false,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": false,
"hide": false
},
"refresh": false
+12 -4
View File
@@ -15,8 +15,12 @@ function (angular, app, _) {
var lastPulldownVal;
var lastHideControlsVal;
$scope.$watch('dashboard.current.pulldowns', function() {
var panel = _.find($scope.dashboard.current.pulldowns, function(pulldown) { return pulldown.enable; });
$scope.$watch('dashboard.pulldowns', function() {
if (!$scope.dashboard) {
return;
}
var panel = _.find($scope.dashboard.pulldowns, function(pulldown) { return pulldown.enable; });
var panelEnabled = panel ? panel.enable : false;
if (lastPulldownVal !== panelEnabled) {
elem.toggleClass('submenu-controls-visible', panelEnabled);
@@ -24,8 +28,12 @@ function (angular, app, _) {
}
}, true);
$scope.$watch('dashboard.current.hideControls', function() {
var hideControls = $scope.dashboard.current.hideControls || $scope.playlist_active;
$scope.$watch('dashboard.hideControls', function() {
if (!$scope.dashboard) {
return;
}
var hideControls = $scope.dashboard.hideControls || $scope.playlist_active;
if (lastHideControlsVal !== hideControls) {
elem.toggleClass('hide-controls', hideControls);
+4 -3
View File
@@ -6,7 +6,7 @@ function (angular) {
var module = angular.module('kibana.directives');
module.directive('dashUpload', function(timer, dashboard, alertSrv) {
module.directive('dashUpload', function(timer, alertSrv) {
return {
restrict: 'A',
link: function(scope) {
@@ -14,7 +14,8 @@ function (angular) {
var files = evt.target.files; // FileList object
var readerOnload = function() {
return function(e) {
dashboard.dash_load(JSON.parse(e.target.result));
var dashboard = JSON.parse(e.target.result);
scope.emitAppEvent('setup-dashboard', dashboard);
scope.$apply();
};
};
@@ -34,4 +35,4 @@ function (angular) {
}
};
});
});
});
+4 -3
View File
@@ -10,13 +10,14 @@ function (angular, $, kbn, moment, _) {
var module = angular.module('kibana.directives');
module.directive('grafanaGraph', function($rootScope, dashboard) {
module.directive('grafanaGraph', function($rootScope) {
return {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var data, plot, annotations;
var hiddenData = {};
var dashboard = scope.dashboard;
var legendSideLastValue = null;
scope.$on('refresh',function() {
@@ -195,7 +196,7 @@ function (angular, $, kbn, moment, _) {
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.getTime();
options.xaxis = {
timezone: dashboard.current.timezone,
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: min,
@@ -354,7 +355,7 @@ function (angular, $, kbn, moment, _) {
value = kbn.getFormatFunction(format, 2)(value);
timestamp = dashboard.current.timezone === 'browser' ?
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');
$tooltip
+12 -21
View File
@@ -1,4 +1,5 @@
<div bindonce class="modal-body">
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
<div class="modal-body">
<div class="pull-right editor-title">Annotations</div>
<div class="editor-row">
@@ -29,42 +30,31 @@
<div class="editor-option">
<label class="small">Name</label>
<input type="text" class="input-medium" ng-model='currentAnnnotation.name' placeholder="name"></input>
<input type="text" class="input-medium" ng-model='currentAnnotation.name' placeholder="name"></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select ng-model="currentAnnnotation.type" ng-options="f for f in ['graphite metric', 'graphite events']"></select>
<label class="small">Datasource</label>
<select ng-model="currentDatasource" ng-options="f.name for f in datasources" ng-change="setDatasource()"></select>
</div>
<div class="editor-option">
<label class="small">Icon color</label>
<spectrum-picker ng-model="currentAnnnotation.iconColor"></spectrum-picker>
<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Icon size</label>
<select class="input-mini" ng-model="currentAnnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
</div>
<div class="editor-option">
<label class="small">Grid line</label>
<input type="checkbox" ng-model="currentAnnnotation.showLine" ng-checked="currentAnnnotation.showLine">
<input type="checkbox" ng-model="currentAnnotation.showLine" ng-checked="currentAnnotation.showLine">
</div>
<div class="editor-option">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnnotation.lineColor"></spectrum-picker>
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite metric'">
<div class="editor-option">
<label class="small">Graphite target expression</label>
<input type="text" class="span10" ng-model='currentAnnnotation.target' placeholder=""></input>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite events'">
<div class="editor-option">
<label class="small">Graphite event tags</label>
<input type="text" ng-model='currentAnnnotation.tags' placeholder=""></input>
</div>
<div ng-include src="currentDatasource.editorSrc">
</div>
</div>
@@ -73,4 +63,5 @@
<button ng-show="currentIsNew" type="button" class="btn btn-success" ng-click="add()">Add annotation</button>
<button ng-show="!currentIsNew" type="button" class="btn btn-success" ng-click="update()">Update</button>
<button type="button" class="btn btn-danger" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
</div>
</div>
</div>
+68
View File
@@ -0,0 +1,68 @@
/*
*/
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.panels.annotations', []);
app.useModule(module);
module.controller('AnnotationsEditorCtrl', function($scope, datasourceSrv, $rootScope) {
var annotationDefaults = {
name: '',
datasource: null,
showLine: true,
iconColor: '#C0C6BE',
lineColor: 'rgba(255, 96, 96, 0.592157)',
iconSize: 13,
enable: true
};
$scope.init = function() {
$scope.currentAnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
$scope.datasources = datasourceSrv.getAnnotationSources();
if ($scope.datasources.length > 0) {
$scope.currentDatasource = $scope.datasources[0];
}
};
$scope.setDatasource = function() {
$scope.currentAnnotation.datasource = $scope.currentDatasource.name;
};
$scope.edit = function(annotation) {
$scope.currentAnnotation = annotation;
$scope.currentIsNew = false;
$scope.currentDatasource = _.findWhere($scope.datasources, { name: annotation.datasource });
if (!$scope.currentDatasource) {
$scope.currentDatasource = $scope.datasources[0];
}
};
$scope.update = function() {
$scope.currentAnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.add = function() {
$scope.currentAnnotation.datasource = $scope.currentDatasource.name;
$scope.panel.annotations.push($scope.currentAnnotation);
$scope.currentAnnnotation = angular.copy(annotationDefaults);
};
$scope.hide = function (annotation) {
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};
});
});
+6 -34
View File
@@ -6,7 +6,8 @@
define([
'angular',
'app',
'underscore'
'underscore',
'./editor'
],
function (angular, app, _) {
'use strict';
@@ -14,7 +15,7 @@ function (angular, app, _) {
var module = angular.module('kibana.panels.annotations', []);
app.useModule(module);
module.controller('AnnotationsCtrl', function($scope, dashboard, $rootScope) {
module.controller('AnnotationsCtrl', function($scope, datasourceSrv, $rootScope) {
$scope.panelMeta = {
status : "Stable",
@@ -26,37 +27,7 @@ function (angular, app, _) {
annotations: []
};
var annotationDefaults = {
name: '',
type: 'graphite metric',
showLine: true,
iconColor: '#C0C6BE',
lineColor: 'rgba(255, 96, 96, 0.592157)',
iconSize: 13,
enable: true
};
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.edit = function(annotation) {
$scope.currentAnnnotation = annotation;
$scope.currentIsNew = false;
};
$scope.update = function() {
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.add = function() {
$scope.panel.annotations.push($scope.currentAnnnotation);
$scope.currentAnnnotation = angular.copy(annotationDefaults);
};
_.defaults($scope.panel, _d);
$scope.hide = function (annotation) {
annotation.enable = !annotation.enable;
@@ -64,4 +35,5 @@ function (angular, app, _) {
};
});
});
});
+1 -1
View File
@@ -43,7 +43,7 @@ function (angular, app, _) {
.then(function() {
// only refresh in the outermost call
if (!recursive) {
$scope.dashboard.refresh();
$scope.dashboard.emit_refresh();
}
});
};
+2 -2
View File
@@ -271,10 +271,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
targets: $scope.panel.targets,
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: $scope.resolution,
datasource: $scope.panel.datasource
datasource: $scope.panel.datasource,
};
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.filter, $scope.rangeUnparsed);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.filter, $scope.rangeUnparsed, $scope.dashboard);
return $scope.datasource.query($scope.filter, graphiteQuery)
.then($scope.dataHandler)
+3 -3
View File
@@ -25,7 +25,7 @@
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
</span>
<span ng-hide="filter.time">Time filter</span>
<span ng-show="dashboard.current.refresh" class="text-warning">refreshed every {{dashboard.current.refresh}} </span>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
<i class="icon-caret-down"></i>
</a>
@@ -47,8 +47,8 @@
</ul>
</li>
<li ng-show="!dashboard.current.refresh" class="grafana-menu-refresh">
<a class="icon-refresh" ng-click="dashboard.refresh()"></a>
<li ng-show="!dashboard.refresh" class="grafana-menu-refresh">
<a class="icon-refresh" ng-click="dashboard.emit_refresh()"></a>
</li>
</ul>
+12 -13
View File
@@ -16,7 +16,7 @@
</a>
</li>
<li ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"><kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel></li>
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"><kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel></li>
<li class="dropdown grafana-menu-save" ng-show="showDropdown('save')">
<a href="#" bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
@@ -25,17 +25,17 @@
<ul class="save-dashboard-dropdown dropdown-menu">
<li ng-show="dashboard.current.loader.save_elasticsearch">
<li ng-show="dashboard.loader.save_elasticsearch">
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
<input class='input-medium' ng-model="dashboard.current.title" type="text" ng-model="elasticsearch.title"/>
<button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
<input class='input-medium' ng-model="dashboard.title" type="text" ng-model="elasticsearch.title"/>
<button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
</form>
</li>
<li ng-show="dashboard.current.loader.save_default">
<li ng-show="dashboard.loader.save_default">
<a class="link" ng-click="set_default()">Save as Home</a>
</li>
<li ng-show="dashboard.current.loader.save_default">
<li ng-show="dashboard.loader.save_default">
<a class="link" ng-click="purge_default()">Reset Home</a>
</li>
<li ng-show="!isFavorite">
@@ -44,12 +44,12 @@
<li ng-show="isFavorite">
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
</li>
<li ng-show="dashboard.current.loader.save_local">
<a class="link" ng-click="dashboard.to_file()">Export dashboard</a>
<li ng-show="dashboard.loader.save_local">
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
</li>
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
<li ng-show="dashboard.current.loader.save_gist" style="margin:10px">
<li ng-show="dashboard.loader.save_gist" style="margin:10px">
<h6>Gist</h6>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
@@ -65,10 +65,9 @@
</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-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
<li class="grafana-menu-edit" ng-show="dashboard.current.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-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)'>
+1 -1
View File
@@ -4,7 +4,7 @@
</div>
<div class="modal-body">
<label>Share this dashboard with this URL</label>
<input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share = dashboard.share_link(share.title,share.type,share.id)">
<input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>
+84 -71
View File
@@ -1,53 +1,65 @@
<div class="submenu-controls">
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
<div class="submenu-panel-title">
<span class="small"><strong>{{pulldown.type}}:</strong></span>
</div>
<div class="submenu-panel-wrapper">
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
<div ng-controller="DashCtrl" body-class>
<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 class="clearfix"></div>
</div>
<div class="clearfix"></div>
<div class="container-fluid main">
<div>
<div class="grafana-container container">
<!-- Rows -->
<div class="kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
<div class="row-control">
<div class="grafana-row" style="padding:0px;margin:0px;position:relative;">
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
<span class="row-button bgWarning" config-modal="app/partials/roweditor.html" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button bgPrimary" ng-click="toggle_row(row)" ng-show="row.collapsable">
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
</div>
<div class="row-open" ng-show="!row.collapse">
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
<span class="row-tab-button">
<i class="icon-caret-right" ></i>
<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>
</div>
<div class="submenu-panel-wrapper">
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
</div>
</div>
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
<div class="container-fluid main">
<div>
<div class="grafana-container container">
<!-- Rows -->
<div class="kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.rows" ng-style="row_style(row)">
<div class="row-control">
<div class="grafana-row" style="padding:0px;margin:0px;position:relative;">
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
<span class="row-button bgWarning" config-modal="app/partials/roweditor.html" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button bgPrimary" ng-click="toggle_row(row)" ng-show="row.collapsable">
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
</div>
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
<i class="icon-th-list"></i>
</span>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="drop1">
<li class="dropdown-submenu">
<div class="row-open" ng-show="!row.collapse">
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
<span class="row-tab-button">
<i class="icon-caret-right"></i>
</span>
</div>
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
<i class="icon-th-list"></i>
</span>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="drop1">
<li class="dropdown-submenu">
<a href="javascript:void(0);">Add Panel</a>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
<li><a ng-click="add_panel_default('graph')">Graph</a></li>
<li><a ng-click="add_panel_default('text')">Text</a></li>
</ul>
</li>
<li class="dropdown-submenu">
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void(0);">Set height</a>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
<li><a ng-click="set_height('100px')">100 px</a></li>
<li><a ng-click="set_height('150px')">150 px</a></li>
<li><a ng-click="set_height('200px')">200 px</a></li>
@@ -58,53 +70,54 @@
<li><a ng-click="set_height('500px')">500 px</a></li>
<li><a ng-click="set_height('600px')">600 px</a></li>
<li><a ng-click="set_height('700px')">700 px</a></li>
</ul>
</li>
<li class="dropdown-submenu">
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void(0);">Move</a>
<ul class="dropdown-menu">
<ul class="dropdown-menu">
<li><a ng-click="move_row(-1)">Up</a></li>
<li><a ng-click="move_row(1)">Down</a></li>
</ul>
</li>
<li>
<a config-modal="app/partials/roweditor.html">Row editor</a>
</li>
<li>
<a ng-click="delete_row()">Delete row</a>
</li>
</ul>
</div>
</div>
</div>
<div style="padding-top:0px" ng-if="!row.collapse">
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':!panel.span?'100%':(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">
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
</ul>
</li>
<li>
<a config-modal="app/partials/roweditor.html">Row editor</a>
</li>
<li>
<a ng-click="delete_row()">Delete row</a>
</li>
</ul>
</div>
</div>
</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>
<div style="padding-top:0px" ng-if="!row.collapse">
<div class="clearfix"></div>
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':!panel.span?'100%':(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">
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
</div>
</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>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div>
<div ng-show='dashboard.current.editable && dashboard.current.panel_hints' class="row-fluid add-row-panel-hint">
<div ng-show='dashboard.editable' class="row-fluid add-row-panel-hint">
<div class="span12" style="text-align:right;">
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-mini">
<span><i class="icon-plus-sign"></i> ADD A ROW</span>
</span>
</div>
</div>
</div>
</div>
</div>
+25 -40
View File
@@ -4,7 +4,7 @@
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Metrics', 'Import']" data-title="{{tab}}">
</div>
<div ng-repeat="tab in dashboard.current.nav" data-title="{{tab.type}}">
<div ng-repeat="tab in dashboard.nav" data-title="{{tab.type}}">
</div>
</div>
@@ -12,21 +12,18 @@
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.current.title'></input>
<label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.title'></input>
</div>
<div class="editor-option">
<label class="small">Style</label><select class="input-small" ng-model="dashboard.current.style" ng-options="f for f in ['dark','light']"></select>
<label class="small">Theme</label><select class="input-small" ng-model="dashboard.style" ng-options="f for f in ['dark','light']" ng-change="styleUpdated()"></select>
</div>
<div class="editor-option">
<label class="small">Time correction</label>
<select ng-model="dashboard.current.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
<select ng-model="dashboard.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
</div>
<div class="editor-option">
<label class="small"> Hints <tip>Show 'Add panel' hints in empty spaces</tip></label><input type="checkbox" ng-model="dashboard.current.panel_hints" ng-checked="dashboard.current.panel_hints" />
</div>
<div class="editor-option">
<label class="small">Hide controls</label>
<input type="checkbox" ng-model="dashboard.current.hideControls" ng-checked="dashboard.current.hideControls">
<label class="small">Hide controls (CTRL+H)</label>
<input type="checkbox" ng-model="dashboard.hideControls" ng-checked="dashboard.hideControls">
</div>
</div>
</div>
@@ -34,7 +31,7 @@
<div class="section">
<div class="editor-option">
<label class="small">Tags</label>
<bootstrap-tagsinput ng-model="dashboard.current.tags" tagclass="label label-tag" placeholder="add tags">
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
<tip>Press enter to a add tag</tip>
</div>
@@ -53,10 +50,10 @@
<th width="1%"></th>
<th width="97%">Title</th>
</thead>
<tr ng-repeat="row in dashboard.current.rows">
<td><i ng-click="_.move(dashboard.current.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(dashboard.current.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td><i ng-click="dashboard.current.rows = _.without(dashboard.current.rows,row)" class="pointer icon-remove"></i></td>
<tr ng-repeat="row in dashboard.rows">
<td><i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td><i ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="pointer icon-remove"></i></td>
<td>{{row.title}}</td>
</tr>
</table>
@@ -76,43 +73,31 @@
<div class="section">
<h5>Save to</h5>
<div class="editor-option">
<label class="small">Export</label><input type="checkbox" ng-model="dashboard.current.loader.save_local" ng-checked="dashboard.current.loader.save_local">
<label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="dashboard.loader.save_gist" ng-checked="dashboard.loader.save_gist">
</div>
<div class="editor-option">
<label class="small">Browser</label><input type="checkbox" ng-model="dashboard.current.loader.save_default" ng-checked="dashboard.current.loader.save_default">
</div>
<div class="editor-option">
<label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_gist" ng-checked="dashboard.current.loader.save_gist">
</div>
<div class="editor-option">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.save_elasticsearch" ng-checked="dashboard.current.loader.save_elasticsearch">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.loader.save_elasticsearch" ng-checked="dashboard.loader.save_elasticsearch">
</div>
</div>
<div class="section">
<h5>Load from</h5>
<div class="editor-option">
<label class="small">Local file</label><input type="checkbox" ng-model="dashboard.current.loader.load_local" ng-checked="dashboard.current.loader.load_local">
<label class="small">Gist</label><input type="checkbox" ng-model="dashboard.loader.load_gist" ng-checked="dashboard.loader.load_gist">
</div>
<div class="editor-option">
<label class="small">Gist</label><input type="checkbox" ng-model="dashboard.current.loader.load_gist" ng-checked="dashboard.current.loader.load_gist">
</div>
<div class="editor-option">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.load_elasticsearch" ng-checked="dashboard.current.loader.load_elasticsearch">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.load.elasticsearch">
<label class="small">ES list size</label><input class="input-mini" type="number" ng-model="dashboard.current.loader.load_elasticsearch_size">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.loader.load_elasticsearch" ng-checked="dashboard.loader.load_elasticsearch">
</div>
</div>
<div class="section">
<h5>Sharing</h5>
<div class="editor-option" >
<label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp" ng-checked="dashboard.current.loader.save_temp">
<label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="dashboard.loader.save_temp" ng-checked="dashboard.loader.save_temp">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.save_temp">
<label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp_ttl_enable">
<div class="editor-option" ng-show="dashboard.loader.save_temp">
<label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="dashboard.loader.save_temp_ttl_enable">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.save_temp &amp;&amp; dashboard.current.loader.save_temp_ttl_enable">
<label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="dashboard.current.loader.save_temp_ttl">
<div class="editor-option" ng-show="dashboard.loader.save_temp &amp;&amp; dashboard.loader.save_temp_ttl_enable">
<label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="dashboard.loader.save_temp_ttl">
</div>
</div>
</div>
@@ -122,10 +107,10 @@
<div class="editor-row">
<div class="section">
<h5>Feature toggles</h5>
<div class="editor-option" ng-repeat="pulldown in dashboard.current.pulldowns">
<div class="editor-option" ng-repeat="pulldown in dashboard.pulldowns">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
</div>
<div class="editor-option" ng-repeat="pulldown in dashboard.current.nav">
<div class="editor-option" ng-repeat="pulldown in dashboard.nav">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
</div>
</div>
@@ -140,7 +125,7 @@
<ng-include src="'app/partials/import.html'"></ng-include>
</div>
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 5+$index">
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 5+$index">
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
</div>
@@ -156,6 +141,6 @@
</div>
</div>
<button ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-success" ng-show="editor.index == 1">Create Row</button>
<button type="button" class="btn btn-info" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Close</button>
<button ng-click="add_row(dashboard,row); reset_row();" class="btn btn-success" ng-show="editor.index == 1">Create Row</button>
<button type="button" class="btn btn-info" ng-click="editor.index=0;dismiss();reset_panel();dashboard.emit_refresh()">Close</button>
</div>
@@ -0,0 +1,15 @@
<div class="editor-row">
<div class="editor-option">
<label class="small">Graphite target expression</label>
<input type="text" class="span10" ng-model='currentAnnotation.target' placeholder=""></input>
</div>
</div>
<div class="editor-row">
<div class="editor-option">
<label class="small">Graphite event tags</label>
<input type="text" ng-model='currentAnnotation.tags' placeholder=""></input>
</div>
</div>
@@ -0,0 +1,29 @@
<div class="editor-row">
<div class="section">
<h5>InfluxDB Query <tip>Example: select text from events where [[timeFilter]]</tip></h5>
<div class="editor-option">
<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where [[timeFilter]]"></input>
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names bellow. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<div class="editor-option">
<label class="small">Title</label>
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">Tags</label>
<input type="text" class="input-small" ng-model='currentAnnotation.tagsColumn' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">Text</label>
<input type="text" class="input-small" ng-model='currentAnnotation.textColumn' placeholder=""></input>
</div>
</div>
</div>
+1 -1
View File
@@ -48,7 +48,7 @@
<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 dashboard.availablePanels|stringSort"></select>
<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>
+4 -19
View File
@@ -77,7 +77,7 @@
<tr bindonce
ng-repeat="row in results.dashboards"
ng-class="{'selected': $index === selectedIndex }">
<td><a confirm-click="elasticsearch_delete(row._id)" confirmation="Are you sure you want to delete the {{row._id}} dashboard"><i class="icon-remove"></i></a></td>
<td><a confirm-click="deleteDashboard(row._id)" confirmation="Are you sure you want to delete the {{row._id}} dashboard"><i class="icon-remove"></i></a></td>
<td style="width:100%">
<a href="#/dashboard/elasticsearch/{{row._id}}" bo-text="row._id"></a>
</td>
@@ -86,13 +86,13 @@
{{tag}}
</a>
</td>
<td><a><i class="icon-share" ng-click="share = dashboard.share_link(row._id,'elasticsearch',row._id)" config-modal="app/partials/dashLoaderShare.html"></i></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>
</table>
</div>
</li>
<!-- ng-show="dashboard.current.loader.load_gist || dashboard.current.loader.load_local" -->
<!-- ng-show="dashboard.loader.load_gist || dashboard.loader.load_local" -->
<li ng-if="showImport" style="margin: 20px;">
<div class="editor-row">
<div class="section">
@@ -104,20 +104,5 @@
</div>
</div>
</div>
<div class="editor-row" ng-show='dashboard.current.loader.load_gist'>
<h5>Gist <tip>Enter a gist number or url</tip></h5>
<form>
<input type="text" ng-model="gist.url" placeholder="Gist number or URL"><br>
<button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
<h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
<h6 ng-hide="gist.files.length || !gist.url.length">No gist dashboards found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="file in gist.files">
<td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
</tr>
</table>
</form>
</div>
</li>
</ul>
</ul>
+7
View File
@@ -0,0 +1,7 @@
define([
'./dashboard-from-es',
'./dashboard-from-file',
'./dashboard-from-script',
'./dashboard-default',
],
function () {});
+24
View File
@@ -0,0 +1,24 @@
define([
'angular',
'config'
],
function (angular, config) {
"use strict";
var module = angular.module('kibana.routes');
module.config(function($routeProvider) {
$routeProvider
.when('/', {
redirectTo: function() {
if (window.localStorage && window.localStorage.grafanaDashboardDefault) {
return window.localStorage.grafanaDashboardDefault;
}
else {
return config.default_route;
}
}
});
});
});
+57
View File
@@ -0,0 +1,57 @@
define([
'angular',
'jquery',
'config'
],
function (angular, $, config) {
"use strict";
var module = angular.module('kibana.routes');
module.config(function($routeProvider) {
$routeProvider
.when('/dashboard/elasticsearch/:id', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromElasticProvider',
})
.when('/dashboard/temp/:id', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromElasticProvider',
});
});
module.controller('DashFromElasticProvider', function($scope, $rootScope, elastic, $routeParams, alertSrv) {
var elasticsearch_load = function(id) {
var url = '/dashboard/' + id;
// hack to check if it is a temp dashboard
if (window.location.href.indexOf('dashboard/temp') > 0) {
url = '/temp/' + id;
}
return elastic.get(url)
.then(function(result) {
if (result._source && result._source.dashboard) {
return angular.fromJson(result._source.dashboard);
} else {
return false;
}
}, function(data, status) {
if(status === 0) {
alertSrv.set('Error',"Could not contact Elasticsearch at " +
config.elasticsearch + ". Please ensure that Elasticsearch is reachable from your browser.",'error');
} else {
alertSrv.set('Error',"Could not find dashboard " + id, 'error');
}
return false;
});
};
elasticsearch_load($routeParams.id).then(function(dashboard) {
$scope.emitAppEvent('setup-dashboard', dashboard);
});
});
});
+59
View File
@@ -0,0 +1,59 @@
define([
'angular',
'jquery',
'config',
'underscore'
],
function (angular, $, config, _) {
"use strict";
var module = angular.module('kibana.routes');
module.config(function($routeProvider) {
$routeProvider
.when('/dashboard/file/:jsonFile', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromFileProvider',
});
});
module.controller('DashFromFileProvider', function($scope, $rootScope, $http, $routeParams, alertSrv) {
var renderTemplate = function(json,params) {
var _r;
_.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
var template = _.template(json);
var rendered = template({ARGS:params});
try {
_r = angular.fromJson(rendered);
} catch(e) {
_r = false;
}
return _r;
};
var file_load = function(file) {
return $http({
url: "app/dashboards/"+file.replace(/\.(?!json)/,"/")+'?' + new Date().getTime(),
method: "GET",
transformResponse: function(response) {
return renderTemplate(response,$routeParams);
}
}).then(function(result) {
if(!result) {
return false;
}
return result.data;
},function() {
alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
return false;
});
};
file_load($routeParams.jsonFile).then(function(result) {
$scope.emitAppEvent('setup-dashboard', result);
});
});
});
+61
View File
@@ -0,0 +1,61 @@
define([
'angular',
'jquery',
'config',
'underscore',
'kbn',
'moment'
],
function (angular, $, config, _, kbn, moment) {
"use strict";
var module = angular.module('kibana.routes');
module.config(function($routeProvider) {
$routeProvider
.when('/dashboard/script/:jsFile', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromScriptProvider',
});
});
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) {
var execute_script = function(result) {
/*jshint -W054 */
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $);
// Handle async dashboard scripts
if (_.isFunction(script_result)) {
var deferred = $q.defer();
script_result(function(dashboard) {
$rootScope.$apply(function() {
deferred.resolve({ data: dashboard });
});
});
return deferred.promise;
}
return { data: script_result };
};
var script_load = function(file) {
var url = 'app/dashboards/'+file.replace(/\.(?!js)/,"/") + '?' + new Date().getTime();
return $http({ url: url, method: "GET" })
.then(execute_script)
.then(null,function(err) {
console.log('Script dashboard error '+ err);
alertSrv.set('Error', "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard", 'error');
return false;
});
};
script_load($routeParams.jsFile).then(function(result) {
$scope.emitAppEvent('setup-dashboard', result.data);
});
});
});
+3 -1
View File
@@ -1,6 +1,5 @@
define([
'./alertSrv',
'./dashboard',
'./datasourceSrv',
'./filterSrv',
'./timer',
@@ -9,5 +8,8 @@ define([
'./annotationsSrv',
'./playlistSrv',
'./unsavedChangesSrv',
'./elasticsearch/es-client',
'./dashboard/dashboardKeyBindings',
'./dashboard/dashboardModel',
],
function () {});
+29 -88
View File
@@ -7,20 +7,14 @@ define([
var module = angular.module('kibana.services');
module.service('annotationsSrv', function(dashboard, datasourceSrv, $q, alertSrv, $rootScope) {
module.service('annotationsSrv', function(datasourceSrv, $q, alertSrv, $rootScope) {
var promiseCached;
var annotationPanel;
var list = [];
var timezone;
this.init = function() {
$rootScope.$on('refresh', this.clearCache);
$rootScope.$on('dashboard-loaded', this.dashboardLoaded);
this.dashboardLoaded();
};
this.dashboardLoaded = function () {
annotationPanel = _.findWhere(dashboard.current.pulldowns, { type: 'annotations' });
};
this.clearCache = function() {
@@ -28,7 +22,8 @@ define([
list = [];
};
this.getAnnotations = function(filterSrv, rangeUnparsed) {
this.getAnnotations = function(filterSrv, rangeUnparsed, dashboard) {
annotationPanel = _.findWhere(dashboard.pulldowns, { type: 'annotations' });
if (!annotationPanel.enable) {
return $q.when(null);
}
@@ -37,10 +32,17 @@ define([
return promiseCached;
}
var graphiteMetrics = this.getGraphiteMetrics(filterSrv, rangeUnparsed);
var graphiteEvents = this.getGraphiteEvents(rangeUnparsed);
timezone = dashboard.timezone;
var annotations = _.where(annotationPanel.annotations, { enable: true });
promiseCached = $q.all(graphiteMetrics.concat(graphiteEvents))
var promises = _.map(annotations, function(annotation) {
var datasource = datasourceSrv.get(annotation.datasource);
return datasource.annotationQuery(annotation, filterSrv, rangeUnparsed)
.then(this.receiveAnnotationResults)
.then(null, errorHandler);
}, this);
promiseCached = $q.all(promises)
.then(function() {
return list;
});
@@ -48,61 +50,10 @@ define([
return promiseCached;
};
this.getGraphiteEvents = function(rangeUnparsed) {
var annotations = this.getAnnotationsByType('graphite events');
if (annotations.length === 0) {
return [];
this.receiveAnnotationResults = function(results) {
for (var i = 0; i < results.length; i++) {
addAnnotation(results[i]);
}
var promises = _.map(annotations, function(annotation) {
return datasourceSrv.default.events({ range: rangeUnparsed, tags: annotation.tags })
.then(function(results) {
_.each(results.data, function (event) {
addAnnotation({
annotation: annotation,
time: event.when * 1000,
description: event.what,
tags: event.tags,
data: event.data
});
});
})
.then(null, errorHandler);
});
return promises;
};
this.getAnnotationsByType = function(type) {
return _.where(annotationPanel.annotations, {
type: type,
enable: true
});
};
this.getGraphiteMetrics = function(filterSrv, rangeUnparsed) {
var annotations = this.getAnnotationsByType('graphite metric');
if (annotations.length === 0) {
return [];
}
var promises = _.map(annotations, function(annotation) {
var graphiteQuery = {
range: rangeUnparsed,
targets: [{ target: annotation.target }],
format: 'json',
maxDataPoints: 100
};
var receiveFunc = _.partial(receiveGraphiteMetrics, annotation);
return datasourceSrv.default.query(filterSrv, graphiteQuery)
.then(receiveFunc)
.then(null, errorHandler);
});
return promises;
};
function errorHandler(err) {
@@ -110,33 +61,23 @@ define([
alertSrv.set('Annotations','Could not fetch annotations','error');
}
function receiveGraphiteMetrics(annotation, results) {
for (var i = 0; i < results.data.length; i++) {
var target = results.data[i];
for (var y = 0; y < target.datapoints.length; y++) {
var datapoint = target.datapoints[y];
if (datapoint[0]) {
addAnnotation({
annotation: annotation,
time: datapoint[1] * 1000,
description: target.target
});
}
}
}
}
function addAnnotation(options) {
var tooltip = "<small><b>" + options.description + "</b><br/>";
var tooltip = "<small><b>" + options.title + "</b><br/>";
if (options.tags) {
tooltip += (options.tags || '') + '<br/>';
}
tooltip += '<i>' + moment(options.time).format('YYYY-MM-DD HH:mm:ss') + '</i><br/>';
if (options.data) {
tooltip += options.data.replace(/\n/g, '<br/>');
if (timezone === 'browser') {
tooltip += '<i>' + moment(options.time).format('YYYY-MM-DD HH:mm:ss') + '</i><br/>';
}
else {
tooltip += '<i>' + moment.utc(options.time).format('YYYY-MM-DD HH:mm:ss') + '</i><br/>';
}
if (options.text) {
tooltip += options.text.replace(/\n/g, '<br/>');
}
tooltip += "</small>";
list.push({
-472
View File
@@ -1,472 +0,0 @@
define([
'angular',
'jquery',
'kbn',
'underscore',
'config',
'moment',
'modernizr',
'filesaver'
],
function (angular, $, kbn, _, config, moment, Modernizr) {
'use strict';
var module = angular.module('kibana.services');
module.service('dashboard', function(
$routeParams, $http, $rootScope, $injector, $location, $timeout,
ejsResource, timer, alertSrv, $q
) {
// A hash of defaults to use when loading a dashboard
var _dash = {
title: "",
tags: [],
style: "dark",
timezone: 'browser',
editable: true,
failover: false,
panel_hints: true,
rows: [],
pulldowns: [{ type: 'templating' }, { type: 'annotations' }],
nav: [{ type: 'timepicker' }],
services: {},
loader: {
save_gist: false,
save_elasticsearch: true,
save_local: true,
save_default: true,
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: false,
load_elasticsearch: true,
load_elasticsearch_size: 20,
load_local: false,
hide: false
},
refresh: false
};
// An elasticJS client to use
var ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
// Store a reference to this
var self = this;
this.current = _.clone(_dash);
this.last = {};
this.availablePanels = [];
$rootScope.$on('$routeChangeSuccess',function() {
// Clear the current dashboard to prevent reloading
self.current = {};
self.indices = [];
route();
});
var route = function() {
// Is there a dashboard type and id in the URL?
if(!(_.isUndefined($routeParams.kbnType)) && !(_.isUndefined($routeParams.kbnId))) {
var _type = $routeParams.kbnType;
var _id = $routeParams.kbnId;
switch(_type) {
case ('elasticsearch'):
self.elasticsearch_load('dashboard',_id);
break;
case ('temp'):
self.elasticsearch_load('temp',_id);
break;
case ('file'):
self.file_load(_id);
break;
case('script'):
self.script_load(_id);
break;
case('local'):
self.local_load();
break;
default:
$location.path(config.default_route);
}
// No dashboard in the URL
} else {
// Check if browser supports localstorage, and if there's an old dashboard. If there is,
// inform the user that they should save their dashboard to Elasticsearch and then set that
// as their default
if (Modernizr.localstorage) {
if(!(_.isUndefined(window.localStorage['dashboard'])) && window.localStorage['dashboard'] !== '') {
$location.path(config.default_route);
alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+
' Click <a href="#/dashboard/local/deprecated">here</a> to load your old dashboard anyway.');
} else if(!(_.isUndefined(window.localStorage.grafanaDashboardDefault))) {
$location.path(window.localStorage.grafanaDashboardDefault);
} else {
$location.path(config.default_route);
}
// No? Ok, grab the default route, its all we have now
} else {
$location.path(config.default_route);
}
}
};
this.refresh = function() {
$rootScope.$broadcast('refresh');
};
var dash_defaults = function(dashboard) {
_.defaults(dashboard, _dash);
_.defaults(dashboard.loader,_dash.loader);
var filtering = _.findWhere(dashboard.pulldowns, {type: 'filtering'});
if (!filtering) {
dashboard.pulldowns.push({
type: 'filtering',
enable: false
});
}
var annotations = _.findWhere(dashboard.pulldowns, {type: 'annotations'});
if (!annotations) {
dashboard.pulldowns.push({
type: 'annotations',
enable: false
});
}
_.each(dashboard.rows, function(row) {
_.each(row.panels, function(panel) {
if (panel.type === 'graphite') {
panel.type = 'graph';
}
});
});
return dashboard;
};
this.dash_load = function(dashboard) {
// Cancel all timers
timer.cancel_all();
// reset fullscreen flag
$rootScope.fullscreen = false;
// Make sure the dashboard being loaded has everything required
dashboard = dash_defaults(dashboard);
window.document.title = 'Grafana - ' + dashboard.title;
// Set the current dashboard
self.current = angular.copy(dashboard);
if(dashboard.refresh) {
self.set_interval(dashboard.refresh);
}
self.availablePanels = config.panels;
$rootScope.$emit('dashboard-loaded', self.current);
return true;
};
this.gist_id = function(string) {
if(self.is_gist(string)) {
return string.match(gist_pattern)[0].replace(/.*\//, '');
}
};
this.is_gist = function(string) {
if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
return string.match(gist_pattern).length > 0 ? true : false;
} else {
return false;
}
};
this.to_file = function() {
var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
// from filesaver.js
window.saveAs(blob, self.current.title+"-"+new Date().getTime());
return true;
};
this.set_default = function(route) {
if (Modernizr.localstorage) {
// Purge any old dashboards
if(!_.isUndefined(window.localStorage['dashboard'])) {
delete window.localStorage['dashboard'];
}
window.localStorage.grafanaDashboardDefault = route;
return true;
} else {
return false;
}
};
this.purge_default = function() {
if (Modernizr.localstorage) {
// Purge any old dashboards
if(!_.isUndefined(window.localStorage['dashboard'])) {
delete window.localStorage['dashboard'];
}
delete window.localStorage.grafanaDashboardDefault;
return true;
} else {
return false;
}
};
// TOFIX: Pretty sure this breaks when you're on a saved dashboard already
this.share_link = function(title,type,id) {
return {
location : window.location.href.replace(window.location.hash,""),
type : type,
id : id,
link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id,
title : title
};
};
var renderTemplate = function(json,params) {
var _r;
_.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
var template = _.template(json);
var rendered = template({ARGS:params});
try {
_r = angular.fromJson(rendered);
} catch(e) {
_r = false;
}
return _r;
};
this.local_load = function() {
var dashboard = JSON.parse(window.localStorage['dashboard']);
dashboard.rows.unshift({
height: "30",
title: "Deprecation Notice",
panels: [
{
title: 'WARNING: Legacy dashboard',
type: 'text',
span: 12,
mode: 'html',
content: 'This dashboard has been loaded from the browsers local cache. If you use '+
'another brower or computer you will not be able to access it! '+
'\n\n <h4>Good news!</h4> Kibana'+
' now stores saved dashboards in Elasticsearch. Click the <i class="icon-save"></i> '+
'button in the top left to save this dashboard. Then select "Set as Home" from'+
' the "advanced" sub menu to automatically use the stored dashboard as your Kibana '+
'landing page afterwards'+
'<br><br><strong>Tip:</strong> You may with to remove this row before saving!'
}
]
});
self.dash_load(dashboard);
};
this.file_load = function(file) {
return $http({
url: "app/dashboards/"+file.replace(/\.(?!json)/,"/")+'?' + new Date().getTime(),
method: "GET",
transformResponse: function(response) {
return renderTemplate(response,$routeParams);
}
}).then(function(result) {
if(!result) {
return false;
}
self.dash_load(dash_defaults(result.data));
return true;
},function() {
alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
return false;
});
};
this.elasticsearch_load = function(type,id) {
var options = {
url: config.elasticsearch + "/" + config.grafana_index + "/"+type+"/"+id+'?' + new Date().getTime(),
method: "GET",
transformResponse: function(response) {
return renderTemplate(angular.fromJson(response)._source.dashboard, $routeParams);
}
};
if (config.elasticsearchBasicAuth) {
options.withCredentials = true;
options.headers = {
"Authorization": "Basic " + config.elasticsearchBasicAuth
};
}
return $http(options)
.error(function(data, status) {
if(status === 0) {
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
". Please ensure that Elasticsearch is reachable from your system." ,'error');
} else {
alertSrv.set('Error',"Could not find "+id+". If you"+
" are using a proxy, ensure it is configured correctly",'error');
}
return false;
}).success(function(data) {
self.dash_load(data);
});
};
this.script_load = function(file) {
return $http({
url: "app/dashboards/"+file.replace(/\.(?!js)/,"/"),
method: "GET"
})
.then(function(result) {
/*jshint -W054 */
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
var script_result = script_func($routeParams,kbn,_,moment, window, document, $, $);
// Handle async dashboard scripts
if (_.isFunction(script_result)) {
var deferred = $q.defer();
script_result(function(dashboard) {
$rootScope.$apply(function() {
deferred.resolve({ data: dashboard });
});
});
return deferred.promise;
}
return { data: script_result };
})
.then(function(result) {
if(!result) {
return false;
}
self.dash_load(dash_defaults(result.data));
return true;
},function() {
alertSrv.set('Error',
"Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard" ,
'error');
return false;
});
};
this.elasticsearch_save = function(type,title,ttl) {
// Clone object so we can modify it without influencing the existing obejct
var save = _.clone(self.current);
var id;
// Change title on object clone
if (type === 'dashboard') {
id = save.title = _.isUndefined(title) ? self.current.title : title;
}
// Create request with id as title. Rethink this.
var request = ejs.Document(config.grafana_index,type,id).source({
user: 'guest',
group: 'guest',
title: save.title,
tags: save.tags,
dashboard: angular.toJson(save)
});
request = type === 'temp' && ttl ? request.ttl(ttl) : request;
return request.doIndex(
// Success
function(result) {
if(type === 'dashboard') {
$location.path('/dashboard/elasticsearch/'+title);
}
return result;
},
// Failure
function() {
return false;
}
);
};
this.elasticsearch_delete = function(id) {
return ejs.Document(config.grafana_index,'dashboard',id).doDelete(
// Success
function(result) {
return result;
},
// Failure
function() {
return false;
}
);
};
this.save_gist = function(title,dashboard) {
var save = _.clone(dashboard || self.current);
save.title = title || self.current.title;
return $http({
url: "https://api.github.com/gists",
method: "POST",
data: {
"description": save.title,
"public": false,
"files": {
"kibana-dashboard.json": {
"content": angular.toJson(save,true)
}
}
}
}).then(function(data) {
return data.data.html_url;
}, function() {
return false;
});
};
this.gist_list = function(id) {
return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
).then(function(response) {
var files = [];
_.each(response.data.data.files,function(v) {
try {
var file = JSON.parse(v.content);
files.push(file);
} catch(e) {
return false;
}
});
return files;
}, function() {
return false;
});
};
this.start_scheduled_refresh = function (after_ms) {
this.cancel_scheduled_refresh();
self.refresh_timer = timer.register($timeout(function () {
self.start_scheduled_refresh(after_ms);
self.refresh();
}, after_ms));
};
this.cancel_scheduled_refresh = function () {
timer.cancel(self.refresh_timer);
};
this.set_interval = function (interval) {
self.current.refresh = interval;
if (interval) {
var _i = kbn.interval_to_ms(interval);
this.start_scheduled_refresh(_i);
} else {
this.cancel_scheduled_refresh();
}
};
});
});
-4
View File
@@ -1,4 +0,0 @@
define([
'./dashboardKeyBindings',
],
function () {});
@@ -6,44 +6,54 @@ define([
function(angular, $) {
"use strict";
var module = angular.module('kibana.services.dashboard');
var module = angular.module('kibana.services');
module.service('dashboardKeybindings', function($rootScope, keyboardManager, dashboard) {
this.shortcuts = function() {
$rootScope.$on('panel-fullscreen-enter', function() {
module.service('dashboardKeybindings', function($rootScope, keyboardManager) {
this.shortcuts = function(scope) {
scope.onAppEvent('panel-fullscreen-enter', function() {
$rootScope.fullscreen = true;
});
$rootScope.$on('panel-fullscreen-exit', function() {
scope.onAppEvent('panel-fullscreen-exit', function() {
$rootScope.fullscreen = false;
});
$rootScope.$on('dashboard-saved', function() {
scope.onAppEvent('dashboard-saved', function() {
if ($rootScope.fullscreen) {
$rootScope.$emit('panel-fullscreen-exit');
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.bind('ctrl+f', function(evt) {
$rootScope.$emit('open-search', evt);
scope.emitAppEvent('open-search', evt);
}, { inputDisabled: true });
keyboardManager.bind('ctrl+h', function() {
var current = dashboard.current.hideControls;
dashboard.current.hideControls = !current;
dashboard.current.panel_hints = current;
var current = scope.dashboard.hideControls;
scope.dashboard.hideControls = !current;
}, { inputDisabled: true });
keyboardManager.bind('ctrl+s', function(evt) {
$rootScope.$emit('save-dashboard', evt);
scope.emitAppEvent('save-dashboard', evt);
}, { inputDisabled: true });
keyboardManager.bind('ctrl+r', function() {
dashboard.refresh();
scope.dashboard.emit_refresh();
}, { inputDisabled: true });
keyboardManager.bind('ctrl+z', function(evt) {
$rootScope.$emit('zoom-out', evt);
scope.emitAppEvent('zoom-out', evt);
}, { inputDisabled: true });
keyboardManager.bind('esc', function() {
@@ -51,7 +61,7 @@ function(angular, $) {
if (popups.length > 0) {
return;
}
$rootScope.$emit('panel-fullscreen-exit');
scope.emitAppEvent('panel-fullscreen-exit');
}, { inputDisabled: true });
};
});
@@ -0,0 +1,99 @@
define([
'angular',
'jquery',
'kbn',
'underscore'
],
function (angular, $, kbn, _) {
'use strict';
var module = angular.module('kibana.services');
module.service('dashboard', function(timer, $rootScope, $timeout) {
function DashboardModel (data) {
if (!data) {
data = {};
}
this.title = data.title;
this.tags = data.tags || [];
this.style = data.style || "dark";
this.timezone = data.timezone || 'browser';
this.editable = data.editble || true;
this.rows = data.rows || [];
this.pulldowns = data.pulldowns || [];
this.nav = data.nav || [];
this.services = data.services || {};
this.loader = data.loader || {};
_.defaults(this.loader, {
save_gist: false,
save_elasticsearch: true,
save_default: true,
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: false,
load_elasticsearch: true,
hide: false
});
if (this.nav.length === 0) {
this.nav.push({ type: 'timepicker' });
}
if (!_.findWhere(this.pulldowns, {type: 'filtering'})) {
this.pulldowns.push({ type: 'filtering', enable: false });
}
if (!_.findWhere(this.pulldowns, {type: 'annotations'})) {
this.pulldowns.push({ type: 'annotations', enable: false });
}
_.each(this.rows, function(row) {
_.each(row.panels, function(panel) {
if (panel.type === 'graphite') {
panel.type = 'graph';
}
});
});
}
var p = DashboardModel.prototype;
p.emit_refresh = function() {
$rootScope.$broadcast('refresh');
};
p.start_scheduled_refresh = function (after_ms) {
this.cancel_scheduled_refresh();
this.refresh_timer = timer.register($timeout(function () {
this.start_scheduled_refresh(after_ms);
this.emit_refresh();
}.bind(this), after_ms));
};
p.cancel_scheduled_refresh = function () {
timer.cancel(this.refresh_timer);
};
p.set_interval = function (interval) {
this.refresh = interval;
if (interval) {
var _i = kbn.interval_to_ms(interval);
this.start_scheduled_refresh(_i);
} else {
this.cancel_scheduled_refresh();
}
};
return {
create: function(dashboard) {
return new DashboardModel(dashboard);
}
};
});
});
+25 -9
View File
@@ -12,13 +12,20 @@ function (angular, _, config) {
var module = angular.module('kibana.services');
module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource, OpenTSDBDatasource) {
var datasources = {};
this.init = function() {
var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true });
if (!defaultDatasource) {
defaultDatasource = config.datasources[_.keys(config.datasources)[0]];
_.each(config.datasources, function(value, key) {
datasources[key] = this.datasourceFactory(value);
if (value.default) {
this.default = datasources[key];
}
}, this);
if (!this.default) {
this.default = datasources[_.keys(datasources)[0]];
this.default.default = true;
}
this.default = this.datasourceFactory(defaultDatasource);
};
this.datasourceFactory = function(ds) {
@@ -34,13 +41,22 @@ function (angular, _, config) {
this.get = function(name) {
if (!name) { return this.default; }
if (datasources[name]) { return datasources[name]; }
var ds = config.datasources[name];
if (!ds) {
return null;
}
throw "Unable to find datasource: " + name;
};
return this.datasourceFactory(ds);
this.getAnnotationSources = function() {
var results = [];
_.each(datasources, function(value, key) {
if (value.supportAnnotations) {
results.push({
name: key,
editorSrc: value.annotationEditorSrc,
});
}
});
return results;
};
this.listOptions = function() {
@@ -0,0 +1,96 @@
define([
'angular',
'config'
],
function(angular, config) {
"use strict";
var module = angular.module('kibana.services');
module.service('elastic', function($http) {
this._request = function(method, url, data) {
var options = {
url: config.elasticsearch + "/" + config.grafana_index + url,
method: method,
data: data
};
if (config.elasticsearchBasicAuth) {
options.headers = {
"Authorization": "Basic " + config.elasticsearchBasicAuth
};
}
return $http(options);
};
this.get = function(url) {
return this._request('GET', url)
.then(function(results) {
return results.data;
});
};
this.post = function(url, data) {
return this._request('POST', url, data)
.then(function(results) {
return results.data;
});
};
this.deleteDashboard = function(id) {
return this._request('DELETE', '/dashboard/' + id)
.then(function(result) {
return result.data._id;
}, function(err) {
throw err.data;
});
};
this.saveForSharing = function(dashboard) {
var data = {
user: 'guest',
group: 'guest',
title: dashboard.title,
tags: dashboard.tags,
dashboard: angular.toJson(dashboard)
};
var ttl = dashboard.loader.save_temp_ttl;
return this._request('POST', '/temp/?ttl=' + ttl, data)
.then(function(result) {
var baseUrl = window.location.href.replace(window.location.hash,'');
var url = baseUrl + "#dashboard/temp/" + result.data._id;
return { title: dashboard.title, url: url };
}, function(err) {
throw "Failed to save to temp dashboard to elasticsearch " + err.data;
});
};
this.saveDashboard = function(dashboard, title) {
var dashboardClone = angular.copy(dashboard);
title = dashboardClone.title = title ? title : dashboard.title;
var data = {
user: 'guest',
group: 'guest',
title: title,
tags: dashboardClone.tags,
dashboard: angular.toJson(dashboardClone)
};
return this._request('PUT', '/dashboard/' + encodeURIComponent(title), data)
.then(function() {
return { title: title, url: '/dashboard/elasticsearch/' + title };
}, function(err) {
throw 'Failed to save to elasticsearch ' + err.data;
});
};
});
});
+16 -10
View File
@@ -8,7 +8,7 @@ define([
var module = angular.module('kibana.services');
module.factory('filterSrv', function(dashboard, $rootScope, $timeout, $routeParams) {
module.factory('filterSrv', function($rootScope, $timeout, $routeParams) {
// defaults
var _d = {
templateParameters: [],
@@ -53,16 +53,14 @@ define([
// disable refresh if we have an absolute time
if (time.to !== 'now') {
this.old_refresh = this.dashboard.refresh;
dashboard.set_interval(false);
this.dashboard.set_interval(false);
}
else if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) {
dashboard.set_interval(this.old_refresh);
this.dashboard.set_interval(this.old_refresh);
this.old_refresh = null;
}
$timeout(function() {
dashboard.refresh();
},0);
$timeout(this.dashboard.emit_refresh, 0);
},
timeRange: function(parse) {
@@ -96,14 +94,22 @@ define([
this.dashboard = dashboard;
this.templateSettings = { interpolate : /\[\[([\s\S]+?)\]\]/g };
if(dashboard.services && dashboard.services.filter) {
this.time = dashboard.services.filter.time;
this.templateParameters = dashboard.services.filter.list || [];
this.updateTemplateData(true);
if (!this.dashboard.services.filter) {
this.dashboard.services.filter = {
list: [],
time: {
from: '1h',
to: 'now'
}
};
}
this.time = dashboard.services.filter.time;
this.templateParameters = dashboard.services.filter.list || [];
this.updateTemplateData(true);
}
};
return result;
});
@@ -11,7 +11,7 @@ function (angular, _, $, config, kbn, moment) {
var module = angular.module('kibana.services');
module.factory('GraphiteDatasource', function(dashboard, $q, $http) {
module.factory('GraphiteDatasource', function($q, $http) {
function GraphiteDatasource(datasource) {
this.type = 'graphite';
@@ -20,6 +20,8 @@ function (angular, _, $, config, kbn, moment) {
this.editorSrc = 'app/partials/graphite/editor.html';
this.name = datasource.name;
this.render_method = datasource.render_method || 'POST';
this.supportAnnotations = true;
this.annotationEditorSrc = 'app/partials/graphite/annotation_editor.html';
}
GraphiteDatasource.prototype.query = function(filterSrv, options) {
@@ -55,6 +57,57 @@ function (angular, _, $, config, kbn, moment) {
}
};
GraphiteDatasource.prototype.annotationQuery = function(annotation, filterSrv, rangeUnparsed) {
// Graphite metric as annotation
if (annotation.target) {
var graphiteQuery = {
range: rangeUnparsed,
targets: [{ target: annotation.target }],
format: 'json',
maxDataPoints: 100
};
return this.query(filterSrv, graphiteQuery)
.then(function(result) {
var list = [];
for (var i = 0; i < result.data.length; i++) {
var target = result.data[i];
for (var y = 0; y < target.datapoints.length; y++) {
var datapoint = target.datapoints[y];
if (!datapoint[0]) { continue; }
list.push({
annotation: annotation,
time: datapoint[1] * 1000,
title: target.target
});
}
}
return list;
});
}
// Graphite event as annotation
else if (annotation.tags) {
return this.events({ range: rangeUnparsed, tags: annotation.tags })
.then(function(results) {
var list = [];
for (var i = 0; i < results.data; i++) {
list.push({
annotation: annotation,
time: event.when * 1000,
title: event.what,
tags: event.tags,
text: event.data
});
}
return list;
});
}
};
GraphiteDatasource.prototype.events = function(options) {
try {
var tags = '';
+41 -1
View File
@@ -8,6 +8,7 @@ function (_) {
this.seriesList = options.seriesList;
this.alias = options.alias;
this.groupByField = options.groupByField;
this.annotation = options.annotation;
}
var p = InfluxSeries.prototype;
@@ -65,6 +66,45 @@ function (_) {
return output;
};
p.getAnnotations = function () {
var list = [];
var self = this;
_.each(this.seriesList, function (series) {
var titleCol = null;
var timeCol = null;
var tagsCol = null;
var textCol = null;
_.each(series.columns, function(column, index) {
if (column === 'time') { timeCol = index; return; }
if (column === 'sequence_number') { return; }
if (!titleCol) { titleCol = index; }
if (column === self.annotation.titleColumn) { titleCol = index; return; }
if (column === self.annotation.tagsColumn) { tagsCol = index; return; }
if (column === self.annotation.textColumn) { textCol = index; return; }
});
_.each(series.points, function (point) {
var data = {
annotation: self.annotation,
time: point[timeCol] * 1000,
title: point[titleCol],
tags: point[tagsCol],
text: point[textCol]
};
if (tagsCol) {
data.tags = point[tagsCol];
}
list.push(data);
});
});
return list;
};
p.createNameForSeries = function(seriesName, groupByColValue) {
var name = this.alias
.replace('$s', seriesName);
@@ -84,4 +124,4 @@ function (_) {
};
return InfluxSeries;
});
});
@@ -21,6 +21,9 @@ function (angular, _, kbn, InfluxSeries) {
this.templateSettings = {
interpolate : /\[\[([\s\S]+?)\]\]/g,
};
this.supportAnnotations = true;
this.annotationEditorSrc = 'app/partials/influxdb/annotation_editor.html';
}
InfluxDatasource.prototype.query = function(filterSrv, options) {
@@ -116,6 +119,15 @@ function (angular, _, kbn, InfluxSeries) {
};
InfluxDatasource.prototype.annotationQuery = function(annotation, filterSrv, rangeUnparsed) {
var timeFilter = getTimeFilter({ range: rangeUnparsed });
var query = _.template(annotation.query, { timeFilter: timeFilter }, this.templateSettings);
return this.doInfluxRequest(query).then(function(results) {
return new InfluxSeries({ seriesList: results, annotation: annotation }).getAnnotations();
});
};
InfluxDatasource.prototype.listColumns = function(seriesName) {
return this.doInfluxRequest('select * from /' + seriesName + '/ limit 1').then(function(data) {
if (!data) {
+26 -14
View File
@@ -7,27 +7,33 @@ function (angular, _) {
var module = angular.module('kibana.services');
module.service('panelMove', function(dashboard, $rootScope) {
module.service('panelMoveSrv', function($rootScope) {
function PanelMoveSrv(dashboard) {
this.dashboard = dashboard;
_.bindAll(this, 'onStart', 'onOver', 'onOut', 'onDrop', 'onStop', 'cleanup');
}
var p = PanelMoveSrv.prototype;
/* each of these can take event,ui,data parameters */
this.onStart = function() {
dashboard.panelDragging = true;
p.onStart = function() {
this.dashboard.panelDragging = true;
$rootScope.$apply();
};
this.onOver = function() {
p.onOver = function() {
$rootScope.$apply();
};
this.onOut = function() {
p.onOut = function() {
$rootScope.$apply();
};
/*
Use our own drop logic. the $parent.$parent this is ugly.
*/
this.onDrop = function(event,ui,data) {
p.onDrop = function(event,ui,data) {
var
dragRow = data.draggableScope.$parent.$parent.row.panels,
dropRow = data.droppableScope.$parent.$parent.row.panels,
@@ -42,26 +48,32 @@ function (angular, _) {
dropRow.splice(dropIndex,0,data.dragItem);
}
dashboard.panelDragging = false;
this.dashboard.panelDragging = false;
// Cleanup nulls/undefined left behind
cleanup();
this.cleanup();
$rootScope.$apply();
$rootScope.$broadcast('render');
};
this.onStop = function() {
dashboard.panelDragging = false;
cleanup();
p.onStop = function() {
this.dashboard.panelDragging = false;
this.cleanup();
$rootScope.$apply();
};
var cleanup = function () {
_.each(dashboard.current.rows, function(row) {
p.cleanup = function () {
_.each(this.dashboard.rows, function(row) {
row.panels = _.without(row.panels,{});
row.panels = _.compact(row.panels);
});
};
return {
create: function(dashboard) {
return new PanelMoveSrv(dashboard);
}
};
});
});
+6 -6
View File
@@ -8,7 +8,7 @@ function (angular, _, kbn) {
var module = angular.module('kibana.services');
module.service('playlistSrv', function(dashboard, $location, $rootScope) {
module.service('playlistSrv', function($location, $rootScope) {
var timerInstance;
var favorites = { dashboards: [] };
@@ -33,17 +33,17 @@ function (angular, _, kbn) {
}
};
this.isCurrentFavorite = function() {
return this._find(dashboard.current.title) ? true : false;
this.isCurrentFavorite = function(dashboard) {
return this._find(dashboard.title) ? true : false;
};
this.markAsFavorite = function() {
var existing = this._find(dashboard.current.title);
this.markAsFavorite = function(dashboard) {
var existing = this._find(dashboard.title);
this._remove(existing);
favorites.dashboards.push({
url: $location.path(),
title: dashboard.current.title
title: dashboard.title
});
this._save();
+17 -10
View File
@@ -12,16 +12,22 @@ function(angular, _, config) {
var module = angular.module('kibana.services');
module.service('unsavedChangesSrv', function($rootScope, $modal, dashboard, $q, $location, $timeout) {
module.service('unsavedChangesSrv', function($rootScope, $modal, $q, $location, $timeout) {
var self = this;
var modalScope = $rootScope.$new();
$rootScope.$on("dashboard-loaded", function(event, newDashboard) {
self.original = angular.copy(newDashboard);
// wait for different services to patch the dashboard (missing properties)
$timeout(function() {
self.original = angular.copy(newDashboard);
self.current = newDashboard;
}, 1000);
});
$rootScope.$on("dashboard-saved", function(event, savedDashboard) {
self.original = angular.copy(savedDashboard);
self.current = savedDashboard;
});
$rootScope.$on("$routeChangeSuccess", function() {
@@ -39,19 +45,20 @@ function(angular, _, config) {
if (self.has_unsaved_changes()) {
event.preventDefault();
self.next = next;
self.open_modal();
$timeout(self.open_modal);
}
});
};
this.open_modal = function() {
var confirmModal = $modal({
template: './app/partials/unsaved-changes.html',
persist: true,
show: false,
scope: modalScope,
keyboard: false
});
template: './app/partials/unsaved-changes.html',
persist: true,
show: false,
scope: modalScope,
keyboard: false
});
$q.when(confirmModal).then(function(modalEl) {
modalEl.modal('show');
@@ -63,7 +70,7 @@ function(angular, _, config) {
return false;
}
var current = angular.copy(dashboard.current);
var current = angular.copy(self.current);
var original = self.original;
// ignore timespan changes
-12
View File
@@ -35,18 +35,6 @@ function (Settings) {
// Elasticsearch index for storing dashboards
grafana_index: "grafana-dash",
// timezoneOFfset:
// If you experiance problems with zoom, it is probably caused by timezone diff between
// your browser and the graphite-web application. timezoneOffset setting can be used to have Grafana
// translate absolute time ranges to the graphite-web timezone.
// Example:
// If TIME_ZONE in graphite-web config file local_settings.py is set to America/New_York, then set
// timezoneOffset to "-0500" (for UTC - 5 hours)
// Example:
// If TIME_ZONE is set to UTC, set this to "0000"
//
timezoneOffset: null,
// set to false to disable unsaved changes warning
unsaved_changes_warning: true,
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -2
View File
@@ -460,7 +460,6 @@ select.grafana-target-segment-input {
width: 200px;
text-align: center;
cursor: auto;
//background: rgba(50,50,50,0.8);
padding: 10px;
}
@@ -474,4 +473,17 @@ select.grafana-target-segment-input {
.hide {
display: block;
}
}
}
.grafana-tooltip {
position : absolute;
top: -1000;
left: 0;
color: #c8c8c8;
padding: 10px;
font-size: 11pt;
font-weight : 200;
background-color: rgb(58, 57, 57);
border-radius: 5px;
z-index: 9999;
}
+2
View File
@@ -127,3 +127,5 @@
padding: 5px;
}
}
+2 -11
View File
@@ -18,9 +18,9 @@
</head>
<body ng-cloak body-class>
<body ng-cloak ng-controller="GrafanaCtrl">
<link rel="stylesheet" href="css/bootstrap.light.min.css" ng-if="dashboard.current.style === 'light'">
<link rel="stylesheet" href="css/bootstrap.light.min.css" ng-if="grafana.style === 'light'">
<link rel="stylesheet" href="css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
@@ -28,15 +28,6 @@
<button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">&times;</button>
<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 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.current.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-view ng-class="{'dashboard-fullscreen': fullscreen}"></div>
+33 -30
View File
@@ -5,37 +5,40 @@ define([],
return {
create: function() {
return {
refresh: function() {},
set_interval: function(value) { this.current.refresh = value; },
emit_refresh: function() {},
set_interval: function(value) { this.refresh = value; },
current: {
title: "",
tags: [],
style: "dark",
timezone: 'browser',
editable: true,
failover: false,
panel_hints: true,
rows: [],
pulldowns: [ { type: 'templating' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {},
loader: {
save_gist: false,
save_elasticsearch: true,
save_local: true,
save_default: true,
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: false,
load_elasticsearch: true,
load_elasticsearch_size: 20,
load_local: false,
hide: false
},
refresh: true
}
title: "",
tags: [],
style: "dark",
timezone: 'browser',
editable: true,
failover: false,
panel_hints: true,
rows: [],
pulldowns: [ { type: 'templating' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {
filter: {
time: {},
list: []
}
},
loader: {
save_gist: false,
save_elasticsearch: true,
save_local: true,
save_default: true,
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: false,
load_elasticsearch: true,
load_elasticsearch_size: 20,
load_local: false,
hide: false
},
refresh: true
};
}
};
+7 -8
View File
@@ -10,9 +10,8 @@ define([
var _dashboard;
beforeEach(module('kibana.services'));
beforeEach(module(function($provide){
beforeEach(module(function(){
_dashboard = dashboardMock.create();
$provide.value('dashboard', _dashboard);
}));
beforeEach(inject(function(filterSrv) {
@@ -20,7 +19,7 @@ define([
}));
beforeEach(function() {
_filterSrv.init(_dashboard.current);
_filterSrv.init(_dashboard);
});
describe('init', function() {
@@ -68,18 +67,18 @@ define([
describe('setTime', function() {
it('should return disable refresh for absolute times', function() {
_dashboard.current.refresh = true;
_dashboard.refresh = true;
_filterSrv.setTime({from: '2011-01-01', to: '2015-01-01' });
expect(_dashboard.current.refresh).to.be(false);
expect(_dashboard.refresh).to.be(false);
});
it('should restore refresh after relative time range is set', function() {
_dashboard.current.refresh = true;
_dashboard.refresh = true;
_filterSrv.setTime({from: '2011-01-01', to: '2015-01-01' });
expect(_dashboard.current.refresh).to.be(false);
expect(_dashboard.refresh).to.be(false);
_filterSrv.setTime({from: '2011-01-01', to: 'now' });
expect(_dashboard.current.refresh).to.be(true);
expect(_dashboard.refresh).to.be(true);
});
});
+57
View File
@@ -139,4 +139,61 @@ define([
});
describe("when creating annotations from influxdb response", function() {
describe('given column mapping for all columns', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'text', 'sequence_number', 'title', 'tags'],
name: 'events1',
points: [[1402596000, 'some text', 1, 'Hello', 'B'], [1402596001, 'asd', 2, 'Hello2', 'B']]
}
],
annotation: {
query: 'select',
titleColumn: 'title',
tagsColumn: 'tags',
textColumn: 'text',
}
});
var result = series.getAnnotations();
it(' should generate 2 annnotations ', function() {
expect(result.length).to.be(2);
expect(result[0].annotation.query).to.be('select');
expect(result[0].title).to.be('Hello');
expect(result[0].time).to.be(1402596000000);
expect(result[0].tags).to.be('B');
expect(result[0].text).to.be('some text');
});
});
describe('given no column mapping', function() {
var series = new InfluxSeries({
seriesList: [
{
columns: ['time', 'text', 'sequence_number'],
name: 'events1',
points: [[1402596000, 'some text', 1]]
}
],
annotation: { query: 'select' }
});
var result = series.getAnnotations();
it('should generate 1 annnotation', function() {
expect(result.length).to.be(1);
expect(result[0].title).to.be('some text');
expect(result[0].time).to.be(1402596000000);
expect(result[0].tags).to.be(undefined);
expect(result[0].text).to.be(undefined);
});
});
});
});
-4
View File
@@ -44,7 +44,6 @@ require.config({
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
modernizr: '../vendor/modernizr-2.6.1',
elasticjs: '../vendor/elasticjs/elastic-angular-client',
},
shim: {
@@ -97,8 +96,6 @@ require.config({
timepicker: ['jquery', 'bootstrap'],
datepicker: ['jquery', 'bootstrap'],
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
}
});
@@ -107,7 +104,6 @@ require([
'angularMocks',
'jquery',
'underscore',
'elasticjs',
'bootstrap',
'angular-sanitize',
'angular-strap',
-100
View File
@@ -1,100 +0,0 @@
/*! elastic.js - v1.1.1 - 2013-05-24
* https://github.com/fullscale/elastic.js
* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */
/*jshint browser:true */
/*global angular:true */
'use strict';
/*
Angular.js service wrapping the elastic.js API. This module can simply
be injected into your angular controllers.
*/
angular.module('elasticjs.service', [])
.factory('ejsResource', ['$http', function ($http) {
return function (config, basicAuth) {
var
// use existing ejs object if it exists
ejs = window.ejs || {},
/* results are returned as a promise */
promiseThen = function (httpPromise, successcb, errorcb) {
return httpPromise.then(function (response) {
(successcb || angular.noop)(response.data);
return response.data;
}, function (response) {
(errorcb || angular.noop)(response.data);
return response.data;
});
};
// check if we have a config object
// if not, we have the server url so
// we convert it to a config object
if (config !== Object(config)) {
config = {server: config};
}
// set url to empty string if it was not specified
if (config.server == null) {
config.server = '';
}
// set authentication header
if (basicAuth || config.basicAuth) {
config.headers = angular.extend( config.headers||{}, {
"Authorization": "Basic " + (basicAuth||config.basicAuth)
});
}
/* implement the elastic.js client interface for angular */
ejs.client = {
server: function (s) {
if (s == null) {
return config.server;
}
config.server = s;
return this;
},
post: function (path, data, successcb, errorcb) {
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'POST'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
get: function (path, data, successcb, errorcb) {
path = config.server + path;
// no body on get request, data will be request params
var reqConfig = {url: path, params: data, method: 'GET'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
put: function (path, data, successcb, errorcb) {
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'PUT'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
del: function (path, data, successcb, errorcb) {
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'DELETE'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
head: function (path, data, successcb, errorcb) {
path = config.server + path;
// no body on HEAD request, data will be request params
var reqConfig = {url: path, params: data, method: 'HEAD'};
return $http(angular.extend(reqConfig, config))
.then(function (response) {
(successcb || angular.noop)(response.headers());
return response.headers();
}, function (response) {
(errorcb || angular.noop)(undefined);
return undefined;
});
}
};
return ejs;
};
}]);
-22268
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -49,7 +49,6 @@ module.exports = function(config,grunt) {
'settings',
'bootstrap',
'modernizr',
'elasticjs',
'timepicker',
'datepicker',
'underscore',