diff --git a/common/css/main.css b/common/css/main.css index ec45aad147c..419320593fb 100644 --- a/common/css/main.css +++ b/common/css/main.css @@ -123,8 +123,6 @@ max-width: 500px; } -.popover-title { display: none; } - .tiny { font-size: 50%; } diff --git a/js/controllers.js b/js/controllers.js index 6c436fc437e..a3ba095aa39 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -3,7 +3,8 @@ 'use strict'; angular.module('kibana.controllers', []) -.controller('DashCtrl', function($scope, $rootScope, $http, $timeout, $route, ejsResource, eventBus, fields, dashboard) { +.controller('DashCtrl', function($scope, $rootScope, $http, $timeout, $route, ejsResource, eventBus, + fields, dashboard) { var _d = { title: "", diff --git a/js/services.js b/js/services.js index f5777e12aa0..e710757f7e0 100644 --- a/js/services.js +++ b/js/services.js @@ -84,6 +84,7 @@ angular.module('kibana.services', []) }) .service('kbnIndex',function($http) { + // returns a promise containing an array of all indices matching the index // pattern that exist in a given range this.indices = function(from,to,pattern,interval) { @@ -233,14 +234,14 @@ angular.module('kibana.services', []) var _query = { query: '*', alias: '', - color: colorAt(_id) + color: colorAt(_id), + id: _id } _.defaults(query,_query) self.list[_id] = query; self.ids.push(_id) - return id; + return _id; } - } this.remove = function(id) { @@ -256,6 +257,10 @@ angular.module('kibana.services', []) } } + this.findQuery = function(queryString) { + return _.findWhere(self.list,{query:queryString}) + } + var nextId = function() { if(_q.idQueue.length > 0) { return _q.idQueue.shift() @@ -271,14 +276,185 @@ angular.module('kibana.services', []) init(); }) -.service('dashboard', function($routeParams, $http, $rootScope, ejsResource, timer) { +.service('filterSrv', function(dashboard, ejsResource) { + // Create an object to hold our service state on the dashboard + dashboard.current.services.filter = dashboard.current.services.filter || {}; + _.defaults(dashboard.current.services.filter,{ + idQueue : [], + list : {}, + ids : [], + }); + + // For convenience + var ejs = ejsResource(config.elasticsearch); + var _f = dashboard.current.services.filter; + + // Save a reference to this + var self = this; + + // Accessors + this.list = dashboard.current.services.filter.list; + this.ids = dashboard.current.services.filter.ids; + + // This is used both for adding filters and modifying them. + // If an id is passed, the filter at that id is updated + this.set = function(filter,id) { + _.defaults(filter,{mandate:'must'}) + if(!_.isUndefined(id)) { + if(!_.isUndefined(self.list[id])) { + _.extend(self.list[id],filter); + return id; + } else { + return false; + } + } else { + if(_.isUndefined(filter.type)) { + return false; + } else { + var _id = nextId(); + var _filter = { + alias: '', + id: _id + } + _.defaults(filter,_filter) + self.list[_id] = filter; + self.ids.push(_id) + return _id; + } + } + } + + this.getBoolFilter = function(ids) { + // A default match all filter, just in case there are no other filters + var bool = ejs.BoolFilter().must(ejs.MatchAllFilter()); + _.each(ids,function(id) { + switch(self.list[id].mandate) + { + case 'mustNot': + bool = bool.mustNot(self.getEjsObj(id)); + break; + case 'should': + bool = bool.should(self.getEjsObj(id)); + break; + default: + bool = bool.must(self.getEjsObj(id)); + } + }) + return bool; + } + + this.getEjsObj = function(id) { + return self.toEjsObj(self.list[id]) + } + + this.toEjsObj = function (filter) { + switch(filter.type) + { + case 'time': + return ejs.RangeFilter(filter.field) + .from(filter.from) + .to(filter.to) + break; + case 'range': + return ejs.RangeFilter(filter.field) + .from(filter.from) + .to(filter.to) + break; + case 'querystring': + console.log(filter.query) + return ejs.QueryFilter(ejs.QueryStringQuery(filter.query)) + break; + case 'terms': + return ejs.TermsFilter(filter.field,filter.value) + break; + case 'exists': + return ejs.ExistsFilter(filter.field) + break; + case 'missing': + return ejs.MissingFilter(filter.field) + break; + default: + return false; + } + } + + this.getByType = function(type) { + return _.pick(self.list,self.idsByType(type)) + } + + this.removeByType = function(type) { + var ids = self.idsByType(type) + _.each(ids,function(id) { + self.remove(id) + }) + return ids; + } + + this.idsByType = function(type) { + return _.pluck(_.where(self.list,{type:type}),'id') + } + + // This special function looks for all time filters, and returns a time range according to the mode + this.timeRange = function(mode) { + var _t = _.where(self.list,{type:'time'}) + if(_t.length == 0) { + return false; + } + switch(mode) { + case "min": + return { + from: new Date(_.max(_.pluck(_t,'from'))), + to: new Date(_.min(_.pluck(_t,'to'))) + } + break; + case "max": + return { + from: new Date(_.min(_.pluck(_t,'from'))), + to: new Date(_.max(_.pluck(_t,'to'))) + } + break; + default: + return false; + } + + } + + this.remove = function(id) { + if(!_.isUndefined(self.list[id])) { + delete self.list[id]; + // This must happen on the full path also since _.without returns a copy + self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id) + _f.idQueue.unshift(id) + _f.idQueue.sort(function(a,b){return a-b}); + return true; + } else { + return false; + } + } + + + var nextId = function() { + if(_f.idQueue.length > 0) { + return _f.idQueue.shift() + } else { + return self.ids.length; + } + } + +}) +.service('dashboard', function($routeParams, $http, $rootScope, $injector, ejsResource, timer, kbnIndex) { // A hash of defaults to use when loading a dashboard var _dash = { title: "", editable: true, rows: [], - services: {} + services: {}, + index: { + interval: 'none', + pattern: '_all', + default: '_all' + }, }; // An elasticJS client to use @@ -288,9 +464,11 @@ angular.module('kibana.services', []) // Empty dashboard object this.current = {}; this.last = {}; + this.indices = []; // Store a reference to this var self = this; + var filterSrv,query; $rootScope.$on('$routeChangeSuccess',function(){ route(); @@ -326,6 +504,33 @@ angular.module('kibana.services', []) } } + // Since the dashboard is responsible for index computation, we can compute and assign the indices + // here before telling the panels to refresh + this.refresh = function() { + if(self.current.index.interval !== 'none') { + if(filterSrv.idsByType('time').length > 0) { + var _range = filterSrv.timeRange('min'); + kbnIndex.indices(_range.from,_range.to, + self.current.index.pattern,self.current.index.interval + ).then(function (p) { + if(p.length > 0) { + self.indices = p; + } else { + self.indices = [self.current.index.default] + } + $rootScope.$broadcast('refresh') + }); + } else { + // This is not optimal, we should be getting the entire index list here, or at least every + // index that possibly matches the pattern + self.indices = [self.current.index.default] + } + } else { + self.indices = [self.current.index.pattern] + $rootScope.$broadcast('refresh') + } + } + this.to_file = function() { var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"}); // from filesaver.js @@ -422,8 +627,6 @@ angular.module('kibana.services', []) return false; } ); - - } this.elasticsearch_delete = function(id) { @@ -497,8 +700,18 @@ angular.module('kibana.services', []) } this.dash_load = function(dashboard) { + + if(dashboard.index.interval === 'none') { + self.indices = [dashboard.index.pattern] + } + self.current = dashboard; + timer.cancel_all(); + + // Ok, now that we've setup the current dashboard, we can inject our services + query = $injector.get('query'); + filterSrv = $injector.get('filterSrv') return true; } diff --git a/panels/bettermap/module.js b/panels/bettermap/module.js index ab54a24c9d8..c8f4a512a6d 100644 --- a/panels/bettermap/module.js +++ b/panels/bettermap/module.js @@ -12,17 +12,10 @@ * field :: field containing a 2 element array in the format [lon,lat] * tooltip :: field to extract the tool tip value from * spyable :: Show the 'eye' icon that reveals the last ES query - - ### Group Events - #### Sends - * get_time :: On panel initialization get time range to query - #### Receives - * time :: An object containing the time range to use and the index(es) to query - * query :: An Array of queries, this panel uses only the first one */ angular.module('kibana.bettermap', []) -.controller('bettermap', function($scope, eventBus, query) { +.controller('bettermap', function($scope, eventBus, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -37,50 +30,53 @@ angular.module('kibana.bettermap', []) _.defaults($scope.panel,_d) $scope.init = function() { - $scope.$on('refresh',function(){ $scope.get_data(); }) - - eventBus.register($scope,'time', function(event,time){set_time(time)}); - - // Now that we're all setup, request the time from our group - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') + $scope.get_data(); } -$scope.get_data = function(segment,query_id) { + $scope.get_data = function(segment,query_id) { $scope.panel.error = false; // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) - return + if(dashboard.indices.length == 0) { + return; + } if(_.isUndefined($scope.panel.field)) { $scope.panel.error = "Please select a field that contains geo point in [lon,lat] format" return } - //$scope.panel.loading = true; + // Determine the field to sort on + var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field')) + if(timeField.length > 1) { + $scope.panel.error = "Time field must be consistent amongst time filters" + } else if(timeField.length == 0) { + timeField = null; + } else { + timeField = timeField[0] + } var _segment = _.isUndefined(segment) ? 0 : segment - $scope.segment = _segment; var boolQuery = ejs.BoolQuery(); _.each(query.list,function(q) { - boolQuery = boolQuery.should(ejs.QueryStringQuery((q.query || '*') + " AND _exists_:"+$scope.panel.field)) + boolQuery = boolQuery.should(ejs.QueryStringQuery((q.query || '*'))) }) - var request = $scope.ejs.Request().indices($scope.index[_segment]) + var request = $scope.ejs.Request().indices(dashboard.indices[_segment]) .query(ejs.FilteredQuery( boolQuery, - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) - ) - ) + filterSrv.getBoolFilter(filterSrv.ids).must(ejs.ExistsFilter($scope.panel.field)) + )) .fields([$scope.panel.field,$scope.panel.tooltip]) .size($scope.panel.size) - .sort($scope.time.field,'desc'); + + if(!_.isNull(timeField)) { + request = request.sort(timeField,'desc'); + } $scope.populate_modal(request) @@ -125,7 +121,7 @@ $scope.get_data = function(segment,query_id) { $scope.$emit('draw') // Get $size results then stop querying - if($scope.data.length < $scope.panel.size && _segment+1 < $scope.index.length) + if($scope.data.length < $scope.panel.size && _segment+1 < dashboard.indices.length) $scope.get_data(_segment+1,$scope.query_id) }); @@ -136,18 +132,12 @@ $scope.get_data = function(segment,query_id) { $scope.modal = { title: "Inspector", body : "
"+ - 'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+ + 'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+ angular.toJson(JSON.parse(request.toString()),true)+ "'", } } - function set_time(time) { - $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index - $scope.get_data(); - } - }) .directive('bettermap', function() { return { diff --git a/panels/derivequeries/editor.html b/panels/derivequeries/editor.html index 56922bc2efa..91963632534 100644 --- a/panels/derivequeries/editor.html +++ b/panels/derivequeries/editor.html @@ -1,22 +1,26 @@
- Query Mode allows to optionally append original query to each term in the list. -
+ Query Mode allows to optionally append original query to each term in the list. +
"+ - 'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+ + 'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+ angular.toJson(JSON.parse(request.toString()),true)+ "'", } } - function set_time(time) { - $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index - $scope.get_data(); - } - - $scope.send_query = function(query) { - var _query = _.isArray(query) ? query : [query] - eventBus.broadcast($scope.$id,$scope.panel.group,'query',_query) - } - var update_history = function(query) { query = _.isArray(query) ? query : [query]; if($scope.panel.remember > 0) { diff --git a/panels/fields/micropanel.html b/panels/fields/micropanel.html index 6a9055c548f..863660b4025 100644 --- a/panels/fields/micropanel.html +++ b/panels/fields/micropanel.html @@ -1,8 +1,8 @@ ×
{{query.label}} | {{query.data[0][1]}} | +|
{{query.info.alias}} | {{query.data[0][1]}} |
{{query.label}} | {{query.data[0][1]}} | +|
{{query.info.alias}} | {{query.data[0][1]}} |
"+ - 'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+ + 'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+ angular.toJson(JSON.parse(request.toString()),true)+ "'", } } - function set_time(time) { - $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index - $scope.get_data(); - } - $scope.build_search = function(field,value) { - $scope.panel.query = add_to_query($scope.panel.query,field,value,false) - $scope.get_data(); - eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query]); + _.each(query.list,function(q) { + q.query = add_to_query(q.query,field,value,false); + }) + dashboard.refresh(); } }) @@ -155,20 +150,12 @@ angular.module('kibana.map', []) }] }, onRegionLabelShow: function(event, label, code){ - $('.jvectormap-label').css({ - "position" : "absolute", - "display" : "none", - 'color' : "#c8c8c8", - 'padding' : '10px', - 'font-size' : '11pt', - 'font-weight' : 200, - 'background-color': '#1f1f1f', - 'border-radius': '5px' - }) + elem.children('.map-legend').show() var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code]; - $('.jvectormap-label').text(label.text() + ": " + count); + elem.children('.map-legend').text(label.text() + ": " + count); }, onRegionOut: function(event, code) { + $('.map-legend').hide(); }, onRegionClick: function(event, code) { var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code]; @@ -176,6 +163,8 @@ angular.module('kibana.map', []) scope.build_search(scope.panel.field,code) } }); + elem.prepend(''); + $('.map-legend').hide(); }) } } diff --git a/panels/pie/editor.html b/panels/pie/editor.html index bd5d5de33cc..d650cbdf80d 100644 --- a/panels/pie/editor.html +++ b/panels/pie/editor.html @@ -1,83 +1,62 @@ -
"+ - 'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+ + 'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+ angular.toJson(JSON.parse(request.toString()),true)+ "'", } } - $scope.build_search = function(field,value) { - $scope.panel.query.query = add_to_query($scope.panel.query.query,field,value,false) - $scope.get_data(); - eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query.query]); - } - - function set_time(time) { - $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index - $scope.get_data(); - } - }) -.directive('pie', function() { +.directive('pie', function(query, filterSrv, dashboard) { return { restrict: 'A', link: function(scope, elem, attrs) { @@ -228,8 +207,8 @@ angular.module('kibana.pie', []) show: scope.panel.labels, radius: 2/3, formatter: function(label, series){ - return '
- Time stamped indices use your selected time range to create a list of
- indices that match a specified timestamp pattern. This can be very
- efficient for some data sets (eg, logs) For example, to match the
- default logstash index pattern you might use
- [logstash-]YYYY.MM.DD
. The [] in "[logstash-]" are
- important as they instruct Kibana not to treat those letters as a
- pattern.
-
- See http://momentjs.com/docs/#/displaying/format/ - for documentation on date formatting. -
- -
+ Time stamped indices use your selected time range to create a list of
+ indices that match a specified timestamp pattern. This can be very
+ efficient for some data sets (eg, logs) For example, to match the
+ default logstash index pattern you might use
+ [logstash-]YYYY.MM.DD
. The [] in "[logstash-]" are
+ important as they instruct Kibana not to treat those letters as a
+ pattern.
+
+ See http://momentjs.com/docs/#/displaying/format/ + for documentation on date formatting. +
+ +Title | @@ -30,23 +85,6 @@
---|