Просмотр исходного кода

Merge remote-tracking branch 'upstream/master' into zerofillDefault

Rashid Khan 12 лет назад
Родитель
Сommit
e00d327c5d
50 измененных файлов с 642 добавлено и 369 удалено
  1. 2 1
      .jshintrc
  2. 61 0
      Gruntfile.js
  3. 1 1
      package.json
  4. 7 7
      sample/apache_ldap.conf
  5. 5 1
      sample/nginx.conf
  6. 0 1
      src/app/app.js
  7. 29 3
      src/app/components/kbn.js
  8. 1 1
      src/app/components/require.config.js
  9. 0 1
      src/app/directives/esVersion.js
  10. 9 8
      src/app/directives/kibanaPanel.js
  11. 1 1
      src/app/filters/all.js
  12. 4 3
      src/app/panels/bettermap/module.js
  13. 1 23
      src/app/panels/derivequeries/editor.html
  14. 1 31
      src/app/panels/derivequeries/module.html
  15. 3 110
      src/app/panels/derivequeries/module.js
  16. 1 1
      src/app/panels/histogram/editor.html
  17. 8 6
      src/app/panels/histogram/module.html
  18. 11 10
      src/app/panels/histogram/module.js
  19. 10 13
      src/app/panels/hits/module.js
  20. 9 6
      src/app/panels/map/module.js
  21. 4 3
      src/app/panels/pie/module.js
  22. 0 0
      src/app/panels/query/editors/lucene.html
  23. 0 0
      src/app/panels/query/editors/regex.html
  24. 12 0
      src/app/panels/query/editors/topN.html
  25. 30 0
      src/app/panels/query/help/lucene.html
  26. 10 0
      src/app/panels/query/help/regex.html
  27. 14 0
      src/app/panels/query/help/topN.html
  28. 12 0
      src/app/panels/query/helpModal.html
  29. 29 23
      src/app/panels/query/meta.html
  30. 1 1
      src/app/panels/query/module.html
  31. 60 2
      src/app/panels/query/module.js
  32. 3 0
      src/app/panels/query/query.css
  33. 17 19
      src/app/panels/table/micropanel.html
  34. 20 3
      src/app/panels/table/module.html
  35. 29 14
      src/app/panels/table/module.js
  36. 7 3
      src/app/panels/terms/module.js
  37. 3 0
      src/app/panels/timepicker/module.html
  38. 8 2
      src/app/panels/trends/module.html
  39. 15 18
      src/app/panels/trends/module.js
  40. 1 1
      src/app/partials/dashboard.html
  41. 4 3
      src/app/services/dashboard.js
  42. 2 2
      src/app/services/fields.js
  43. 144 38
      src/app/services/querySrv.js
  44. 0 0
      src/css/bootstrap.dark.min.css
  45. 0 0
      src/css/bootstrap.light.min.css
  46. BIN
      src/img/small.png
  47. 1 1
      src/index.html
  48. 2 2
      src/vendor/bootstrap/bootstrap.js
  49. 16 6
      src/vendor/bootstrap/less/overrides.less
  50. 34 0
      src/vendor/chromath.js

+ 2 - 1
.jshintrc

@@ -28,6 +28,7 @@
 
   "globals": {
     "define": true,
-    "require": true
+    "require": true,
+    "Chromath": false
   }
 }

+ 61 - 0
Gruntfile.js

@@ -189,6 +189,42 @@ module.exports = function (grunt) {
             dest: '<%= pkg.name %>-latest'
           }
         ]
+      },
+      zip_release: {
+        options: {
+          archive: '<%= tempDir %>/<%= pkg.name %>-<%= pkg.version %>.zip'
+        },
+        files : [
+          {
+            expand: true,
+            cwd: '<%= destDir %>',
+            src: ['**/*'],
+            dest: '<%= pkg.name %>-<%= pkg.version %>'
+          },
+          {
+            expand: true,
+            src: ['LICENSE.md', 'README.md'],
+            dest: '<%= pkg.name %>-<%= pkg.version %>'
+          }
+        ]
+      },
+      tgz_release: {
+        options: {
+          archive: '<%= tempDir %>/<%= pkg.name %>-<%= pkg.version %>.tar.gz'
+        },
+        files : [
+          {
+            expand: true,
+            cwd: '<%= destDir %>',
+            src: ['**/*'],
+            dest: '<%= pkg.name %>-<%= pkg.version %>'
+          },
+          {
+            expand: true,
+            src: ['LICENSE.md', 'README.md'],
+            dest: '<%= pkg.name %>-<%= pkg.version %>'
+          }
+        ]
       }
     },
     s3: {
@@ -206,6 +242,21 @@ module.exports = function (grunt) {
             dest: 'kibana/kibana/<%= pkg.name %>-latest.tar.gz',
           }
         ]
+      },
+      release: {
+        bucket: 'download.elasticsearch.org',
+        access: 'private',
+        // debug: true, // uncommment to prevent actual upload
+        upload: [
+          {
+            src: '<%= tempDir %>/<%= pkg.name %>-<%= pkg.version %>.zip',
+            dest: 'kibana/kibana/<%= pkg.name %>-<%= pkg.version %>.zip',
+          },
+          {
+            src: '<%= tempDir %>/<%= pkg.name %>-<%= pkg.version %>.tar.gz',
+            dest: 'kibana/kibana/<%= pkg.name %>-<%= pkg.version %>.tar.gz',
+          }
+        ]
       }
     }
   };
@@ -306,6 +357,16 @@ module.exports = function (grunt) {
     'clean:temp'
   ]);
 
+  // build, then zip and upload to s3
+  grunt.registerTask('release', [
+    'distribute:load_s3_config',
+    'build',
+    'compress:zip_release',
+    'compress:tgz_release',
+    's3:release',
+    'clean:temp'
+  ]);
+
   // collect the key and secret from the .aws-config.json file, finish configuring the s3 task
   grunt.registerTask('distribute:load_s3_config', function () {
     var config = grunt.file.readJSON('.aws-config.json');

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
     "company": "Elasticsearch BV"
   },
   "name": "kibana",
-  "version": "3.0.0m4pre",
+  "version": "3.0.0milestone4",
   "devDependencies": {
     "rjs-build-analysis": "0.0.3",
     "grunt": "~0.4.0",

+ 7 - 7
sample/apache_ldap.conf

@@ -31,15 +31,15 @@
   </Proxy>
  
   # Proxy for _aliases and .*/_search
-  <LocationMatch "^(/_aliases|.*/_search|.*/_mapping)$">
-    ProxyPassMatch http://127.0.0.1:9200
-    ProxyPassReverse http://127.0.0.1:9200
+  <LocationMatch "^/(_nodes|_aliases|_search|.*/_search|_mapping|.*/_mapping)$">
+    ProxyPassMatch http://127.0.0.1:9200/$1
+    ProxyPassReverse http://127.0.0.1:9200/$1
   </LocationMatch>
- 
+
   # Proxy for kibana-int/{dashboard,temp} stuff (if you don't want auth on /, then you will want these to be protected)
-  <LocationMatch "^(/kibana-int/dashboard/|/kibana-int/temp).*$">
-    ProxyPassMatch http://127.0.0.1:9200
-    ProxyPassReverse http://127.0.0.1:9200 
+  <LocationMatch "^/(kibana-int/dashboard/|kibana-int/temp)(.*)$">
+    ProxyPassMatch http://127.0.0.1:9200/$1$2
+    ProxyPassReverse http://127.0.0.1:9200/$1$2
   </LocationMatch>
  
   # Optional disable auth for a src IP (eg: your monitoring host or subnet)

+ 5 - 1
sample/nginx.conf

@@ -25,6 +25,10 @@ server {
     proxy_pass http://127.0.0.1:9200;
     proxy_read_timeout 90;
   }
+  location ~ ^/_nodes$ {
+    proxy_pass http://127.0.0.1:9200;
+    proxy_read_timeout 90;
+  }
   location ~ ^/.*/_search$ {
     proxy_pass http://127.0.0.1:9200;
     proxy_read_timeout 90;
@@ -53,4 +57,4 @@ server {
       auth_basic_user_file /etc/nginx/conf.d/kibana.myhost.org.htpasswd;
     }
   }
-}
+}

+ 0 - 1
src/app/app.js

