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 : "
Last Elasticsearch Query
"+
-          '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 @@
-
-
- The derive queries panel takes a query and a field, then runs a terms facet against both and generates a list of terms to query on. For example, you might want to see a histogram of the top 5 requests that return a 404. You should be careful not to select a high cardinality field as Elasticsearch must load all of these values into memory.

- Query Mode allows to optionally append original query to each term in the list. -

-
+
+ + +
-
+
+
+
+ The derive queries panel takes a query and a field, runs a terms facet, then creates queries based on them. For example, you might want to see a histogram of the top 5 requests that return a 404. You should be careful not to select a high cardinality field as Elasticsearch must load all of these values into memory.

+ Query Mode allows to optionally append original query to each term in the list. +

+
diff --git a/panels/derivequeries/module.html b/panels/derivequeries/module.html index 523f1970c38..5b37410aeb6 100644 --- a/panels/derivequeries/module.html +++ b/panels/derivequeries/module.html @@ -1,8 +1,23 @@ + -
+ + + + +
+
\ No newline at end of file diff --git a/panels/derivequeries/module.js b/panels/derivequeries/module.js index c03ee03e8fe..0d3762d73cb 100644 --- a/panels/derivequeries/module.js +++ b/panels/derivequeries/module.js @@ -1,6 +1,6 @@ /* - ## Termsquery + ## Derivequeries Broadcasts an array of queries based on the results of a terms facet @@ -23,13 +23,15 @@ */ angular.module('kibana.derivequeries', []) -.controller('derivequeries', function($scope, eventBus) { +.controller('derivequeries', function($scope, $rootScope, query, eventBus, fields, dashboard, filterSrv) { // Set and populate defaults var _d = { + loading : false, status : "Beta", label : "Search", query : "*", + ids : [], group : "default", field : '_type', fields : [], @@ -43,26 +45,19 @@ angular.module('kibana.derivequeries', []) _.defaults($scope.panel,_d); $scope.init = function() { - eventBus.register($scope,'fields', function(event, fields) { - $scope.panel.fields = fields.all; - }); - eventBus.register($scope,'time', function(event,time){set_time(time)}); - eventBus.register($scope,'query', function(event, query) { - $scope.panel.query = _.isArray(query) ? query[0] : query; - $scope.get_data(); - }); - // Now that we're all setup, request the time from our group - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') + $scope.panel.fields = fields.list } $scope.get_data = function() { update_history($scope.panel.query); + // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } $scope.panel.loading = true; - var request = $scope.ejs.Request().indices($scope.index); + var request = $scope.ejs.Request().indices(dashboard.indices); // Terms mode request = request @@ -73,9 +68,7 @@ angular.module('kibana.derivequeries', []) .facetFilter(ejs.QueryFilter( ejs.FilteredQuery( ejs.QueryStringQuery($scope.panel.query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) + filterSrv.getBoolFilter(filterSrv.ids) )))).size(0) $scope.populate_modal(request); @@ -93,10 +86,22 @@ angular.module('kibana.derivequeries', []) } else if ($scope.panel.mode === 'OR') { var suffix = ' OR (' + $scope.panel.query + ')'; } + var ids = []; _.each(results.facets.query.terms, function(v) { - data.push($scope.panel.field+':"'+v.term+'"'+suffix) + var _q = $scope.panel.field+':"'+v.term+'"'+suffix; + // if it isn't in the list, remove it + var _iq = query.findQuery(_q) + if(!_iq) { + ids.push(query.set({query:_q})); + } else { + ids.push(_iq.id); + } }); - $scope.send_query(data) + _.each(_.difference($scope.panel.ids,ids),function(id){ + query.remove(id) + }) + $scope.panel.ids = ids; + dashboard.refresh(); }); } @@ -114,23 +119,12 @@ angular.module('kibana.derivequeries', []) $scope.modal = { title: "Inspector", body : "
Last Elasticsearch Query
"+
-          '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 @@ ×

Micro Analysis of {{micropanel.field}} - - + +
{{micropanel.count}} events in the table set

diff --git a/panels/fields/module.js b/panels/fields/module.js index 80272772a0d..8657a5b58ff 100644 --- a/panels/fields/module.js +++ b/panels/fields/module.js @@ -18,7 +18,7 @@ */ angular.module('kibana.fields', []) -.controller('fields', function($scope, eventBus, $timeout) { +.controller('fields', function($scope, eventBus, $timeout, dashboard, query, filterSrv) { // Set and populate defaults var _d = { @@ -79,9 +79,15 @@ angular.module('kibana.fields', []) eventBus.broadcast($scope.$id,$scope.panel.group,"selected_fields",$scope.active) } - $scope.build_search = function(field, value,negate) { - $scope.panel.query = [add_to_query($scope.panel.query,field,value,negate)] - eventBus.broadcast($scope.$id,$scope.panel.group,'query',$scope.panel.query); + $scope.build_search = function(field,value,negate) { + var query = (negate ? '-':'+')+field+":\""+value+"\"" + filterSrv.set({type:'querystring',query:query}) + dashboard.refresh(); + } + + $scope.fieldExists = function(field,mode) { + filterSrv.set({type:mode,field:field}) + dashboard.refresh(); } $scope.is_active = function(field) { diff --git a/panels/histogram/editor.html b/panels/histogram/editor.html index 1a1657b3ff9..7e3b6e4f52c 100644 --- a/panels/histogram/editor.html +++ b/panels/histogram/editor.html @@ -4,13 +4,19 @@ -
- +
+ + + + +
+
+
-
+
In {{panel.mode}} mode the configured field must be a numeric type
diff --git a/panels/histogram/module.html b/panels/histogram/module.html index 4a806674feb..18b994811d6 100644 --- a/panels/histogram/module.html +++ b/panels/histogram/module.html @@ -22,12 +22,12 @@
- Zoom In + Zoom Out | - -
-
{{series.label}} ({{series.hits}})
+ +
+
{{series.info.alias}} ({{series.hits}})
{{panel.value_field}} {{panel.mode}} per {{panel.interval}} | ({{hits}} hits)
diff --git a/panels/histogram/module.js b/panels/histogram/module.js index eca7517ed3d..539892f223f 100644 --- a/panels/histogram/module.js +++ b/panels/histogram/module.js @@ -42,7 +42,7 @@ */ angular.module('kibana.histogram', []) -.controller('histogram', function($scope, eventBus,query) { +.controller('histogram', function($scope, eventBus, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -50,6 +50,7 @@ angular.module('kibana.histogram', []) group : "default", query : [ {query: "*", label:"Query"} ], mode : 'count', + time_field : '@timestamp', value_field : null, auto_int : true, resolution : 100, @@ -75,50 +76,46 @@ angular.module('kibana.histogram', []) $scope.queries = query; - eventBus.register($scope,'time', function(event,time){$scope.set_time(time)}); - $scope.$on('refresh',function(){ $scope.get_data(); }) - // Now that we're all setup, request the time from our group if we don't - // have it yet - if(_.isUndefined($scope.time)) - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') } $scope.get_data = function(segment,query_id) { delete $scope.panel.error + // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } + var _range = $scope.range = filterSrv.timeRange('min'); + if ($scope.panel.auto_int) - $scope.panel.interval = secondsToHms(calculate_interval($scope.time.from,$scope.time.to,$scope.panel.resolution,0)/1000); - + $scope.panel.interval = secondsToHms(calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000); + $scope.panel.loading = true; var _segment = _.isUndefined(segment) ? 0 : segment - var request = $scope.ejs.Request().indices($scope.index[_segment]); + var request = $scope.ejs.Request().indices(dashboard.indices[_segment]); // Build the query _.each($scope.queries.ids, function(id) { var query = $scope.ejs.FilteredQuery( ejs.QueryStringQuery($scope.queries.list[id].query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) + filterSrv.getBoolFilter(filterSrv.ids) ) var facet = $scope.ejs.DateHistogramFacet(id) if($scope.panel.mode === 'count') { - facet = facet.field($scope.time.field) + facet = facet.field($scope.panel.time_field) } else { if(_.isNull($scope.panel.value_field)) { $scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified"; return } - facet = facet.keyField($scope.time.field).valueField($scope.panel.value_field) + facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field) } facet = facet.interval($scope.panel.interval).facetFilter($scope.ejs.QueryFilter(query)) request = request.facet(facet).size(0) @@ -132,6 +129,7 @@ angular.module('kibana.histogram', []) // Populate scope when we have results results.then(function(results) { + $scope.panel.loading = false; if(_segment == 0) { $scope.hits = 0; @@ -145,15 +143,21 @@ angular.module('kibana.histogram', []) return; } - // Make sure we're still on the same query - if($scope.query_id === query_id) { + // Convert facet ids to numbers + var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k);}) + + // Make sure we're still on the same query/queries + if($scope.query_id === query_id && + _.intersection(facetIds,query.ids).length == query.ids.length + ) { var i = 0; - _.each(results.facets, function(v, id) { + _.each(query.ids, function(id) { + var v = results.facets[id]; // Null values at each end of the time range ensure we see entire range if(_.isUndefined($scope.data[i]) || _segment == 0) { - var data = [[$scope.time.from.getTime(), null],[$scope.time.to.getTime(), null]]; + var data = [[_range.from.getTime(), null],[_range.to.getTime(), null]]; var hits = 0; } else { var data = $scope.data[i].data @@ -172,7 +176,7 @@ angular.module('kibana.histogram', []) // Create the flot series object var series = { data: { - id: id, + info: $scope.queries.list[id], data: data, hits: hits }, @@ -187,7 +191,7 @@ angular.module('kibana.histogram', []) $scope.$emit('render') // If we still have segments left, get them - if(_segment < $scope.index.length-1) { + if(_segment < dashboard.indices.length-1) { $scope.get_data(_segment+1,query_id) } @@ -198,7 +202,32 @@ angular.module('kibana.histogram', []) // function $scope.zoom // factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan $scope.zoom = function(factor) { - eventBus.broadcast($scope.$id,$scope.panel.group,'zoom',factor); + var _now = Date.now(); + var _range = filterSrv.timeRange('min'); + var _timespan = (_range.to.valueOf() - _range.from.valueOf()); + var _center = _range.to.valueOf() - _timespan/2 + + var _to = (_center + (_timespan*factor)/2) + var _from = (_center - (_timespan*factor)/2) + + // If we're not already looking into the future, don't. + if(_to > Date.now() && _range.to < Date.now()) { + var _offset = _to - Date.now() + _from = _from - _offset + _to = Date.now(); + } + + if(factor > 1) { + filterSrv.removeByType('time') + } + filterSrv.set({ + type:'time', + from:moment.utc(_from), + to:moment.utc(_to), + field:$scope.panel.time_field + }) + dashboard.refresh(); + } // I really don't like this function, too much dom manip. Break out into directive? @@ -223,14 +252,8 @@ angular.module('kibana.histogram', []) $scope.$emit('render'); } - $scope.set_time = function(time) { - $scope.time = time; - $scope.index = time.index || $scope.index - $scope.get_data(); - } - }) -.directive('histogramChart', function(eventBus) { +.directive('histogramChart', function(dashboard, eventBus, filterSrv, $rootScope) { return { restrict: 'A', link: function(scope, elem, attrs, ctrl) { @@ -249,10 +272,12 @@ angular.module('kibana.histogram', []) function render_panel() { // Populate from the query service - _.each(scope.data,function(series) { - series.label = scope.queries.list[series.id].alias, - series.color = scope.queries.list[series.id].color - }) + try { + _.each(scope.data,function(series) { + series.label = series.info.alias, + series.color = series.info.color + }) + } catch(e) {return} // Set barwidth based on specified interval var barwidth = interval_to_seconds(scope.panel.interval)*1000 @@ -366,9 +391,13 @@ angular.module('kibana.histogram', []) }); elem.bind("plotselected", function (event, ranges) { - scope.time.from = moment(ranges.xaxis.from); - scope.time.to = moment(ranges.xaxis.to) - eventBus.broadcast(scope.$id,scope.panel.group,'set_time',scope.time) + var _id = filterSrv.set({ + type : 'time', + from : moment.utc(ranges.xaxis.from), + to : moment.utc(ranges.xaxis.to), + field : scope.panel.time_field + }) + dashboard.refresh(); }); } }; diff --git a/panels/hits/module.html b/panels/hits/module.html index 7a6df45f4fe..fb99a3737f0 100644 --- a/panels/hits/module.html +++ b/panels/hits/module.html @@ -3,14 +3,14 @@
- - + +
{{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]}})

@@ -20,23 +20,26 @@
- - - + +
{{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]}})

{{hits}}
- {{query.label}} ({{query.hits}})
+ +
+ {{query.info.alias}} ({{query.hits}}) +
+

\ No newline at end of file diff --git a/panels/hits/module.js b/panels/hits/module.js index 06aedcbdbbf..f6bc705f496 100644 --- a/panels/hits/module.js +++ b/panels/hits/module.js @@ -22,7 +22,7 @@ */ angular.module('kibana.hits', []) -.controller('hits', function($scope, eventBus, query) { +.controller('hits', function($scope, eventBus, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -30,8 +30,8 @@ angular.module('kibana.hits', []) query : ["*"], group : "default", style : { "font-size": '10pt'}, - arrangement : 'vertical', - chart : 'none', + arrangement : 'horizontal', + chart : 'bar', counter_pos : 'above', donut : false, tilt : false, @@ -40,22 +40,13 @@ angular.module('kibana.hits', []) _.defaults($scope.panel,_d) $scope.init = function () { - $scope.queries = query; - $scope.hits = 0; - eventBus.register($scope,'time', function(event,time){ - set_time(time) - }); - - + $scope.$on('refresh',function(){ - console.log($scope.queries) - console.log(query) $scope.get_data(); }) + $scope.get_data(); - // Now that we're all setup, request the time from our group - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') } $scope.get_data = function(segment,query_id) { @@ -63,23 +54,22 @@ angular.module('kibana.hits', []) $scope.panel.loading = true; // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } var _segment = _.isUndefined(segment) ? 0 : segment - var request = $scope.ejs.Request().indices($scope.index[_segment]); + var request = $scope.ejs.Request().indices(dashboard.indices[_segment]); // Build the question part of the query - _.each($scope.queries.ids, function(id) { - var query = $scope.ejs.FilteredQuery( - ejs.QueryStringQuery($scope.queries.list[id].query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to)) + _.each(query.ids, function(id) { + var _q = $scope.ejs.FilteredQuery( + ejs.QueryStringQuery(query.list[id].query || '*'), + filterSrv.getBoolFilter(filterSrv.ids)); request = request .facet($scope.ejs.QueryFacet(id) - .query(query) + .query(_q) ).size(0) }); @@ -91,7 +81,6 @@ angular.module('kibana.hits', []) // Populate scope when we have results results.then(function(results) { - $scope.panel.loading = false; if(_segment == 0) { $scope.hits = 0; @@ -104,16 +93,24 @@ angular.module('kibana.hits', []) $scope.panel.error = $scope.parse_error(results.error); return; } - if($scope.query_id === query_id) { + + // Convert facet ids to numbers + var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k);}) + + // Make sure we're still on the same query/queries + if($scope.query_id === query_id && + _.intersection(facetIds,query.ids).length == query.ids.length + ) { var i = 0; - _.each(results.facets, function(v, id) { + _.each(query.ids, function(id) { + var v = results.facets[id] var hits = _.isUndefined($scope.data[i]) || _segment == 0 ? v.count : $scope.data[i].hits+v.count $scope.hits += v.count // Create series $scope.data[i] = { - //label: $scope.panel.query[i].label || "query"+(parseInt(i)+1), + info: query.list[id], id: id, hits: hits, data: [[i,hits]] @@ -122,26 +119,13 @@ angular.module('kibana.hits', []) i++; }); $scope.$emit('render'); - if(_segment < $scope.index.length-1) + if(_segment < dashboard.indices.length-1) $scope.get_data(_segment+1,query_id) } }); } - $scope.remove_query = function(q) { - $scope.panel.query = _.without($scope.panel.query,q); - $scope.get_data(); - } - - $scope.add_query = function(label,query) { - $scope.panel.query.unshift({ - query: query, - label: label, - }); - $scope.get_data(); - } - $scope.set_refresh = function (state) { $scope.refresh = state; } @@ -155,11 +139,10 @@ angular.module('kibana.hits', []) function set_time(time) { $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index $scope.get_data(); } -}).directive('hitsChart', function(eventBus) { +}).directive('hitsChart', function(eventBus, query) { return { restrict: 'A', link: function(scope, elem, attrs, ctrl) { @@ -177,10 +160,12 @@ angular.module('kibana.hits', []) // Function for rendering panel function render_panel() { - _.each(scope.data,function(series) { - series.label = scope.queries.list[series.id].alias, - series.color = scope.queries.list[series.id].color - }) + try { + _.each(scope.data,function(series) { + series.label = series.info.alias, + series.color = series.info.color + }) + } catch(e) {return} var scripts = $LAB.script("common/lib/panels/jquery.flot.js").wait() .script("common/lib/panels/jquery.flot.pie.js") @@ -201,13 +186,12 @@ angular.module('kibana.hits', []) yaxis: { show: true, min: 0, color: "#c8c8c8" }, xaxis: { show: false }, grid: { - backgroundColor: '#272b30', borderWidth: 0, borderColor: '#eee', color: "#eee", hoverable: true, }, - colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D'] + colors: query.colors }) if(scope.panel.chart === 'pie') scope.plot = $.plot(elem, scope.data, { @@ -223,7 +207,6 @@ angular.module('kibana.hits', []) label: 'The Rest' }, stroke: { - color: '#272b30', width: 0 }, label: { @@ -239,7 +222,7 @@ angular.module('kibana.hits', []) }, //grid: { hoverable: true, clickable: true }, grid: { hoverable: true, clickable: true }, - colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D'] + colors: query.colors }); // Compensate for the height of the legend. Gross diff --git a/panels/map/module.html b/panels/map/module.html index 7c9ba8a2707..47191c3cd08 100644 --- a/panels/map/module.html +++ b/panels/map/module.html @@ -3,6 +3,7 @@ .jvectormap-label { position: absolute; display: none; + visibility: hidden; border: solid 1px #CDCDCD; -webkit-border-radius: 3px; -moz-border-radius: 3px; @@ -30,6 +31,10 @@ text-align: center; } + .jvectormap { + position: relative; + } + .jvectormap-zoomin { display: none; top: 10px; @@ -39,9 +44,23 @@ display: none; top: 30px; } + + .map-legend { + color : #c8c8c8; + padding : 10px; + font-size: 11pt; + font-weight: 200; + background-color: #1f1f1f; + border-radius: 5px; + position: absolute; + right: 0px; + top: 15px; + display: none; + z-index: 99; + } -
+
\ No newline at end of file diff --git a/panels/map/module.js b/panels/map/module.js index 3049b5b4726..bd10628452e 100644 --- a/panels/map/module.js +++ b/panels/map/module.js @@ -28,7 +28,7 @@ */ angular.module('kibana.map', []) -.controller('map', function($scope, eventBus) { +.controller('map', function($scope, $rootScope, eventBus, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -45,22 +45,24 @@ angular.module('kibana.map', []) _.defaults($scope.panel,_d) $scope.init = function() { - eventBus.register($scope,'time', function(event,time){set_time(time)}); - eventBus.register($scope,'query', function(event, query) { - $scope.panel.query = _.isArray(query) ? query[0] : query; - $scope.get_data(); - }); - // Now that we're all setup, request the time from our group - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') + $scope.$on('refresh',function(){$scope.get_data()}) + $scope.get_data(); } $scope.get_data = function() { + // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } $scope.panel.loading = true; - var request = $scope.ejs.Request().indices($scope.index); + var request = $scope.ejs.Request().indices(dashboard.indices); + + var boolQuery = ejs.BoolQuery(); + _.each(query.list,function(q) { + boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*')) + }) // Then the insert into facet and make the request var request = request @@ -70,10 +72,8 @@ angular.module('kibana.map', []) .exclude($scope.panel.exclude) .facetFilter(ejs.QueryFilter( ejs.FilteredQuery( - ejs.QueryStringQuery($scope.panel.query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) + boolQuery, + filterSrv.getBoolFilter(filterSrv.ids) )))).size(0); $scope.populate_modal(request); @@ -97,22 +97,17 @@ angular.module('kibana.map', []) $scope.modal = { title: "Inspector", body : "
Last Elasticsearch Query
"+
-          '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 @@ -
-
- - -
-
+
+
-
-
- - -
-
-
-
- - - -
-
-
-
-
- - -
-
-
- - - -
-
-
-
-
-
-
- - -
-
- - -
-
-
-
- - - -
+ + +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + +
+
-
-
-
- +
+
+ +
+
+ +
+
+ +
+
+ + +
-
- -
-
- -
-
- - -
-
-
Panel Spy
-
-
- -
-
- The panel spy shows 'behind the scenes' information about a panel. It can - be accessed by clicking the in the top right - of the panel. +
Panel Spy
+
+
+ +
+
+ The panel spy shows 'behind the scenes' information about a panel. It can + be accessed by clicking the in the top right + of the panel. +
\ No newline at end of file diff --git a/panels/pie/module.js b/panels/pie/module.js index 7f5b67b283e..f07c1c4f16b 100644 --- a/panels/pie/module.js +++ b/panels/pie/module.js @@ -33,17 +33,17 @@ */ angular.module('kibana.pie', []) -.controller('pie', function($scope, eventBus) { +.controller('pie', function($scope, $rootScope, eventBus, query, dashboard, filterSrv) { // Set and populate defaults var _d = { status : "Deprecating Soon", - query : { field:"_all", query:"*", goal: 1}, + query : { field:"_type", goal: 100}, size : 10, exclude : [], donut : false, tilt : false, - legend : true, + legend : "above", labels : true, mode : "terms", group : "default", @@ -53,30 +53,7 @@ angular.module('kibana.pie', []) _.defaults($scope.panel,_d) $scope.init = function() { - eventBus.register($scope,'time', function(event,time){set_time(time)}); - eventBus.register($scope,'query', function(event, query) { - $scope.panel.query.query = _.isArray(query) ? query[0] : query; - $scope.get_data(); - }); - // Now that we're all setup, request the time from our group - eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') - } - - - $scope.remove_query = function(q) { - if($scope.panel.mode !== 'query') - return false; - $scope.panel.query = _.without($scope.panel.query,q); - $scope.get_data(); - } - - $scope.add_query = function(label,query) { - if($scope.panel.mode !== 'query') - return false; - $scope.panel.query.unshift({ - query: query, - label: label, - }); + $scope.$on('refresh',function(){$scope.get_data()}) $scope.get_data(); } @@ -84,21 +61,40 @@ angular.module('kibana.pie', []) switch(mode) { case 'terms': - $scope.panel.query = {query:"*",field:"_all"}; + $scope.panel.query = {field:"_all"}; break; case 'goal': - $scope.panel.query = {query:"*",goal:100}; + $scope.panel.query = {goal:100}; break; } } + $scope.set_refresh = function (state) { + $scope.refresh = state; + } + + $scope.close_edit = function() { + if($scope.refresh) + $scope.get_data(); + $scope.refresh = false; + $scope.$emit('render'); + } + $scope.get_data = function() { + // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } $scope.panel.loading = true; - var request = $scope.ejs.Request().indices($scope.index); + var request = $scope.ejs.Request().indices(dashboard.indices); + + // This could probably be changed to a BoolFilter + var boolQuery = ejs.BoolQuery(); + _.each(query.list,function(q) { + boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*')) + }) // Terms mode if ($scope.panel.mode == "terms") { @@ -109,10 +105,8 @@ angular.module('kibana.pie', []) .exclude($scope.panel.exclude) .facetFilter(ejs.QueryFilter( ejs.FilteredQuery( - ejs.QueryStringQuery($scope.panel.query.query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) + boolQuery, + filterSrv.getBoolFilter(filterSrv.ids) )))).size(0) $scope.populate_modal(request); @@ -141,11 +135,8 @@ angular.module('kibana.pie', []) // Goal mode } else { request = request - .query(ejs.QueryStringQuery($scope.panel.query.query || '*')) - .filter(ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) - .cache(false)) + .query(boolQuery) + .filter(filterSrv.getBoolFilter(filterSrv.ids)) .size(0) $scope.populate_modal(request); @@ -169,26 +160,14 @@ angular.module('kibana.pie', []) $scope.modal = { title: "Inspector", body : "
Last Elasticsearch Query
"+
-          '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 '
'+ - label+'
'+Math.round(series.percent)+'%
'; + return '
'+ + series.info.alias+'
'+Math.round(series.percent)+'%
'; }, threshold: 0.1 } @@ -258,7 +237,7 @@ angular.module('kibana.pie', []) clickable: true }, legend: { show: false }, - colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D'] + colors: query.colors }; // Populate element @@ -269,7 +248,7 @@ angular.module('kibana.pie', []) } } - function piett(x, y, contents) { + function tt(x, y, contents) { var tooltip = $('#pie-tooltip').length ? $('#pie-tooltip') : $('
'); @@ -287,16 +266,19 @@ angular.module('kibana.pie', []) } elem.bind("plotclick", function (event, pos, object) { - if (!object) + if (!object) { return; - if(scope.panel.mode === 'terms') - scope.build_search(scope.panel.query.field,object.series.label); + } + if(scope.panel.mode === 'terms') { + filterSrv.set({type:'terms',field:scope.panel.query.field,value:object.series.label}) + dashboard.refresh(); + } }); elem.bind("plothover", function (event, pos, item) { if (item) { var percent = parseFloat(item.series.percent).toFixed(1) + "%"; - piett(pos.pageX, pos.pageY, "
" + + tt(pos.pageX, pos.pageY, "
" + (item.series.label||"")+ " " + percent); } else { $("#pie-tooltip").remove(); diff --git a/panels/query/meta.html b/panels/query/meta.html index 3d6edfcc4f8..8af862ff5bf 100644 --- a/panels/query/meta.html +++ b/panels/query/meta.html @@ -1,5 +1,15 @@ - -× - - +
+ + × +
Query Alias
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/panels/query/module.js b/panels/query/module.js index c7cdd3e2e71..019ff8a2167 100644 --- a/panels/query/module.js +++ b/panels/query/module.js @@ -39,12 +39,10 @@ angular.module('kibana.query', []) } $scope.refresh = function(query) { - console.log('refresh') $rootScope.$broadcast('refresh') } $scope.render = function(query) { - console.log('render') $rootScope.$broadcast('render') } diff --git a/panels/table/module.js b/panels/table/module.js index 3924d13a454..4f17b8d6196 100644 --- a/panels/table/module.js +++ b/panels/table/module.js @@ -29,7 +29,7 @@ */ angular.module('kibana.table', []) -.controller('table', function($rootScope, $scope, eventBus, fields, query) { +.controller('table', function($rootScope, $scope, eventBus, fields, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -61,10 +61,6 @@ angular.module('kibana.table', []) $scope.set_listeners = function(group) { $scope.$on('refresh',function(){$scope.get_data()}) - eventBus.register($scope,'time',function(event,time) { - $scope.panel.offset = 0; - set_time(time) - }); eventBus.register($scope,'sort', function(event,sort){ $scope.panel.sort = _.clone(sort); $scope.get_data(); @@ -112,26 +108,26 @@ angular.module('kibana.table', []) } $scope.build_search = function(field,value,negate) { - _.each(query.list,function(q) { - q.query = add_to_query(q.query,field,value,negate); - }) + var query = (negate ? '-':'+')+field+":\""+value+"\"" + filterSrv.set({type:'querystring',query:query}) $scope.panel.offset = 0; - $rootScope.$broadcast('refresh') + dashboard.refresh(); } $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)) + if(dashboard.indices.length == 0) { return + } $scope.panel.loading = true; var _segment = _.isUndefined(segment) ? 0 : segment $scope.segment = _segment; - var request = $scope.ejs.Request().indices($scope.index[_segment]) + var request = $scope.ejs.Request().indices(dashboard.indices[_segment]) var boolQuery = ejs.BoolQuery(); _.each(query.list,function(q) { @@ -141,11 +137,8 @@ angular.module('kibana.table', []) request = request.query( ejs.FilteredQuery( boolQuery, - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to) - ) - ) + filterSrv.getBoolFilter(filterSrv.ids) + )) .highlight( ejs.Highlight($scope.panel.highlight) .fragmentSize(2147483647) // Max size of a 32bit unsigned int @@ -209,10 +202,10 @@ angular.module('kibana.table', []) // If we're not sorting in reverse chrono order, query every index for // size*pages results // Otherwise, only get size*pages results then stop querying - if( - ($scope.data.length < $scope.panel.size*$scope.panel.pages || - !(($scope.panel.sort[0] === $scope.time.field) && $scope.panel.sort[1] === 'desc')) && - _segment+1 < $scope.index.length + if($scope.data.length < $scope.panel.size*$scope.panel.pages + //($scope.data.length < $scope.panel.size*$scope.panel.pages + // || !(($scope.panel.sort[0] === $scope.time.field) && $scope.panel.sort[1] === 'desc')) + && _segment+1 < dashboard.indices.length ) { $scope.get_data(_segment+1,$scope.query_id) } @@ -265,12 +258,6 @@ angular.module('kibana.table', []) } - function set_time(time) { - $scope.time = time; - $scope.index = _.isUndefined(time.index) ? $scope.index : time.index - $scope.get_data(); - } - }) .filter('highlight', function() { return function(text) { diff --git a/panels/timepicker/editor.html b/panels/timepicker/editor.html index a6ae2a10879..3e88f6d31b4 100644 --- a/panels/timepicker/editor.html +++ b/panels/timepicker/editor.html @@ -8,40 +8,6 @@
-
-
Index Settings
-
-
-

- 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. -

- -
-
-
-
-
Timestamp
-
-
-
Index pattern Absolutes in []
- -
-
-
Failover Index If index not found
- -
-
-
Relative mode settings
diff --git a/panels/timepicker/module.js b/panels/timepicker/module.js index 9302d8caf47..7cdef446b6e 100644 --- a/panels/timepicker/module.js +++ b/panels/timepicker/module.js @@ -11,9 +11,6 @@ * time_options :: An array of possible time options. Default: ['5m','15m','1h','6h','12h','24h','2d','7d','30d'] * timespan :: The default options selected for the relative view. Default: '15m' * timefield :: The field in which time is stored in the document. - * index :: Index pattern to match. Literals should be double quoted. Default: '_all' - * defaultindex :: Index to failover to if index not found - * index_interval :: Time between timestamped indices (can be 'none') for static index * refresh: Object containing refresh parameters * enable :: true/false, enable auto refresh by default. Default: false * interval :: Seconds between auto refresh. Default: 30 @@ -28,7 +25,7 @@ */ angular.module('kibana.timepicker', []) -.controller('timepicker', function($scope, eventBus, $timeout, timer, $http, kbnIndex) { +.controller('timepicker', function($scope, $rootScope, eventBus, $timeout, timer, $http, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -37,9 +34,6 @@ angular.module('kibana.timepicker', []) time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'], timespan : '15m', timefield : '@timestamp', - index : '_all', - defaultindex : "_all", - index_interval: "none", timeformat : "", group : "default", refresh : { @@ -58,6 +52,7 @@ angular.module('kibana.timepicker', []) // unnecessary refreshes during changes $scope.refresh_interval = $scope.panel.refresh.interval + // Init a private time object with Date() objects depending on mode switch($scope.panel.mode) { case 'absolute': @@ -86,35 +81,20 @@ angular.module('kibana.timepicker', []) if ($scope.panel.refresh.enable) $scope.set_interval($scope.panel.refresh.interval); - // In the case that a panel is not ready to receive a time event, it may - // request one be sent by broadcasting a 'get_time' with its _id to its group - // This panel can handle multiple groups - eventBus.register($scope,"get_time", function(event,id) { - eventBus.broadcast($scope.$id,id,'time',compile_time($scope.time)) - }); - // In case some other panel broadcasts a time, set us to an absolute range - eventBus.register($scope,"set_time", function(event,time) { - $scope.panel.mode = 'absolute'; - set_timepicker(moment(time.from),moment(time.to)) - $scope.time_apply() - }); - - eventBus.register($scope,"zoom", function(event,factor) { - var _timespan = ($scope.time.to.valueOf() - $scope.time.from.valueOf()); - try { - if($scope.panel.mode != 'absolute') { - $scope.panel.mode = 'since' - set_timepicker(moment($scope.time.to.valueOf() - _timespan*factor),$scope.time.to) - } else { - var _center = $scope.time.to.valueOf() - _timespan/2 - set_timepicker(moment(_center - (_timespan*factor)/2), - moment(_center + (_timespan*factor)/2)) - } - } catch (e) { - console.log(e) - } - $scope.time_apply(); + $scope.$on('refresh', function() { + var time = filterSrv.timeRange('min') + + if($scope.time.from.diff(moment.utc(time.from)) != 0 + || $scope.time.to.diff(moment.utc(time.to)) != 0) + { + $scope.panel.mode = 'absolute'; + + // These 3 statements basicly do everything time_apply() does + set_timepicker(moment(time.from),moment(time.to)) + $scope.time = $scope.time_calc(); + update_panel() + } }); } @@ -146,10 +126,26 @@ angular.module('kibana.timepicker', []) } } + var update_panel = function() { + // Update panel's string representation of the time object.Don't update if + // we're in relative mode since we dont want to store the time object in the + // json for relative periods + if($scope.panel.mode !== 'relative') { + $scope.panel.time = { + from : $scope.time.from.format("MM/DD/YYYY HH:mm:ss"), + to : $scope.time.to.format("MM/DD/YYYY HH:mm:ss"), + }; + } else { + delete $scope.panel.time; + } + } + $scope.set_mode = function(mode) { $scope.panel.mode = mode; $scope.panel.refresh.enable = mode === 'absolute' ? false : $scope.panel.refresh.enable + + update_panel(); } $scope.to_now = function() { @@ -201,54 +197,43 @@ angular.module('kibana.timepicker', []) }; } - $scope.time_apply = function() { + $scope.time_apply = function() { $scope.panel.error = ""; // Update internal time object + + // Remove all other time filters + filterSrv.removeByType('time') + $scope.time = $scope.time_calc(); $scope.time.field = $scope.panel.timefield + update_panel() - // Get indices for the time period, then broadcast time range and index list - // in a single object. Not sure if I like this. - if($scope.panel.index_interval !== 'none') { - kbnIndex.indices($scope.time.from, - $scope.time.to, - $scope.panel.index, - $scope.panel.index_interval - ).then(function (p) { - if(p.length > 0) { - $scope.time.index = p; - eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time)) - } else { - $scope.panel.error = "Could not match index pattern to any ElasticSearch indices" - } - }); - } else { - $scope.time.index = [$scope.panel.index]; - eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time)) - } + set_time_filter($scope.time) + dashboard.refresh(); - // Update panel's string representation of the time object.Don't update if - // we're in relative mode since we dont want to store the time object in the - // json for relative periods - if($scope.panel.mode !== 'relative') { - $scope.panel.time = { - from : $scope.time.from.format("MM/DD/YYYY HH:mm:ss"), - to : $scope.time.to.format("MM/DD/YYYY HH:mm:ss"), - index : $scope.time.index, - }; - } else { - delete $scope.panel.time; - } }; + + function set_time_filter(time) { + time.type = 'time' + // Check if there's a time filter we remember, if not, set one and remember it + if(!_.isUndefined($scope.panel.filter_id) && + !_.isUndefined(filterSrv.list[$scope.panel.filter_id]) && + filterSrv.list[$scope.panel.filter_id].type == 'time') + { + filterSrv.set(compile_time(time),$scope.panel.filter_id) + } else { + $scope.panel.filter_id = filterSrv.set(compile_time(time)) + } + return $scope.panel.filter_id; + } + // Prefer to pass around Date() objects in the EventBus since interacting with // moment objects in libraries that are expecting Date()s can be tricky function compile_time(time) { time = _.clone(time) time.from = time.from.toDate() time.to = time.to.toDate() - time.interval = $scope.panel.index_interval - time.pattern = $scope.panel.index return time; } diff --git a/panels/trends/module.html b/panels/trends/module.html index e17abdd8fcc..db75e6ba0fd 100644 --- a/panels/trends/module.html +++ b/panels/trends/module.html @@ -5,7 +5,7 @@ {{query.percent}}% - ({{query.info.alias}}) + ({{query.info.alias}})
\ No newline at end of file diff --git a/panels/trends/module.js b/panels/trends/module.js index c0c894ac040..518146c152a 100644 --- a/panels/trends/module.js +++ b/panels/trends/module.js @@ -19,7 +19,7 @@ */ angular.module('kibana.trends', []) -.controller('trends', function($scope, eventBus, kbnIndex, query) { +.controller('trends', function($scope, eventBus, kbnIndex, query, dashboard, filterSrv) { // Set and populate defaults var _d = { @@ -34,15 +34,12 @@ angular.module('kibana.trends', []) $scope.init = function () { $scope.hits = 0; + + $scope.$on('refresh',function(){$scope.get_data()}) + eventBus.register($scope,'time', function(event,time){ set_time(time) }); - eventBus.register($scope,'query', function(event, query) { - $scope.panel.query = _.map(query,function(q) { - return {query: q, label: q}; - }) - $scope.get_data(); - }); // Now that we're all setup, request the time from our group eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') } @@ -52,8 +49,11 @@ angular.module('kibana.trends', []) $scope.panel.loading = true; // Make sure we have everything for the request to complete - if(_.isUndefined($scope.index) || _.isUndefined($scope.time)) + if(dashboard.indices.length == 0) { return + } else { + $scope.index = dashboard.indices + } $scope.old_time = { from : new Date($scope.time.from.getTime() - interval_to_seconds($scope.panel.ago)*1000), @@ -67,9 +67,8 @@ angular.module('kibana.trends', []) _.each(query.ids, function(id) { var q = $scope.ejs.FilteredQuery( ejs.QueryStringQuery(query.list[id].query || '*'), - ejs.RangeFilter($scope.time.field) - .from($scope.time.from) - .to($scope.time.to)) + filterSrv.getBoolFilter(filterSrv.ids)) + request = request .facet($scope.ejs.QueryFacet(id) .query(q) @@ -110,7 +109,6 @@ angular.module('kibana.trends', []) // Populate scope when we have results function process_results(results) { results.then(function(results) { - console.log(results) $scope.panel.loading = false; if(_segment == 0) { @@ -124,9 +122,17 @@ angular.module('kibana.trends', []) $scope.panel.error = $scope.parse_error(results.error); return; } - if($scope.query_id === query_id) { + + // Convert facet ids to numbers + var facetIds = _.map(_.keys(results.facets),function(k){if(!isNaN(k)){return parseInt(k)}}) + + // Make sure we're still on the same query/queries + if($scope.query_id === query_id && + _.intersection(facetIds,query.ids).length == query.ids.length + ) { var i = 0; _.each(query.ids, function(id) { + var v = results.facets[id] var n = results.facets[id].count var o = results.facets['old_'+id].count @@ -193,7 +199,6 @@ angular.module('kibana.trends', []) function set_time(time) { $scope.time = time; - $scope.index = time.index || $scope.index $scope.get_data(); } diff --git a/partials/dasheditor.html b/partials/dasheditor.html index c2a5a7dde0e..d6773959e5b 100644 --- a/partials/dasheditor.html +++ b/partials/dasheditor.html @@ -12,9 +12,64 @@
+
+

Index Settings

+
+
+

+ 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. +

+ +
+
+
+
+
Timestamping
+
+
+
Index pattern Absolutes in []
+ +
+
+
Failover Index If index not found
+ +
+
+
+
+

Rows

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
-

Rows

@@ -30,23 +85,6 @@
Title
-

New row

-
-
- - -
-
- - -
-
- - -
-
-
-