@@ -6,7 +6,6 @@ define([
   'jquery',
   'underscore',
   'require',
-
   'elasticjs',
   'bootstrap',
   'angular-sanitize',

+ 29 - 3
src/app/components/kbn.js

@@ -1,4 +1,4 @@
-define(['jquery', 'underscore','moment'],
+define(['jquery','underscore','moment','chromath'],
 function($, _, moment) {
   'use strict';
 
@@ -14,9 +14,10 @@ function($, _, moment) {
   };
 
   kbn.get_all_fields = function(data) {
+    var _d = data;
     var fields = [];
-    _.each(data,function(hit) {
-      fields = _.uniq(fields.concat(_.keys(hit)));
+    _.each(_d,function(hit) {
+      fields = _.uniq(fields.concat(_.keys(kbn.flatten_json(hit._source))));
     });
     // Remove stupid angular key
     fields = _.without(fields,'$$hashKey');
@@ -315,6 +316,15 @@ function($, _, moment) {
       }
       unit = mathString.charAt(i++);
       switch (unit) {
+      case 'y':
+        if (type === 0) {
+          roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
+        } else if (type === 1) {
+          dateTime.add('years',num);
+        } else if (type === 2) {
+          dateTime.subtract('years',num);
+        }
+        break;
       case 'M':
         if (type === 0) {
           roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
@@ -458,5 +468,21 @@ function($, _, moment) {
       ].join(';') + '"></div>';
   };
 
+  kbn.colorSteps = function(col,steps) {
+
+    var _d = steps > 5 ? 1.6/steps : 0.3, // distance between steps
+      _p = []; // adjustment percentage
+
+    // Create a range of numbers between -0.8 and 0.8
+    for(var i = 1; i<steps+1; i+=1) {
+      _p.push(i%2 ? ((i-1)*_d*-1)/2 : i*_d/2);
+    }
+
+    // Create the color range
+    return _.map(_p.sort(function(a,b){return a-b;}),function(v) {
+      return v<0 ? Chromath.darken(col,v*-1).toString() : Chromath.lighten(col,v).toString();
+    });
+  };
+
   return kbn;
 });

+ 1 - 1
src/app/components/require.config.js

@@ -13,7 +13,7 @@ require.config({
     text:                     '../vendor/require/text',
     moment:                   '../vendor/moment',
     filesaver:                '../vendor/filesaver',
-
+    chromath:                 '../vendor/chromath',
     angular:                  '../vendor/angular/angular',
     'angular-dragdrop':       '../vendor/angular/angular-dragdrop',
     'angular-strap':          '../vendor/angular/angular-strap',

+ 0 - 1
src/app/directives/esVersion.js

@@ -16,7 +16,6 @@ function (angular) {
         restrict: 'A',
         link: function(scope, elem, attr) {
           if(!esVersion.is(attr.esVersion)) {
-            console.log('hiding');
             elem.hide();
           }
         }

+ 9 - 8
src/app/directives/kibanaPanel.js

@@ -13,8 +13,15 @@ function (angular) {
 
         '<div class="row-fluid panel-extra"><div class="panel-extra-container">' +
 
+
+          '<span class="extra row-button" ng-show="panel.editable != false">' +
+            '<span confirm-click="row.panels = _.without(row.panels,panel)" '+
+            'confirmation="Are you sure you want to remove this {{panel.type}} panel?" class="pointer">'+
+            '<i class="icon-remove pointer" bs-tooltip="\'Remove\'"></i></span>'+
+          '</span>' +
+
           '<span class="extra row-button" ng-hide="panel.draggable == false">' +
-            '<span class="row-text pointer" bs-tooltip="\'Drag here to move\'"' +
+            '<span class="pointer" bs-tooltip="\'Drag here to move\'"' +
             'data-drag=true data-jqyoui-options="{revert: \'invalid\',helper:\'clone\'}"'+
             ' jqyoui-draggable="'+
             '{'+
@@ -23,18 +30,12 @@ function (angular) {
               'index:{{$index}},'+
               'onStart:\'panelMoveStart\','+
               'onStop:\'panelMoveStop\''+
-              '}"  ng-model="row.panels">{{panel.type}}</span>'+
+              '}"  ng-model="row.panels"><i class="icon-move"></i></span>'+
           '</span>' +
           '<span class="extra row-button" ng-show="panel.draggable == false">' +
             '<span class="row-text">{{panel.type}}</span>'+
           '</span>' +
 
-          '<span class="extra row-button" ng-show="panel.editable != false">' +
-            '<span confirm-click="row.panels = _.without(row.panels,panel)" '+
-            'confirmation="Are you sure you want to remove this {{panel.type}} panel?" class="pointer">'+
-            '<i class="icon-remove pointer" bs-tooltip="\'Remove\'"></i></span>'+
-          '</span>' +
-
           '<span class="row-button extra" ng-show="panel.editable != false">' +
             '<span bs-modal="\'app/partials/paneleditor.html\'" class="pointer">'+
             '<i class="icon-cog pointer" bs-tooltip="\'Configure\'"></i></span>'+

+ 1 - 1
src/app/filters/all.js

@@ -49,7 +49,7 @@ define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, m
       if(_.isObject(arr) && !_.isArray(arr)) {
         return angular.toJson(arr);
       } else {
-        return arr.toString();
+        return _.isNull(arr) ? null : arr.toString();
       }
     };
   });

+ 4 - 3
src/app/panels/bettermap/module.js

@@ -101,10 +101,11 @@ function (angular, app, _, L, localRequire) {
         var _segment = _.isUndefined(segment) ? 0 : segment;
 
         $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
-        // This could probably be changed to a BoolFilter
+        var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
         var boolQuery = $scope.ejs.BoolQuery();
-        _.each($scope.panel.queries.ids,function(id) {
-          boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+        _.each(queries,function(q) {
+          boolQuery = boolQuery.should(querySrv.toEjsObj(q));
         });
 
         var request = $scope.ejs.Request().indices(dashboard.indices[_segment])

+ 1 - 23
src/app/panels/derivequeries/editor.html

@@ -1,23 +1 @@
-<div>
-  <div class="row-fluid">
-    <div class="span1">
-      <label class="small">Length</label>
-      <input type="number" style="width:80%" ng-model="panel.size" ng-change="set_refresh(true)">
-    </div>
-    <div class="span3">
-      <label class="small">Field</label>
-      <input type="text" bs-typeahead="fields.list" style="width:80%" ng-change="set_refresh(true)" ng-model='panel.field'></select>
-    </div>
-    <div class="span3">
-      <label class="small">Query Mode</label>
-      <select style="width:80%" ng-change="set_refresh(true)" ng-model='panel.mode' ng-options="f for f in ['terms only','AND', 'OR']"></select>
-    </div>
-    <div class="span4">
-      <label class="small">Exclude Terms(s) (comma seperated)</label>
-      <input array-join type="text" style="width:90%" ng-change="set_refresh(true)" ng-model='panel.exclude'></input>
-    </div>
-    <div class="span1">
-      <label class="small"> Rest </label><input type="checkbox" ng-model="panel.rest" ng-checked="panel.rest" ng-change="set_refresh(true)">
-    </div>
-  </div>
-</div>
+<div></div>

+ 1 - 31
src/app/panels/derivequeries/module.html

@@ -1,33 +1,3 @@
 <div ng-controller='derivequeries' ng-init="init()">
-<style>
-  .end-derive {
-    position:absolute;
-    right:15px;
-    top:5px;
-  }
-  .panel-derive-field {
-    text-decoration: underline;
-    cursor: pointer;
-  }
-  .panel-derive {
-    padding-right: 35px !important;
-    height: 31px !important;
-    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
-    box-sizing: border-box;         /* Opera/IE 8+ */
-  }
-</style>
-  <label class="small">Create new queries from
-    <span class="panel-derive-field" ng-show="!editing" ng-click="editing=true">{{panel.field}}</span>
-    <select ng-show="editing && fields.list.length>1" class="input-medium" ng-model="panel.field" ng-options="f for f in fields.list" ng-change='editing=false' ng-blur="editing=false"></select>
-    <input ng-show="editing && fields.list.length<2" type="text" ng-model="panel.field" ng-blur="editing=false"/>
-    ({{panel.mode}} mode)</label>
-  <div>
-    <form class="form-search" style="position:relative" ng-submit="get_data()">
-      <input class="search-query panel-derive input-block-level" bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text" ng-model="panel.query"/>
-      <span class="end-derive">
-        <i class="icon-search pointer" ng-click="get_data()"></i>
-      </span
-    </form>
-  </div>
+ <h4>This panel has been removed and replaced with the new topN query type. Click the colored dot associated with a query to configure the, much improved, equivilent of a derived query.</h4>
 </div>

+ 3 - 110
src/app/panels/derivequeries/module.js

@@ -22,21 +22,10 @@ function (angular, app, _) {
   var module = angular.module('kibana.panels.derivequeries', []);
   app.useModule(module);
 
-  module.controller('derivequeries', function($scope, $rootScope, querySrv, fields, dashboard, filterSrv) {
+  module.controller('derivequeries', function($scope) {
     $scope.panelMeta = {
-      modals : [
-        {
-          description: "Inspect",
-          icon: "icon-info-sign",
-          partial: "app/partials/inspector.html",
-          show: $scope.panel.spyable
-        }
-      ],
-      status  : "Experimental",
-      description : "Creates a new set of queries using the Elasticsearch terms facet. For example,"+
-       " you might want to create 5 queries showing the most frequent HTTP response codes. Be "+
-       "careful not to select a high cardinality field, as Elasticsearch must load all unique values"+
-       " into memory."
+      status  : "Deprecated",
+      description : "This panel has been replaced with the 'topN' mode in the query pull down."
     };
 
     // Set and populate defaults
@@ -59,102 +48,6 @@ function (angular, app, _) {
 
     $scope.init = function() {
       $scope.editing = false;
-      $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(dashboard.indices.length === 0) {
-        return;
-      }
-
-      $scope.panelMeta.loading = true;
-      var request = $scope.ejs.Request().indices(dashboard.indices);
-
-      // Terms mode
-      request = request
-        .facet($scope.ejs.TermsFacet('query')
-          .field($scope.panel.field)
-          .size($scope.panel.size)
-          .exclude($scope.panel.exclude)
-          .facetFilter($scope.ejs.QueryFilter(
-            $scope.ejs.FilteredQuery(
-              $scope.ejs.QueryStringQuery($scope.panel.query || '*'),
-              filterSrv.getBoolFilter(filterSrv.ids)
-              )))).size(0);
-
-      $scope.populate_modal(request);
-
-      var results = request.doSearch();
-
-      // Populate scope when we have results
-      results.then(function(results) {
-        $scope.panelMeta.loading = false;
-        var suffix;
-        if ($scope.panel.query === '' || $scope.panel.mode === 'terms only') {
-          suffix = '';
-        } else if ($scope.panel.mode === 'AND') {
-          suffix = ' AND (' + $scope.panel.query + ')';
-        } else if ($scope.panel.mode === 'OR') {
-          suffix = ' OR (' + $scope.panel.query + ')';
-        }
-        var ids = [];
-        var terms = results.facets.query.terms;
-        var others = [];
-        _.each(terms, function(v) {
-          var _q = $scope.panel.field+':"'+v.term+'"'+suffix;
-          // if it isn't in the list, remove it
-          var _iq = querySrv.findQuery(_q);
-          if(!_iq) {
-            ids.push(querySrv.set({alias: v.term, query:_q}));
-          } else {
-            ids.push(_iq.id);
-          }
-          others.push("NOT (" + _q + ")");
-        });
-        if ($scope.panel.rest) {
-          var _other_q = others.join(' AND ');
-          var _iq = querySrv.findQuery(_other_q);
-          if (!_iq) {
-            ids.push(querySrv.set({alias: 'other', query: _other_q}));
-          } else {
-            ids.push(_iq.id);
-          }
-        }
-        _.each(_.difference($scope.panel.ids,ids),function(id){
-          querySrv.remove(id);
-        });
-        $scope.panel.ids = ids;
-        dashboard.refresh();
-      });
-    };
-
-    $scope.set_refresh = function (state) {
-      $scope.refresh = state;
-    };
-
-    $scope.close_edit = function() {
-      if($scope.refresh) {
-        $scope.get_data();
-      }
-      $scope.refresh =  false;
-    };
-
-    $scope.populate_modal = function(request) {
-      $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
-    };
-
-    var update_history = function(query) {
-      query = _.isArray(query) ? query : [query];
-      if($scope.panel.remember > 0) {
-        $scope.panel.history = _.union(query.reverse(),$scope.panel.history);
-        var _length = $scope.panel.history.length;
-        if(_length > $scope.panel.remember) {
-          $scope.panel.history = $scope.panel.history.slice(0,$scope.panel.remember);
-        }
-      }
     };
   });
 });

+ 1 - 1
src/app/panels/histogram/editor.html

@@ -1,7 +1,7 @@
   <div class="row-fluid">
     <div class="span2">
       <label class="small">Mode</label>
-      <select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total','change']"></select>
+      <select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total']"></select>
     </div>
     <div class="span2">
       <label class="small">Time Field</label>

+ 8 - 6
src/app/panels/histogram/module.html

@@ -63,12 +63,6 @@
           Lines
         </label>
       </div>
-      <div class="checkbox">
-        <label class="small">
-          <input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
-          Points
-        </label>
-      </div>
       <div class="checkbox">
         <label class="small">
           <input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
@@ -84,6 +78,14 @@
         </label>
       </div>
     </span>
+    <span>
+      <div class="checkbox">
+        <label class="small">
+          <input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend" ng-change="render()">
+          Legend
+        </label>
+      </div>
+    </span>
     <span>
       <label class="small">Interval</label> <select ng-change="set_interval(panel.interval);get_data();" class="input-small" ng-model="panel.interval" ng-options="interval_label(time) for time in _.union([panel.interval],panel.intervals)"></select>
     </span>

+ 11 - 10
src/app/panels/histogram/module.js

@@ -190,14 +190,17 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       var request = $scope.ejs.Request().indices(dashboard.indices[segment]);
 
       $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+
+      var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
       // Build the query
-      _.each($scope.panel.queries.ids, function(id) {
+      _.each(queries, function(q) {
         var query = $scope.ejs.FilteredQuery(
-          querySrv.getEjsObj(id),
+          querySrv.toEjsObj(q),
           filterSrv.getBoolFilter(filterSrv.ids)
         );
 
-        var facet = $scope.ejs.DateHistogramFacet(id);
+        var facet = $scope.ejs.DateHistogramFacet(q.id);
 
         if($scope.panel.mode === 'count') {
           facet = facet.field($scope.panel.time_field);
@@ -220,6 +223,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
 
       // Populate scope when we have results
       results.then(function(results) {
+
         $scope.panelMeta.loading = false;
         if(segment === 0) {
           $scope.hits = 0;
@@ -233,18 +237,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
           return;
         }
 
-        // Convert facet ids to numbers
-        var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k, 10);});
-
         // Make sure we're still on the same query/queries
-        if($scope.query_id === query_id && _.difference(facetIds, $scope.panel.queries.ids).length === 0) {
+        if($scope.query_id === query_id) {
 
           var i = 0,
             time_series,
             hits;
 
-          _.each($scope.panel.queries.ids, function(id) {
-            var query_results = results.facets[id];
+          _.each(queries, function(q) {
+            var query_results = results.facets[q.id];
             // we need to initialize the data variable on the first run,
             // and when we are working on the first segment of the data.
             if(_.isUndefined($scope.data[i]) || segment === 0) {
@@ -267,7 +268,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
               $scope.hits += entry.count; // Entire dataset level hits counter
             });
             $scope.data[i] = {
-              info: querySrv.list[id],
+              info: q,
               time_series: time_series,
               hits: hits
             };

+ 10 - 13
src/app/panels/hits/module.js

@@ -84,14 +84,16 @@ define([
       var request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
 
       $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+      var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
       // Build the question part of the query
-      _.each($scope.panel.queries.ids, function(id) {
+      _.each(queries, function(q) {
         var _q = $scope.ejs.FilteredQuery(
-          querySrv.getEjsObj(id),
+          querySrv.toEjsObj(q),
           filterSrv.getBoolFilter(filterSrv.ids));
 
         request = request
-          .facet($scope.ejs.QueryFacet(id)
+          .facet($scope.ejs.QueryFacet(q.id)
             .query(_q)
           ).size(0);
       });
@@ -117,24 +119,19 @@ define([
           return;
         }
 
-        // Convert facet ids to numbers
-        var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k, 10);});
-
         // Make sure we're still on the same query/queries
-        if($scope.query_id === query_id &&
-          _.intersection(facetIds,$scope.panel.queries.ids).length === $scope.panel.queries.ids.length
-          ) {
+        if($scope.query_id === query_id) {
           var i = 0;
-          _.each($scope.panel.queries.ids, function(id) {
-            var v = results.facets[id];
+          _.each(queries, function(q) {
+            var v = results.facets[q.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] = {
-              info: querySrv.list[id],
-              id: id,
+              info: q,
+              id: q.id,
               hits: hits,
               data: [[i,hits]]
             };

+ 9 - 6
src/app/panels/map/module.js

@@ -76,14 +76,17 @@ function (angular, app, _, $) {
       $scope.panelMeta.loading = true;
 
 
-      var request;
-      request = $scope.ejs.Request().indices(dashboard.indices);
+      var request,
+        boolQuery,
+        queries;
 
       $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
-      // This could probably be changed to a BoolFilter
-      var boolQuery = $scope.ejs.BoolQuery();
-      _.each($scope.panel.queries.ids,function(id) {
-        boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+      request = $scope.ejs.Request().indices(dashboard.indices);
+      queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
+      boolQuery = $scope.ejs.BoolQuery();
+      _.each(queries,function(q) {
+        boolQuery = boolQuery.should(querySrv.toEjsObj(q));
       });
 
       // Then the insert into facet and make the request

+ 4 - 3
src/app/panels/pie/module.js

@@ -109,13 +109,14 @@ define([
       var request = $scope.ejs.Request().indices(dashboard.indices);
 
       $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+      var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
       // This could probably be changed to a BoolFilter
       var boolQuery = $scope.ejs.BoolQuery();
-      _.each($scope.panel.queries.ids,function(id) {
-        boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+      _.each(queries,function(q) {
+        boolQuery = boolQuery.should(querySrv.toEjsObj(q));
       });
 
-
       var results;
 
       // Terms mode

+ 0 - 0
src/app/panels/query/editors/lucene.html


+ 0 - 0
src/app/panels/query/editors/regex.html


+ 12 - 0
src/app/panels/query/editors/topN.html

@@ -0,0 +1,12 @@
+  <fieldset>
+    <label class="small">Field</label><br>
+    <input ng-model="querySrv.list[id].field" type="text" bs-typeahead="fields.list" placeholder="Field">
+    <p>
+    <label class="small">Count</label><br>
+    <input ng-model="querySrv.list[id].size" type="number">
+    <p>
+    <label class="small">Union</label><br>
+      <select class="input-small" ng-model="querySrv.list[id].union">
+      <option ng-repeat="mode in ['none','AND','OR']">{{mode}}</option>
+    </select>
+  </fieldset>

+ 30 - 0
src/app/panels/query/help/lucene.html

@@ -0,0 +1,30 @@
+The lucene query type uses <a target="_blank" href='http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax'>LUCENE query string syntax</a> to find matching documents or events within Elasticsearch.
+
+<h4>Examples</h4>
+<ul class="unstyled" type="disc">
+  <li class="listitem"><p class="simpara">
+  <code class="literal">status</code> field contains <code class="literal">active</code>
+  </p><pre class="literallayout">status:active</pre></li>
+  <li class="listitem"><p class="simpara">
+  <code class="literal">title</code> field contains <code class="literal">quick</code> or <code class="literal">brown</code>
+  </p><pre class="literallayout">title:(quick brown)</pre></li>
+  <li class="listitem"><p class="simpara">
+  <code class="literal">author</code> field contains the exact phrase <code class="literal">"john smith"</code>
+  </p><pre class="literallayout">author:"John Smith"</pre></li>
+</ul>
+
+<p>Wildcard searches can be run on individual terms, using <code class="literal">?</code> to replace
+a single character, and <code class="literal">*</code> to replace zero or more characters:</p>
+<pre class="literallayout">qu?ck bro*</pre>
+
+<ul class="unstyled" type="disc">
+  <li class="listitem"><p class="simpara">
+  Numbers 1..5
+  </p><pre class="literallayout">count:[1 TO 5]</pre></li>
+  <li class="listitem"><p class="simpara">
+  Tags between <code class="literal">alpha</code> and <code class="literal">omega</code>, excluding <code class="literal">alpha</code> and <code class="literal">omega</code>:
+  </p><pre class="literallayout">tag:{alpha TO omega}</pre></li>
+  <li class="listitem"><p class="simpara">
+  Numbers from 10 upwards
+  </p><pre class="literallayout">count:[10 TO *]</pre></li>
+</ul>

+ 10 - 0
src/app/panels/query/help/regex.html

@@ -0,0 +1,10 @@
+The regex query allows you to use regular expressions to match terms in the <i>_all</i> field.
+
+A detailed overview of lucene's regex engine is available here: <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax">Regular expressions in Elasticsearch</a>
+
+<h5>A note on anchoring</h5>
+Lucene’s patterns are always anchored. The pattern provided must match the entire string. For string "abcde":
+<p>
+<code>ab.*</code> will match<br>
+<code>abcd</code> will not match</br>
+

+ 14 - 0
src/app/panels/query/help/topN.html

@@ -0,0 +1,14 @@
+The topN query uses an <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html">Elasticsearch terms facet</a> to find the most common terms in a field and build queries from the result. The topN query uses <a target="_blank" href='http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax'>LUCENE query string syntax</a>
+
+<h4>Parameters</h4>
+<ul>
+  <li>
+    <strong>Field</strong> / The field to facet on. Fields with a large number of unique terms will <a target="_blank" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-terms-facet.html#_memory_considerations_2">use more memory</a> to calculate.
+  </li>
+  <li>
+    <strong>Count</strong> / How many queries to generate. The resulting queries will use brightness variations on the original query's color for their own.
+  </li>
+  <li>
+    <strong>Union</strong> / The relation the generated queries have to the original. For example, if your field was set to 'extension', your original query was "user:B.Awesome" and your union was AND. Kibana might generate the following example query: <code>extension:"html" AND (user:B.Awesome)</code>
+  </li>
+</ul>

+ 12 - 0
src/app/panels/query/helpModal.html

@@ -0,0 +1,12 @@
+<div class="modal-header">
+  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+  <h3>About the {{help.type}} query</h3>
+</div>
+<div class="modal-body">
+
+  <div ng-include="queryHelpPath(help.type)"></div>
+
+</div>
+<div class="modal-footer">
+  <button type="button" class="btn btn-danger" ng-click="dismiss()">Close</button>
+</div>

+ 29 - 23
src/app/panels/query/meta.html

@@ -1,28 +1,34 @@
-<div class="panel-query-meta row-fluid" style="width:220px">
+<div class="panel-query-meta row-fluid" style="width:260px">
 
   <style>
-    .input-query-alias {
-      margin-bottom: 5px !important;
+    .panel-query-meta fieldset label {
+      margin-top: 3px;
     }
-    .panel-query-meta .pin {
-      text-decoration: underline;
-    }
-
   </style>
-  <a class="close" ng-click="render();dismiss();" href="">×</a>
-  <label class="strong small ">Query Alias <button class="btn btn-mini" ng-class="{active:querySrv.list[id].pin}" ng-click="toggle_pin(id);dismiss();" class="pointer"><i class="icon-pushpin"></i></button></label>
-
-  <form>
-    <input class="input-large input-query-alias" type="text" ng-model="querySrv.list[id].alias" placeholder='Alias...' />
-    <div>
-      <i ng-repeat="color in querySrv.colors" class="pointer" ng-class="{'icon-circle-blank':querySrv.list[id].color == color,'icon-circle':querySrv.list[id].color != color}" ng-style="{color:color}" ng-click="querySrv.list[id].color = color;render();"> </i>
-    </div>
-  </form>
-
-  <span>
-    <label class="small">Query type</label>
-    <select ng-change="dismiss();" class="input-small" ng-model="querySrv.list[id].type">
-      <option ng-repeat="type in querySrv.queryTypes|esVersion:'require'">{{type.name}}</option>
-    </select>
-  </span>
+
+  <fieldset>
+    <select class="input-small" ng-model="querySrv.list[id].type" ng-change="typeChange(querySrv.list[id])">
+      <option ng-repeat="type in queryTypes|esVersion:'require'">{{type.name}}</option>
+    </select> &nbsp<a href="" class="small" ng-click="queryHelp(querySrv.list[id].type)"> About the {{querySrv.list[id].type}} query</a>
+
+    <hr class="small">
+
+    <label class="small">Legend value</label>
+    <input type="text" ng-model="querySrv.list[id].alias" placeholder="Alias...">
+  </fieldset>
+
+  <div ng-include src="queryConfig(querySrv.list[id].type)"></div>
+
+
+  <hr class="small">
+  <div>
+    <i ng-repeat="color in querySrv.colors" class="pointer" ng-class="{'icon-circle-blank':querySrv.list[id].color == color,'icon-circle':querySrv.list[id].color != color}" ng-style="{color:color}" ng-click="querySrv.list[id].color = color;render();"> </i>
+  </div>
+
+
+  <div class="pull-right">
+
+    <a class="btn btn-mini" ng-class="{active:querySrv.list[id].pin}" ng-click="toggle_pin(id);dismiss();" class="pointer">Pin <i class="icon-pushpin"></i></a>
+    <input class="btn btn-mini" ng-click="dashboard.refresh();dismiss();" type="submit"/ value="Close">
+  </div>
 </div>

+ 1 - 1
src/app/panels/query/module.html

@@ -2,7 +2,7 @@
   <div ng-repeat="id in (unPinnedQueries = (querySrv.ids|pinnedQuery:false))" ng-class="{'short-query': unPinnedQueries.length>1}">
     <form class="form-search" style="position:relative;margin-bottom:5px;" ng-submit="refresh()">
       <span class="begin-query">
-        <i class="icon-circle pointer" data-unique="1" bs-popover="'app/panels/query/meta.html'" data-placement="bottomLeft" ng-style="{color: querySrv.list[id].color}"></i>
+        <i class="pointer" ng-class="queryIcon(querySrv.list[id].type)" data-unique="1" bs-popover="'app/panels/query/meta.html'" data-placement="bottomLeft" ng-style="{color: querySrv.list[id].color}"></i>
         <i class="icon-remove-sign pointer remove-query" ng-show="querySrv.ids.length > 1" ng-click="querySrv.remove(id);refresh()"></i>
       </span>
       <span>

+ 60 - 2
src/app/panels/query/module.js

@@ -19,7 +19,7 @@ define([
   var module = angular.module('kibana.panels.query', []);
   app.useModule(module);
 
-  module.controller('query', function($scope, querySrv, $rootScope) {
+  module.controller('query', function($scope, querySrv, $rootScope, dashboard, $q, $modal) {
     $scope.panelMeta = {
       status  : "Stable",
       description : "Manage all of the queries on the dashboard. You almost certainly need one of "+
@@ -37,12 +37,27 @@ define([
 
     $scope.querySrv = querySrv;
 
+    // A list of query types for the query config popover
+    $scope.queryTypes = _.map(querySrv.queryTypes, function(v,k) {
+      return {
+        name:k,
+        require:v.require
+      };
+    });
+
+    var queryHelpModal = $modal({
+      template: './app/panels/query/helpModal.html',
+      persist: true,
+      show: false,
+      scope: $scope,
+    });
+
     $scope.init = function() {
     };
 
     $scope.refresh = function() {
       update_history(_.pluck($scope.querySrv.list,'query'));
-      $rootScope.$broadcast('refresh');
+      dashboard.refresh();
     };
 
     $scope.render = function() {
@@ -53,6 +68,38 @@ define([
       querySrv.list[id].pin = querySrv.list[id].pin ? false : true;
     };
 
+    $scope.queryIcon = function(type) {
+      return querySrv.queryTypes[type].icon;
+    };
+
+    $scope.queryConfig = function(type) {
+      return "./app/panels/query/editors/"+(type||'lucene')+".html";
+    };
+
+    $scope.queryHelpPath = function(type) {
+      return "./app/panels/query/help/"+(type||'lucene')+".html";
+    };
+
+    $scope.queryHelp = function(type) {
+      $scope.help = {
+        type: type
+      };
+      $q.when(queryHelpModal).then(function(modalEl) {
+        modalEl.modal('show');
+      });
+    };
+
+    $scope.typeChange = function(q) {
+      var _nq = {
+        id   : q.id,
+        type : q.type,
+        query: q.query,
+        alias: q.alias,
+        color: q.color
+      };
+      querySrv.list[_nq.id] = querySrv.defaults(_nq);
+    };
+
     var update_history = function(query) {
       if($scope.panel.remember > 0) {
         $scope.panel.history = _.union(query.reverse(),$scope.panel.history);
@@ -66,4 +113,15 @@ define([
     $scope.init();
 
   });
+
+  module.directive('queryConfig', function() {
+    return {
+      restrict: 'A',
+      template: '<div></div>',
+      link: function(scope, elem) {
+        console.log(elem);
+      }
+    };
+  });
+
 });

+ 3 - 0
src/app/panels/query/query.css

@@ -2,6 +2,9 @@
   display:inline-block;
   margin-right: 10px;
 }
+.short-query input.search-query {
+    width: 280px;
+}
 .begin-query {
   position:absolute;
   left:13px;

+ 17 - 19
src/app/panels/table/micropanel.html

@@ -1,23 +1,22 @@
 <a class="close" ng-click="dismiss()" href="">×</a>
-<h4>
-  Micro Analysis of {{micropanel.field}}
-  <i class="pointer icon-search" ng-click="fieldExists(micropanel.field,'must');dismiss();"></i>
-  <i class="pointer icon-ban-circle" ng-click="fieldExists(micropanel.field,'mustNot');dismiss();"></i>
-  <br>
-  <small>
-    {{micropanel.count}} events in the table set
-    <span ng-show="micropanel.hasArrays">
-      as
-      <a class="link" ng-class="{'strong':micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,true)">Groups</a> /
-      <a class="link" ng-class="{'strong':!micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,false)">Singles</a>
-    </span>
-  </small>
-</h4>
-<table style="width:100%;table-layout:fixed" class='table table-striped table-condensed'>
+<style>
+
+</style>
+<span>
+  <i class="pointer icon-search" ng-click="fieldExists(micropanel.field,'must');dismiss();" bs-tooltip="'Find events with this field'"></i>
+  <i class="pointer icon-ban-circle" ng-click="fieldExists(micropanel.field,'mustNot');dismiss();" bs-tooltip="'Find events without this field'"></i>
+  <strong>Micro Analysis of {{micropanel.field}}</strong>
+  <span ng-show="micropanel.hasArrays">
+    as
+    <a class="link" ng-class="{'strong':micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,true)">Groups</a> /
+    <a class="link" ng-class="{'strong':!micropanel.grouped}" ng-click="toggle_micropanel(micropanel.field,false)">Singles</a>
+  </span>
+</span>
+<table style="width:100%;table-layout:fixed" class='table table-striped table-unpadded'>
   <thead>
     <th style="width:260px">{{micropanel.field}}</th>
     <th style="width:40px">Action</th>
-    <th style="width:100px;text-align:right">Count</th>
+    <th style="width:100px;text-align:right">Count / {{micropanel.count}} events</th>
   </thead>
   <tbody>
     <tr ng-repeat='field in micropanel.values'>
@@ -38,16 +37,15 @@
     </tr>
   </tbody>
 </table>
-<div class="progress" ng-show="micropanel.grouped">
+<div class="progress nomargin" ng-show="micropanel.grouped">
   <div ng-repeat='field in micropanel.values' bs-tooltip="field[0]+' ('+percent(field[1],data.length)+')'" class="bar {{micropanelColor($index)}}" ng-style="{width: percent(field[1],data.length)};"></div>
 </div>
 <div>
-  <span ng-repeat="field in micropanel.related|orderBy:'count':true|limitTo:micropanel.limit track by $index"><a ng-click="toggle_field(field)">{{field.name}}</a> ({{Math.round((field.count / micropanel.count) * 100)}}%), </span>
+  <span ng-repeat="field in micropanel.related|orderBy:'count':true|limitTo:micropanel.limit track by $index"><a ng-click="toggle_field(field.name)" bs-tooltip="'Toggle {{field.name}} column'">{{field.name}}</a> ({{Math.round((field.count / micropanel.count) * 100)}}%), </span>
   <a class="link" ng-show="micropanel.related.length > micropanel.limit" ng-click="micropanel.limit = micropanel.limit + 10">More <i class="icon-caret-right"></i></a>
 </div>
 <div class="row-fluid">
   <div class="span12">
-    <h5>Facets</h5>
     <div class="btn-group">
       <a class="btn dropdown-toggle pointer" data-toggle="dropdown">
         <i class="icon-list-ol"></i> Terms

+ 20 - 3
src/app/panels/table/module.html

@@ -6,12 +6,29 @@
       overflow-x: scroll;
     }
   </style>
+
   <div class="row-fluid">
     <div ng-class="{'span3':panel.field_list}" ng-show="panel.field_list">
       <div class="sidebar-nav">
-        <h5>Fields <i class=" icon-chevron-sign-left pointer " ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Hide field list'" ng-show="panel.field_list"></i></h5>
+        <strong>Fields <i class=" icon-chevron-sign-left pointer " ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Hide field list'" ng-show="panel.field_list"></i></strong><p>
+        <div class="small">
+          <span class="link" ng-click="panel.all_fields = true;" ng-class="{strong:panel.all_fields}">All</span> /
+           <span class="link" ng-click="panel.all_fields = false;" ng-class="{strong:!panel.all_fields}">Current</span>
+        </div>
+        <div><input type="text" class="input-medium" placeholder="Type to filter..." ng-model="fieldFilter"></div>
+
+
         <ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;">
-          <li ng-style="panel.style" ng-repeat="field in fields.list" >
+          <li ng-style="panel.style" ng-repeat="field in metaFields|filter:fieldFilter|orderBy:identity">
+            <i class="pointer" ng-class="{'icon-check': _.contains(panel.fields,field),'icon-check-empty': !_.contains(panel.fields,field)}" ng-click="toggle_field(field)"></i>
+            <a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: _.contains(panel.fields,field)}">{{field}}</a>
+          </li>
+
+          <li ng-style="panel.style" ng-repeat="field in fields.list|filter:fieldFilter|orderBy:identity" ng-show="panel.all_fields">
+            <i class="pointer" ng-class="{'icon-check': _.contains(panel.fields,field),'icon-check-empty': !_.contains(panel.fields,field)}" ng-click="toggle_field(field)"></i>
+            <a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: _.contains(panel.fields,field)}">{{field}}</a>
+          </li>
+          <li ng-style="panel.style" ng-repeat="field in current_fields|filter:fieldFilter|orderBy:identity" ng-hide="panel.all_fields">
             <i class="pointer" ng-class="{'icon-check': _.contains(panel.fields,field),'icon-check-empty': !_.contains(panel.fields,field)}" ng-click="toggle_field(field)"></i>
             <a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: _.contains(panel.fields,field)}">{{field}}</a>
           </li>
@@ -51,7 +68,7 @@
         </thead>
         <tbody ng-repeat="event in data| slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
           <tr ng-click="toggle_details(event)" class="pointer">
-            <td ng-show="panel.fields.length<1">{{event._source|stringify|tableTruncate:panel.trimFactor:1}}</td>
+            <td ng-show="panel.fields.length<1">{{event.kibana._source|stringify|tableTruncate:panel.trimFactor:1}}</td>
             <td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields" ng-bind-html-unsafe="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></td>
           </tr>
           <tr ng-show="event.kibana.details">

+ 29 - 14
src/app/panels/table/module.js

@@ -78,6 +78,7 @@ function (angular, app, _, kbn, moment) {
       header  : true,
       paging  : true,
       field_list: true,
+      all_fields: false,
       trimFactor: 300,
       normTimes : true,
       spyable : true
@@ -86,7 +87,8 @@ function (angular, app, _, kbn, moment) {
 
     $scope.init = function () {
       $scope.Math = Math;
-
+      $scope.metaFields = [];
+      $scope.identity = angular.identity;
       $scope.$on('refresh',function(){$scope.get_data();});
 
       $scope.fields = fields;
@@ -109,7 +111,6 @@ function (angular, app, _, kbn, moment) {
     };
 
     var showModal = function(panel,type) {
-
       $scope.facetPanel = panel;
       $scope.facetType = type;
 
@@ -204,6 +205,12 @@ function (angular, app, _, kbn, moment) {
     };
 
     $scope.get_data = function(segment,query_id) {
+      var
+        _segment,
+        request,
+        boolQuery,
+        results;
+
       $scope.panel.error =  false;
 
       // Make sure we have everything for the request to complete
@@ -213,16 +220,17 @@ function (angular, app, _, kbn, moment) {
 
       $scope.panelMeta.loading = true;
 
-      $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
-
-      var _segment = _.isUndefined(segment) ? 0 : segment;
+      _segment = _.isUndefined(segment) ? 0 : segment;
       $scope.segment = _segment;
 
-      var request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
+      request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
 
-      var boolQuery = $scope.ejs.BoolQuery();
-      _.each($scope.panel.queries.ids,function(id) {
-        boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+      $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+      var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
+      boolQuery = $scope.ejs.BoolQuery();
+      _.each(queries,function(q) {
+        boolQuery = boolQuery.should(querySrv.toEjsObj(q));
       });
 
       request = request.query(
@@ -241,7 +249,7 @@ function (angular, app, _, kbn, moment) {
 
       $scope.populate_modal(request);
 
-      var results = request.doSearch();
+      results = request.doSearch();
 
       // Populate scope when we have results
       results.then(function(results) {
@@ -262,11 +270,15 @@ function (angular, app, _, kbn, moment) {
         // Check that we're still on the same query, if not stop
         if($scope.query_id === query_id) {
           $scope.data= $scope.data.concat(_.map(results.hits.hits, function(hit) {
-            var _h = _.clone(hit);
-            //_h._source = kbn.flatten_json(hit._source);
-            //_h.highlight = kbn.flatten_json(hit.highlight||{});
+            var
+              _h = _.clone(hit),
+              _p = _.omit(hit,'_source','sort','_score');
+
+            $scope.metaFields = _.union(_.keys(_p),$scope.metaFields);
+
+            // _source is kind of a lie here, never display it, only select values from it
             _h.kibana = {
-              _source : kbn.flatten_json(hit._source),
+              _source : _.extend(kbn.flatten_json(hit._source),_p),
               highlight : kbn.flatten_json(hit.highlight||{})
             };
             return _h;
@@ -291,6 +303,9 @@ function (angular, app, _, kbn, moment) {
           // Keep only what we need for the set
           $scope.data = $scope.data.slice(0,$scope.panel.size * $scope.panel.pages);
 
+          // Populate current_fields list
+          $scope.current_fields = kbn.get_all_fields($scope.data);
+
         } else {
           return;
         }

+ 7 - 3
src/app/panels/terms/module.js

@@ -83,17 +83,21 @@ function (angular, app, _, $, kbn) {
       $scope.panelMeta.loading = true;
       var request,
         results,
-        boolQuery;
+        boolQuery,
+        queries;
 
       request = $scope.ejs.Request().indices(dashboard.indices);
 
       $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+      queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
       // This could probably be changed to a BoolFilter
       boolQuery = $scope.ejs.BoolQuery();
-      _.each($scope.panel.queries.ids,function(id) {
-        boolQuery = boolQuery.should(querySrv.getEjsObj(id));
+      _.each(queries,function(q) {
+        boolQuery = boolQuery.should(querySrv.toEjsObj(q));
       });
 
+
       // Terms mode
       request = request
         .facet($scope.ejs.TermsFacet('terms')

+ 3 - 0
src/app/panels/timepicker/module.html

@@ -47,6 +47,9 @@
         </ul>
 
       </li>
+      <li ng-show="!dashboard.current.refresh">
+        <a class="icon-refresh" ng-click="dashboard.refresh()"></a>
+      </li>
     </ul>
 
   </form>

+ 8 - 2
src/app/panels/trends/module.html

@@ -1,11 +1,17 @@
 <div ng-controller='trends' ng-init="init()">
 
-  <div ng-style="panel.style" style="line-height:{{panel.style['font-size']}};display:inline-block;padding-right: 5px;" ng-repeat="query in trends">
+  <style>
+    div.trends-horizontal {
+      display:inline-block;
+      padding-right: 5px;
+    }
+  </style>
+
+  <div ng-class="{'trends-horizontal':panel.arrangement == 'horizontal'}" ng-style="panel.style" style="line-height:{{panel.style['font-size']}};" ng-repeat="query in trends">
     <i class="icon-circle" style="color:{{query.info.color}}"></i>
     <span bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new"  ng-class="{'text-success': query.hits.new >= query.hits.old, 'text-error': query.hits.old > query.hits.new}" class='pointer strong'>
       <i class='large' ng-class="{'icon-caret-up': query.hits.new >= query.hits.old, 'icon-caret-down': query.hits.old > query.hits.new}"></i> {{query.percent}}%
     </span>
     <span class="tiny light" ng-show="query.info.alias != ''">({{query.info.alias}})</span>
-    <br ng-show="panel.arrangement == 'vertical'">
   </div>
 </div>

+ 15 - 18
src/app/panels/trends/module.js

@@ -73,8 +73,6 @@ function (angular, app, _, kbn) {
         $scope.index = segment > 0 ? $scope.index : dashboard.indices;
       }
 
-      $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
-
       // Determine a time field
       var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field'));
       if(timeField.length > 1) {
@@ -98,11 +96,13 @@ function (angular, app, _, kbn) {
       var request = $scope.ejs.Request();
       var _ids_without_time = _.difference(filterSrv.ids,filterSrv.idsByType('time'));
 
+      $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+      var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
 
       // Build the question part of the query
-      _.each($scope.panel.queries.ids, function(id) {
+      _.each(queries, function(query) {
         var q = $scope.ejs.FilteredQuery(
-          querySrv.getEjsObj(id),
+          querySrv.toEjsObj(query),
           filterSrv.getBoolFilter(_ids_without_time).must(
             $scope.ejs.RangeFilter(timeField)
             .from($scope.time.from)
@@ -110,23 +110,23 @@ function (angular, app, _, kbn) {
           ));
 
         request = request
-          .facet($scope.ejs.QueryFacet(id)
+          .facet($scope.ejs.QueryFacet(query.id)
             .query(q)
           ).size(0);
       });
 
 
       // And again for the old time period
-      _.each($scope.panel.queries.ids, function(id) {
+      _.each(queries, function(query) {
         var q = $scope.ejs.FilteredQuery(
-          querySrv.getEjsObj(id),
+          querySrv.toEjsObj(query),
           filterSrv.getBoolFilter(_ids_without_time).must(
             $scope.ejs.RangeFilter(timeField)
             .from($scope.old_time.from)
             .to($scope.old_time.to)
           ));
         request = request
-          .facet($scope.ejs.QueryFacet("old_"+id)
+          .facet($scope.ejs.QueryFacet("old_"+query.id)
             .query(q)
           ).size(0);
       });
@@ -169,17 +169,14 @@ function (angular, app, _, kbn) {
           return;
         }
 
-        // Convert facet ids to numbers
-        var facetIds = _.map(_.keys(results.facets),function(k){if(!isNaN(k)){return parseInt(k, 10);}});
-
         // Make sure we're still on the same query/queries
-        if($scope.query_id === query_id &&
-          _.intersection(facetIds,$scope.panel.queries.ids).length === $scope.panel.queries.ids.length
-          ) {
+        if($scope.query_id === query_id) {
           var i = 0;
-          _.each($scope.panel.queries.ids, function(id) {
-            var n = results.facets[id].count;
-            var o = results.facets['old_'+id].count;
+          var queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
+          _.each(queries, function(query) {
+            var n = results.facets[query.id].count;
+            var o = results.facets['old_'+query.id].count;
 
             var hits = {
               new : _.isUndefined($scope.data[i]) || _segment === 0 ? n : $scope.data[i].hits.new+n,
@@ -193,7 +190,7 @@ function (angular, app, _, kbn) {
               '?' : Math.round(percentage(hits.old,hits.new)*100)/100;
             // Create series
             $scope.data[i] = {
-              info: querySrv.list[id],
+              info: query,
               hits: {
                 new : hits.new,
                 old : hits.old

+ 1 - 1
src/app/partials/dashboard.html

@@ -54,7 +54,7 @@
             <div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.span == 0 || panel.hide" class="span{{panel.span}} panel nospace" style="min-height:{{row.height}}; position:relative" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}">
               <!-- Error Panel -->
               <div class="row-fluid">
-                <div class="span12 alert alert-error panel-error" ng-hide="!panel.error">
+                <div class="span12 alert-error panel-error" ng-hide="!panel.error">
                   <a class="close" ng-click="panel.error=false">&times;</a>
                   <i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
                 </div>

+ 4 - 3
src/app/services/dashboard.js

@@ -143,12 +143,13 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
                 return false;
               }
             }
-            $rootScope.$broadcast('refresh');
+            // Don't resolve queries until indices are updated
+            querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');});
           });
         } else {
           if(self.current.failover) {
             self.indices = [self.current.index.default];
-            $rootScope.$broadcast('refresh');
+            querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');});
           } else {
             alertSrv.set("No time filter",
               'Timestamped indices are configured without a failover. Waiting for time filter.',
@@ -157,7 +158,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
         }
       } else {
         self.indices = [self.current.index.default];
-        $rootScope.$broadcast('refresh');
+        querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');});
       }
     };
 

+ 2 - 2
src/app/services/fields.js

@@ -19,7 +19,7 @@ function (angular, _, config) {
       if(!_.isUndefined(n) && n.length) {
         // Only get the mapping for indices we don't know it for
         var indices = _.difference(n,_.keys(self.mapping));
-        // Only get the mapping if there are indices
+        // Only get the mapping if there are new indices
         if(indices.length > 0) {
           self.map(indices).then(function(result) {
             self.mapping = _.extend(self.mapping,result);
@@ -36,7 +36,7 @@ function (angular, _, config) {
       var fields = [];
       _.each(m, function(types) {
         _.each(types, function(v) {
-          fields = _.union(fields,_.keys(v));
+          fields = _.without(_.union(fields,_.keys(v)),'_all','_source');
         });
       });
       return fields;

+ 144 - 38
src/app/services/querySrv.js

@@ -1,14 +1,16 @@
 define([
   'angular',
   'underscore',
-  'config'
+  'config',
+  'kbn'
 ],
-function (angular, _, config) {
+function (angular, _, config, kbn) {
   'use strict';
 
   var module = angular.module('kibana.services');
 
-  module.service('querySrv', function(dashboard, ejsResource) {
+  module.service('querySrv', function(dashboard, ejsResource, filterSrv, $q) {
+
     // Create an object to hold our service state on the dashboard
     dashboard.current.services.query = dashboard.current.services.query || {};
     _.defaults(dashboard.current.services.query,{
@@ -17,18 +19,6 @@ function (angular, _, config) {
       ids : [],
     });
 
-    // Defaults for query objects
-    var _query = {
-      query: '*',
-      alias: '',
-      pin: false,
-      type: 'lucene'
-    };
-
-    // For convenience
-    var ejs = ejsResource(config.elasticsearch);
-    var _q = dashboard.current.services.query;
-
     this.colors = [
       "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
       "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
@@ -39,25 +29,112 @@ function (angular, _, config) {
       "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"  //7
     ];
 
-    // Define the query types and the version of elasticsearch they were first available in
-    this.queryTypes = [
-      {name:'lucene',require:">=0.17.0"},
-      {name:'regex',require:">=0.90.3"}
-    ];
+    // For convenience
+    var ejs = ejsResource(config.elasticsearch);
+    var _q = dashboard.current.services.query;
 
+    // Holds all actual queries, including all resolved abstract queries
+    var resolvedQueries = [];
+
+    // Defaults for generic query object
+    var _query = {
+      alias: '',
+      pin: false,
+      type: 'lucene'
+    };
+
+    // Defaults for specific query types
+    var _dTypes = {
+      "lucene": {
+        query: "*"
+      },
+      "regex": {
+        query: ".*"
+      },
+      "topN": {
+        query: "*",
+        field: "_type",
+        size: 5,
+        union: 'AND'
+      }
+    };
+
+    // query type meta data that is not stored on the dashboard object
+    this.queryTypes = {
+      lucene: {
+        require:">=0.17.0",
+        icon: "icon-circle",
+        resolve: function(query) {
+          // Simply returns itself
+          var p = $q.defer();
+          p.resolve(_.extend(query,{parent:query.id}));
+          return p.promise;
+        }
+      },
+      regex: {
+        require:">=0.90.3",
+        icon: "icon-circle",
+        resolve: function(query) {
+          // Simply returns itself
+          var p = $q.defer();
+          p.resolve(_.extend(query,{parent:query.id}));
+          return p.promise;
+        }
+      },
+      topN : {
+        require:">=0.90.3",
+        icon: "icon-cog",
+        resolve: function(q) {
+          var suffix = '';
+          if (q.union === 'AND') {
+            suffix = ' AND (' + (q.query||'*') + ')';
+          } else if (q.union === 'OR') {
+            suffix = ' OR (' + (q.query||'*') + ')';
+          }
+
+          var request = ejs.Request().indices(dashboard.indices);
+          // Terms mode
+          request = request
+            .facet(ejs.TermsFacet('query')
+              .field(q.field)
+              .size(q.size)
+              .facetFilter(ejs.QueryFilter(
+                ejs.FilteredQuery(
+                  ejs.QueryStringQuery(q.query || '*'),
+                  filterSrv.getBoolFilter(filterSrv.ids)
+                  )))).size(0);
+
+          var results = request.doSearch();
+          return results.then(function(data) {
+            var _colors = kbn.colorSteps(q.color,data.facets.query.terms.length);
+            var i = -1;
+            return _.map(data.facets.query.terms,function(t) {
+              ++i;
+              return self.defaults({
+                query  : q.field+':"'+t.term+'"'+suffix,
+                alias  : t.term + (q.alias ? " ("+q.alias+")" : ""),
+                type   : 'lucene',
+                color  : _colors[i],
+                parent : q.id
+              });
+            });
+          });
+        }
+      }
+    };
 
     // Save a reference to this
     var self = this;
 
     this.init = function() {
       _q = dashboard.current.services.query;
+
       self.list = dashboard.current.services.query.list;
       self.ids = dashboard.current.services.query.ids;
 
       // Check each query object, populate its defaults
-      _.each(self.list,function(query,id) {
-        _.defaults(query,_query);
-        query.color = query.color || colorAt(id);
+      _.each(self.list,function(query) {
+        query = self.defaults(query);
       });
 
       if (self.ids.length === 0) {
@@ -75,17 +152,23 @@ function (angular, _, config) {
           return false;
         }
       } else {
-        var _id = query.id || nextId();
-        query.id = _id;
-        query.color = query.color || colorAt(_id);
-        _.defaults(query,_query);
-
-        self.list[_id] = query;
-        self.ids.push(_id);
-        return _id;
+        // Query must have an id and color already
+        query.id = _.isUndefined(query.id) ? nextId() : query.id;
+        query.color = query.color || colorAt(query.id);
+        // Then it can get defaults
+        query = self.defaults(query);
+        self.list[query.id] = query;
+        self.ids.push(query.id);
+        return query.id;
       }
     };
 
+    this.defaults = function(query) {
+      _.defaults(query,_query);
+      _.defaults(query,_dTypes[query.type]);
+      return query;
+    };
+
     this.remove = function(id) {
       if(!_.isUndefined(self.list[id])) {
         delete self.list[id];
@@ -101,10 +184,8 @@ function (angular, _, config) {
       }
     };
 
-    this.getEjsObj = function(id) {
-      return self.toEjsObj(self.list[id]);
-    };
-
+    // In the case of a compound query, such as a derived query, we'd need to
+    // return an array of elasticJS objects. Not sure if that is appropriate?
     this.toEjsObj = function (q) {
       switch(q.type)
       {
@@ -113,14 +194,22 @@ function (angular, _, config) {
       case 'regex':
         return ejs.RegexpQuery('_all',q.query);
       default:
-        return _.isUndefined(q.query) ? false : ejs.QueryStringQuery(q.query || '*');
+        return false;
       }
     };
 
-    this.findQuery = function(queryString) {
-      return _.findWhere(self.list,{query:queryString});
+    //
+    this.getQueryObjs = function(ids) {
+      if(_.isUndefined(ids)) {
+        return resolvedQueries;
+      } else {
+        return _.flatten(_.map(ids,function(id) {
+          return _.where(resolvedQueries,{parent:id});
+        }));
+      }
     };
 
+    // BROKEN
     this.idsByMode = function(config) {
       switch(config.mode)
       {
@@ -137,6 +226,23 @@ function (angular, _, config) {
       }
     };
 
+    // This populates the internal query list and returns a promise containing it
+    this.resolve = function() {
+      // Find ids of all abstract queries
+      // Get a list of resolvable ids, constrast with total list to get abstract ones
+      return $q.all(_.map(self.ids,function(q) {
+        return self.queryTypes[self.list[q].type].resolve(_.clone(self.list[q])).then(function(data){
+          return data;
+        });
+      })).then(function(data) {
+        resolvedQueries = _.flatten(data);
+        _.each(resolvedQueries,function(q,i) {
+          q.id = i;
+        });
+        return resolvedQueries;
+      });
+    };
+
     var nextId = function() {
       if(_q.idQueue.length > 0) {
         return _q.idQueue.shift();

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/css/bootstrap.dark.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/css/bootstrap.light.min.css


BIN
src/img/small.png


+ 1 - 1
src/index.html

@@ -34,7 +34,7 @@
     <div class="navbar navbar-static-top">
       <div class="navbar-inner">
         <div class="container-fluid">
-          <span class="brand">{{dashboard.current.title}}</span>
+          <span class="brand"><img src="img/small.png" bs-tooltip="'Kibana 3 milestone 4'" 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>

+ 2 - 2
src/vendor/bootstrap/bootstrap.js

@@ -2085,7 +2085,7 @@
     }
 
   , keydown: function (e) {
-      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27,57])
       this.move(e)
     }
 
@@ -2115,7 +2115,7 @@
           break
 
         default:
-          this.lookup()
+          this.lookup();
       }
 
       if(e.keyCode === 13 && typeof this.$menu.find('.active').attr('data-value') === 'undefined') {

+ 16 - 6
src/vendor/bootstrap/less/overrides.less

@@ -16,6 +16,10 @@ div.fake-input {
   .border-radius(@inputBorderRadius @inputBorderRadius @inputBorderRadius @inputBorderRadius);
 }
 
+hr.small {
+  margin: 5px 0px;
+}
+
 form input.ng-invalid {
   color: @errorText;
 }
@@ -37,6 +41,13 @@ form input.ng-invalid {
   border: 1px solid @tableBorder;
 }
 
+.table-unpadded {
+  th,
+  td {
+    padding: 0px 2px;
+  }
+}
+
 .spy {
   position:absolute;
   right:0px;
@@ -121,12 +132,6 @@ form input.ng-invalid {
   border-top: 0px;
 }
 
-.panel-error {
-  opacity: 0.9;
-  position:absolute;
-  z-index: 1000;
-}
-
 .panel-loading {
   position:absolute;
   top: 0px;
@@ -264,6 +269,11 @@ div.flot-text {
   padding-left:20px;
 }
 
+.panel-error {
+  color: @textColor;
+  padding: 3px 10px 0px 10px;
+}
+
 /* ===================================================
  * popover-extra-placements.css v0.1
  * http://twitter.github.com/bootstrap-popover-extra-placements

+ 34 - 0
src/vendor/chromath.js

@@ -0,0 +1,34 @@
+(function(){function a(d){var c,b,e,f;if("[object String]"===Object.prototype.toString.call(d)||"[object Number]"===Object.prototype.toString.call(d))c=a.parse(d);else{if(m(d))throw Error("Unsure how to parse array `"+d+"`, please pass an object or CSS style or try Chromath.rgb, Chromath.hsl, or Chromath.hsv");d instanceof a?c=k({},d):n(d)&&(c=k({},d))}if(c)isFinite(c.a)||(c.a=1);else throw Error("Could not parse `"+d+"`");if("r"in c)f=[c.r,c.g,c.b],b=a.rgb2hsl(f),e=a.rgb2hsv(f);else if("h"in c)if("l"in
+c)b=[c.h,c.s,c.l],f=a.hsl2rgb(b),e=a.rgb2hsv(f);else if("v"in c||"b"in c)"b"in c&&(c.v=c.b),e=[c.h,c.s,c.v],f=a.hsv2rgb(e),b=a.rgb2hsl(f);k(this,{r:f[0],g:f[1],b:f[2],h:b[0],sl:b[1],l:b[2],sv:e[1],v:e[2],a:c.a});this.h=(this.h%360+360)%360;1<this.sl&&(this.sl/=100);1<this.sv&&(this.sv/=100);1<this.l&&(this.l/=100);this.r=h(parseInt(this.r,10),0,255);this.g=h(parseInt(this.g,10),0,255);this.b=h(parseInt(this.b,10),0,255);this.a=h(this.a,0,1);return this}function q(d,a,b,e){var f=d;m(f)&&(d=f[0],a=
+f[1],b=f[2],e=f[3]);n(f)&&(d=f.r,a=f.g,b=f.b,e=f.a);return[d,a,b,e]}function p(d,a,b,e){var f=d;m(f)&&(d=f[0],a=f[1],b=f[2],e=f[3]);n(f)&&(d=f.h,a=f.s,b=f.l||f.v,e=f.a);return[d,a,b,e]}function r(a,c,b){isFinite(c)||(b=q(a,c,b),a=b[0],c=b[1],b=b[2]);1<a&&(a/=255);1<c&&(c/=255);1<b&&(b/=255);return[a,c,b]}function s(a,c,b){isFinite(c)||(b=p(a,c,b),a=b[0],c=b[1],b=b[2]);1<c&&(c/=100);1<b&&(b/=100);return[(a%360+360)%360,c,b]}function k(){for(var a=arguments[0],c=1,b,e;b=arguments[c++];)for(e in b)a[e]=
+b[e];return a}function m(a){return"[object Array]"===Object.prototype.toString.call(a)}function n(a){return"[object Object]"===Object.prototype.toString.call(a)}function h(a,c,b){return a>b?b:a<c?c:a}function l(a,c,b){return a+(c-a)*b}a.rgb=function(d,c,b,e){e=q(d,c,b,e);d=e[0];c=e[1];b=e[2];e=e[3];return new a({r:d,g:c,b:b,a:e})};a.rgba=a.rgb;a.hsl=function(d,c,b,e){e=p(d,c,b,e);d=e[0];c=e[1];b=e[2];e=e[3];return new a({h:d,s:c,l:b,a:e})};a.hsla=a.hsl;a.hsv=function(d,c,b,e){e=p(d,c,b,e);d=e[0];
+c=e[1];b=e[2];e=e[3];return new a({h:d,s:c,v:b,a:e})};a.hsva=a.hsv;a.hsb=a.hsv;a.hsba=a.hsva;a.toInteger=function(d){d=(new a(d)).toRGBObject();return d.b|d.g<<8|d.r<<16};a.toName=function(d){var d=+new a(d),c;for(c in a.colors)if(+a[c]==d)return c};a.rgb2hex=function(d,c,b){d=a.toInteger({r:d,g:c,b:b}).toString(16).toUpperCase();c=6;b=0;d=d.toString();c||(c=2);for(b||(b="0");d.length<c;)d=b+d;return"#"+d};a.rgb2hsl=function(a,c,b){var b=r(a,c,b),a=b[0],c=b[1],b=b[2],e=Math.max(a,c,b),f=Math.min(a,
+c,b),g=e-f,f=0.5*(e+f),h=0===g?0:g/(1-Math.abs(2*f-1)),i;0===g?i=0:e===a?i=(c-b)/g%6:e===c?i=(b-a)/g+2:e===b&&(i=(a-c)/g+4);return[60*i,parseFloat(h),parseFloat(f)]};a.rgb2hsv=function(a,c,b){var b=r(a,c,b),a=b[0],c=b[1],b=b[2],e=Math.max(a,c,b),f=Math.min(a,c,b),f=e-f,g;0===f?g=0:e===a?g=(c-b)/f%6:e===c?g=(b-a)/f+2:e===b&&(g=(a-c)/f+4);return[60*g,parseFloat(0===f?0:f/e),parseFloat(e)]};a.rgb2hsb=a.rgb2hsv;a.hsl2rgb=function(a,c,b){var b=s(a,c,b),a=b[0],c=b[1],b=b[2],c=(1-Math.abs(2*b-1))*c,a=a/
+60,e=c*(1-Math.abs(a%2-1));switch(Math.floor(a)){case 0:a=[c,e,0];break;case 1:a=[e,c,0];break;case 2:a=[0,c,e];break;case 3:a=[0,e,c];break;case 4:a=[e,0,c];break;case 5:a=[c,0,e];break;default:a=[0,0,0]}b-=c/2;return[255*(a[0]+b),255*(a[1]+b),255*(a[2]+b)]};a.hsv2rgb=function(a,c,b){var b=s(a,c,b),a=b[0],c=b[1],b=b[2],c=b*c,e=a/60,f=c*(1-Math.abs(e%2-1)),g;void 0==a?g=[0,0,0]:0<=e&&1>e?g=[c,f,0]:1<=e&&2>e?g=[f,c,0]:2<=e&&3>e?g=[0,c,f]:3<=e&&4>e?g=[0,f,c]:4<=e&&5>e?g=[f,0,c]:5<=e&&6>e&&(g=[c,0,f]);
+a=b-c;return[255*(g[0]+a),255*(g[1]+a),255*(g[2]+a)]};a.hsb2rgb=a.hsv2rgb;a.complement=function(d){d=(new a(d)).toHSLObject();d.h=(d.h+180)%360;return new a(d)};a.triad=function(d){d=new a(d);return[d,new a({r:d.b,g:d.r,b:d.g}),new a({r:d.g,g:d.b,b:d.r})]};a.tetrad=function(d){d=new a(d);return[d,new a({r:d.b,g:d.r,b:d.b}),new a({r:d.b,g:d.g,b:d.r}),new a({r:d.r,g:d.b,b:d.r})]};a.analogous=function(d,c,b){isFinite(c)||(c=8);isFinite(b)||(b=30);var e=new a(d),d=e.toHSVObject(),b=360/b,e=[e];for(d.h=
+(d.h-(b*c>>1)+720)%360;--c;)d.h+=b,d.h%=360,e.push(new a(d));return e};a.monochromatic=function(d,c){c||(c=5);for(var b=(new a(d)).toHSVObject(),e=1/c,f=[],g=0;g++<c;)b.v=g*e,f.push(new a(b));return f};a.splitcomplement=function(d){var c=new a(d),d=c.toHSVObject(),c=[c];d.h*=0.2;d.h%=360;c.push(new a(d));d.h*=0.4;d.h%=360;c.push(new a(d));return c};a.tint=function(d,c){return a.towards(d,"#FFFFFF",c)};a.lighten=a.tint;a.shade=function(d,c){return a.towards(d,"#000000",c)};a.darken=a.shade;a.desaturate=
+function(d,c){var b=new a(d);switch(c){case 1:b=0.35+13*(b.r+b.g+b.b)/60;break;case 2:b=(13*(b.r+b.g+b.b)+5355)/60;break;default:b=0.3*b.r+0.59*b.g+0.11*b.b}b=h(b,0,255);return new a({r:b,g:b,b:b})};a.greyscale=a.desaturate;a.websafe=function(d){d=new a(d);d.r=51*Math.round(d.r/51);d.g=51*Math.round(d.g/51);d.b=51*Math.round(d.b/51);return new a(d)};a.additive=function(){for(var d=arguments.length-2,c=-1,b,e;c++<d;){b=b||new a(arguments[c]);e=new a(arguments[c+1]);if(255<(b.r+=e.r))b.r=255;if(255<
+(b.g+=e.g))b.g=255;if(255<(b.b+=e.b))b.b=255;b=new a(b)}return b};a.subtractive=function(){for(var d=arguments.length-2,c=-1,b,e;c++<d;){b=b||new a(arguments[c]);e=new a(arguments[c+1]);if(0>(b.r+=e.r-255))b.r=0;if(0>(b.g+=e.g-255))b.g=0;if(0>(b.b+=e.b-255))b.b=0;b=new a(b)}return b};a.multiply=function(){for(var d=arguments.length-2,c=-1,b,e;c++<d;)b=b||new a(arguments[c]),e=new a(arguments[c+1]),b.r=b.r/255*e.r|0,b.g=b.g/255*e.g|0,b.b=b.b/255*e.b|0,b=new a(b);return b};a.average=function(){for(var d=
+arguments.length-2,c=-1,b,e;c++<d;)b=b||new a(arguments[c]),e=new a(arguments[c+1]),b.r=b.r+e.r>>1,b.g=b.g+e.g>>1,b.b=b.b+e.b>>1,b=new a(b);return b};a.overlay=function(d,c,b){d=new a(d);c=new a(c);1<b&&(b/=100);b=h(b-1+c.a,0,1);return new a({r:l(d.r,c.r,b),g:l(d.g,c.g,b),b:l(d.b,c.b,b)})};a.towards=function(d,c,b,e){if(!c)return d;if(!isFinite(b))throw Error("TypeError: `by`("+b+") should be between 0 and 1");d instanceof a||(d=new a(d));c instanceof a||(c=new a(c||"#FFFFFF"));e||(e=l);b=parseFloat(b);
+return new a({r:e(d.r,c.r,b),g:e(d.g,c.g,b),b:e(d.b,c.b,b),a:e(d.a,c.a,b)})};a.gradient=function(d,c,b,e){var f=[],g;b||(b=20);g=b-1;if(isFinite(e))return a.towards(d,c,e/g);for(e=-1;++e<b;)f.push(a.towards(d,c,e/g));return f};a.parse=function(d){var c=a.parsers,b,e,f,g,h;b=0;for(e=c.length;b<e;b++)if(f=c[b],(g=f.regex.exec(d))&&g.length&&(h=f.process.apply(this,g)),h)return h};a.parsers=[{example:["red","burlywood"],regex:/^[a-z]+$/i,process:function(d){if(a.colors[d])return a.colors[d]}},{example:[3554431,
+16809984],regex:/^\d+$/,process:function(a){return{r:a>>16&255,g:a>>8&255,b:a&255}}},{example:["#fb0","f0f"],regex:/^#?([\dA-F]{1})([\dA-F]{1})([\dA-F]{1})$/i,process:function(a,c,b,e){return{r:parseInt(c+c,16),g:parseInt(b+b,16),b:parseInt(e+e,16)}}},{example:["#00ff00","336699"],regex:/^#?([\dA-F]{2})([\dA-F]{2})([\dA-F]{2})$/i,process:function(a,c,b,e){return{r:parseInt(c,16),g:parseInt(b,16),b:parseInt(e,16)}}},{example:["rgb(123, 234, 45)","rgb(25, 50%, 100%)","rgba(12%, 34, 56%, 0.78)"],regex:/^rgba*\((\d{1,3}\%*),\s*(\d{1,3}\%*),\s*(\d{1,3}\%*)(?:,\s*([0-9.]+))?\)/,
+process:function(a,c,b,e,f){c=c&&"%"==c.slice(-1)?Math.round(2.55*c.slice(0,-1)):1*c;b=b&&"%"==b.slice(-1)?Math.round(2.55*b.slice(0,-1)):1*b;e=e&&"%"==e.slice(-1)?Math.round(2.55*e.slice(0,-1)):1*e;f=f&&"%"==f.slice(-1)?Math.round(100*f.slice(0,-1)):1*f;return{r:h(c,0,255),g:h(b,0,255),b:h(e,0,255),a:h(f,0,1)||void 0}}},{example:["hsl(123, 34%, 45%)","hsla(25, 50%, 100%, 0.75)","hsv(12, 34%, 56%)"],regex:/^hs([bvl])a*\((\d{1,3}\%*),\s*(\d{1,3}\%*),\s*(\d{1,3}\%*)(?:,\s*([0-9.]+))?\)/,process:function(a,
+c,b,e,f){b*=1;e=e.slice(0,-1)/100;f=f.slice(0,-1)/100;a={h:h(b,0,360),s:h(e,0,1),a:h(f,0,1)};a[c]=h(f,0,1);return a}}];a.colors={aqua:new a({r:0,g:255,b:255}),black:new a({r:0,g:0,b:0}),blue:new a({r:0,g:0,b:255}),fuchsia:new a({r:255,g:0,b:255}),gray:new a({r:128,g:128,b:128}),green:new a({r:0,g:128,b:0}),lime:new a({r:0,g:255,b:0}),maroon:new a({r:128,g:0,b:0}),navy:new a({r:0,g:0,b:128}),olive:new a({r:128,g:128,b:0}),purple:new a({r:128,g:0,b:128}),red:new a({r:255,g:0,b:0}),silver:new a({r:192,
+g:192,b:192}),teal:new a({r:0,g:128,b:128}),white:new a({r:255,g:255,b:255}),yellow:new a({r:255,g:255,b:0}),aliceblue:new a({r:240,g:248,b:255}),antiquewhite:new a({r:250,g:235,b:215}),aquamarine:new a({r:127,g:255,b:212}),azure:new a({r:240,g:255,b:255}),beige:new a({r:245,g:245,b:220}),bisque:new a({r:255,g:228,b:196}),blanchedalmond:new a({r:255,g:235,b:205}),blueviolet:new a({r:138,g:43,b:226}),brown:new a({r:165,g:42,b:42}),burlywood:new a({r:222,g:184,b:135}),cadetblue:new a({r:95,g:158,b:160}),
+chartreuse:new a({r:127,g:255,b:0}),chocolate:new a({r:210,g:105,b:30}),coral:new a({r:255,g:127,b:80}),cornflowerblue:new a({r:100,g:149,b:237}),cornsilk:new a({r:255,g:248,b:220}),crimson:new a({r:220,g:20,b:60}),cyan:new a({r:0,g:255,b:255}),darkblue:new a({r:0,g:0,b:139}),darkcyan:new a({r:0,g:139,b:139}),darkgoldenrod:new a({r:184,g:134,b:11}),darkgray:new a({r:169,g:169,b:169}),darkgreen:new a({r:0,g:100,b:0}),darkgrey:new a({r:169,g:169,b:169}),darkkhaki:new a({r:189,g:183,b:107}),darkmagenta:new a({r:139,
+g:0,b:139}),darkolivegreen:new a({r:85,g:107,b:47}),darkorange:new a({r:255,g:140,b:0}),darkorchid:new a({r:153,g:50,b:204}),darkred:new a({r:139,g:0,b:0}),darksalmon:new a({r:233,g:150,b:122}),darkseagreen:new a({r:143,g:188,b:143}),darkslateblue:new a({r:72,g:61,b:139}),darkslategray:new a({r:47,g:79,b:79}),darkslategrey:new a({r:47,g:79,b:79}),darkturquoise:new a({r:0,g:206,b:209}),darkviolet:new a({r:148,g:0,b:211}),deeppink:new a({r:255,g:20,b:147}),deepskyblue:new a({r:0,g:191,b:255}),dimgray:new a({r:105,
+g:105,b:105}),dimgrey:new a({r:105,g:105,b:105}),dodgerblue:new a({r:30,g:144,b:255}),firebrick:new a({r:178,g:34,b:34}),floralwhite:new a({r:255,g:250,b:240}),forestgreen:new a({r:34,g:139,b:34}),gainsboro:new a({r:220,g:220,b:220}),ghostwhite:new a({r:248,g:248,b:255}),gold:new a({r:255,g:215,b:0}),goldenrod:new a({r:218,g:165,b:32}),greenyellow:new a({r:173,g:255,b:47}),grey:new a({r:128,g:128,b:128}),honeydew:new a({r:240,g:255,b:240}),hotpink:new a({r:255,g:105,b:180}),indianred:new a({r:205,
+g:92,b:92}),indigo:new a({r:75,g:0,b:130}),ivory:new a({r:255,g:255,b:240}),khaki:new a({r:240,g:230,b:140}),lavender:new a({r:230,g:230,b:250}),lavenderblush:new a({r:255,g:240,b:245}),lawngreen:new a({r:124,g:252,b:0}),lemonchiffon:new a({r:255,g:250,b:205}),lightblue:new a({r:173,g:216,b:230}),lightcoral:new a({r:240,g:128,b:128}),lightcyan:new a({r:224,g:255,b:255}),lightgoldenrodyellow:new a({r:250,g:250,b:210}),lightgray:new a({r:211,g:211,b:211}),lightgreen:new a({r:144,g:238,b:144}),lightgrey:new a({r:211,
+g:211,b:211}),lightpink:new a({r:255,g:182,b:193}),lightsalmon:new a({r:255,g:160,b:122}),lightseagreen:new a({r:32,g:178,b:170}),lightskyblue:new a({r:135,g:206,b:250}),lightslategray:new a({r:119,g:136,b:153}),lightslategrey:new a({r:119,g:136,b:153}),lightsteelblue:new a({r:176,g:196,b:222}),lightyellow:new a({r:255,g:255,b:224}),limegreen:new a({r:50,g:205,b:50}),linen:new a({r:250,g:240,b:230}),magenta:new a({r:255,g:0,b:255}),mediumaquamarine:new a({r:102,g:205,b:170}),mediumblue:new a({r:0,
+g:0,b:205}),mediumorchid:new a({r:186,g:85,b:211}),mediumpurple:new a({r:147,g:112,b:219}),mediumseagreen:new a({r:60,g:179,b:113}),mediumslateblue:new a({r:123,g:104,b:238}),mediumspringgreen:new a({r:0,g:250,b:154}),mediumturquoise:new a({r:72,g:209,b:204}),mediumvioletred:new a({r:199,g:21,b:133}),midnightblue:new a({r:25,g:25,b:112}),mintcream:new a({r:245,g:255,b:250}),mistyrose:new a({r:255,g:228,b:225}),moccasin:new a({r:255,g:228,b:181}),navajowhite:new a({r:255,g:222,b:173}),oldlace:new a({r:253,
+g:245,b:230}),olivedrab:new a({r:107,g:142,b:35}),orange:new a({r:255,g:165,b:0}),orangered:new a({r:255,g:69,b:0}),orchid:new a({r:218,g:112,b:214}),palegoldenrod:new a({r:238,g:232,b:170}),palegreen:new a({r:152,g:251,b:152}),paleturquoise:new a({r:175,g:238,b:238}),palevioletred:new a({r:219,g:112,b:147}),papayawhip:new a({r:255,g:239,b:213}),peachpuff:new a({r:255,g:218,b:185}),peru:new a({r:205,g:133,b:63}),pink:new a({r:255,g:192,b:203}),plum:new a({r:221,g:160,b:221}),powderblue:new a({r:176,
+g:224,b:230}),rosybrown:new a({r:188,g:143,b:143}),royalblue:new a({r:65,g:105,b:225}),saddlebrown:new a({r:139,g:69,b:19}),salmon:new a({r:250,g:128,b:114}),sandybrown:new a({r:244,g:164,b:96}),seagreen:new a({r:46,g:139,b:87}),seashell:new a({r:255,g:245,b:238}),sienna:new a({r:160,g:82,b:45}),skyblue:new a({r:135,g:206,b:235}),slateblue:new a({r:106,g:90,b:205}),slategray:new a({r:112,g:128,b:144}),slategrey:new a({r:112,g:128,b:144}),snow:new a({r:255,g:250,b:250}),springgreen:new a({r:0,g:255,
+b:127}),steelblue:new a({r:70,g:130,b:180}),tan:new a({r:210,g:180,b:140}),thistle:new a({r:216,g:191,b:216}),tomato:new a({r:255,g:99,b:71}),turquoise:new a({r:64,g:224,b:208}),violet:new a({r:238,g:130,b:238}),wheat:new a({r:245,g:222,b:179}),whitesmoke:new a({r:245,g:245,b:245}),yellowgreen:new a({r:154,g:205,b:50})};a.prototype={toName:function(){return a.toName(this)},toString:function(){return this.toHexString()},valueOf:function(){return a.toInteger(this)},rgb:function(){return this.toRGBArray()},
+toRGBArray:function(){return this.toRGBAArray().slice(0,3)},toRGBObject:function(){var a=this.toRGBArray();return{r:a[0],g:a[1],b:a[2]}},toRGBString:function(){return"rgb("+this.toRGBArray().join(",")+")"},rgba:function(){return this.toRGBAArray()},toRGBAArray:function(){return[Math.round(this.r),Math.round(this.g),Math.round(this.b),parseFloat(this.a)]},toRGBAObject:function(){var a=this.toRGBAArray();return{r:a[0],g:a[1],b:a[2],a:a[3]}},toRGBAString:function(){return"rgba("+this.toRGBAArray().join(",")+
+")"},hex:function(){return this.toHexArray()},toHexArray:function(){return this.toHexString().slice(1).match(/([\dA-F]{2})/ig)},toHexObject:function(){var a=this.toHexArray();return{r:a[0],g:a[1],b:a[2]}},toHexString:function(){return a.rgb2hex(this.r,this.g,this.b)},hsl:function(){return this.toHSLArray()},toHSLArray:function(){return this.toHSLAArray().slice(0,3)},toHSLObject:function(){var a=this.toHSLArray();return{h:a[0],s:a[1],l:a[2]}},toHSLString:function(){return"hsl("+this.toHSLArray().join(",")+
+")"},hsla:function(){return this.toHSLAArray()},toHSLAArray:function(){return[Math.round(this.h),parseFloat(this.sl),parseFloat(this.l),parseFloat(this.a)]},toHSLAObject:function(){var a=this.toHSLArray();return{h:a[0],s:a[1],l:a[2],a:a[3]}},toHSLAString:function(){return"hsla("+this.toHSLAArray().join(",")+")"},hsv:function(){return this.toHSVArray()},toHSVArray:function(){return[Math.round(this.h),parseFloat(this.sv),parseFloat(this.v)]},toHSVObject:function(){return{h:Math.round(this.h),s:parseFloat(this.sv),
+v:parseFloat(this.v)}},toHSVString:function(){var a=this.toHSVArray();return"hsv("+[a[0],100*a[1]+"%",100*a[2]+"%"]+")"},hsva:function(){return this.toHSVAArray()},toHSVAArray:function(){return[Math.round(this.h),parseFloat(this.sv),parseFloat(this.v),parseFloat(this.a)]},toHSVAObject:function(){var a=this.toHSVAArray();return{h:a[0],s:a[1],l:a[2],a:a[3]}},toHSVAString:function(){var a=this.toHSVAArray();return"hsva("+[a[0],100*a[1]+"%",100*a[2]+"%",a[3]]+")"},hsb:function(){return this.hsv()},toHSBArray:function(){return this.toHSVArray()},
+toHSBObject:function(){return this.toHSVObject()},toHSBString:function(){return this.toHSVString()},hsba:function(){return this.hsva()},toHSBAArray:function(){return this.toHSVAArray()},toHSBAObject:function(){return this.toHSVAObject()},toHSBAString:function(){return this.toHSVAString()},complement:function(){return a.complement(this)},triad:function(){return a.triad(this)},tetrad:function(){return a.tetrad(this)},analogous:function(d,c){return a.analogous(this,d,c)},monochromatic:function(d){return a.monochromatic(this,
+d)},splitcomplement:function(){return a.splitcomplement(this)},tint:function(d){return a.tint(this,d)},lighten:function(a){return this.tint(a)},shade:function(d){return a.shade(this,d)},darken:function(a){return this.shade(a)},desaturate:function(d){return a.desaturate(this,d)},greyscale:function(a){return this.desaturate(a)},websafe:function(){return a.websafe(this)},additive:function(){var d=Array.prototype.slice.call(arguments);return a.additive.apply(a,[this].concat(d))},subtractive:function(){var d=
+Array.prototype.slice.call(arguments);return a.subtractive.apply(a,[this].concat(d))},multiply:function(){var d=Array.prototype.slice.call(arguments);return a.multiply.apply(a,[this].concat(d))},average:function(){var d=Array.prototype.slice.call(arguments);return a.average.apply(a,[this].concat(d))},overlay:function(d,c){return a.overlay(this,d,c)},clone:function(){return new a(this)},towards:function(d,c){return a.towards(this,d,c)},gradient:function(d,c,b){return a.gradient(this,d,c,b)}};k(a,a.colors);
+var j="Chromath",i=a,t,u;"string"!==typeof j&&(i=j,j=null);t=function(){return this}.call(null);i=i||{};"undefined"!==typeof module&&("undefined"!==typeof exports&&module.exports)&&(u=!0,module.exports=exports=i);"undefined"!==typeof window&&!u&&(j&&i)&&(t[j]=i)})();

Некоторые файлы не были показаны из-за большого количества измененных файлов