You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by om...@apache.org on 2015/12/08 07:37:51 UTC

[27/51] [partial] incubator-metron git commit: Initial import of code from https://github.com/OpenSOC/opensoc at ac0b00373f8f56dfae03a8109af5feb373ea598e.

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/goal/module.js
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/goal/module.js b/opensoc-ui/lib/public/app/panels/goal/module.js
new file mode 100755
index 0000000..544fa05
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/goal/module.js
@@ -0,0 +1,259 @@
+/** @scratch /panels/5
+ *
+ * include::panels/goal.asciidoc[]
+ */
+
+/** @scratch /panels/goal/0
+ *
+ * == Goal
+ * Status: *Stable*
+ *
+ * The goal panel display progress towards a fixed goal on a pie chart
+ *
+ */
+define([
+  'angular',
+  'app',
+  'lodash',
+  'jquery',
+  'kbn',
+  'config',
+  'chromath'
+], function (angular, app, _, $, kbn) {
+  'use strict';
+
+  var module = angular.module('kibana.panels.goal', []);
+  app.useModule(module);
+
+  module.controller('goal', function($scope, $rootScope, querySrv, dashboard, filterSrv) {
+
+    $scope.panelMeta = {
+      editorTabs : [
+        {title:'Queries', src:'app/partials/querySelect.html'}
+      ],
+      modals : [
+        {
+          description: "Inspect",
+          icon: "icon-info-sign",
+          partial: "app/partials/inspector.html",
+          show: $scope.panel.spyable
+        }
+      ],
+      status  : "Stable",
+      description : "Displays the progress towards a fixed goal on a pie chart"
+    };
+
+    // Set and populate defaults
+    var _d = {
+      /** @scratch /panels/goal/3
+       *
+       * === Parameters
+       * donut:: Draw a hole in the middle of the pie, creating a tasty donut.
+       */
+      donut   : true,
+      /** @scratch /panels/goal/3
+       * tilt:: Tilt the pie back into an oval shape
+       */
+      tilt    : false,
+      /** @scratch /panels/goal/3
+       * legend:: The location of the legend, above, below or none
+       */
+      legend  : "above",
+      /** @scratch /panels/goal/3
+       * labels:: Set to false to disable drawing labels inside the pie slices
+       */
+      labels  : true,
+      /** @scratch /panels/goal/3
+       * spyable:: Set to false to disable the inspect function.
+       */
+      spyable : true,
+      /** @scratch /panels/goal/3
+       *
+       * ==== Query
+       *
+       * query object::
+       * query.goal::: the fixed goal for goal mode
+       */
+      query   : {goal: 100},
+      /** @scratch /panels/goal/5
+       *
+       * ==== Queries
+       *
+       * queries object:: This object describes the queries to use on this panel.
+       * queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
+       * queries.ids::: In +selected+ mode, which query ids are selected.
+       */
+      queries     : {
+        mode        : 'all',
+        ids         : []
+      },
+      /*
+       * locked:: whether to lock the query, preventing it from being affected by filters
+       */
+      locked: false
+    };
+    _.defaults($scope.panel,_d);
+
+    $scope.init = function() {
+      $scope.$on('refresh',function(){$scope.get_data();});
+      $scope.get_data();
+    };
+
+    $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(dashboard.indices.length === 0) {
+        return;
+      }
+
+
+      $scope.panelMeta.loading = true;
+      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(queries,function(q) {
+        boolQuery = boolQuery.should(querySrv.toEjsObj(q));
+      });
+
+      var results;
+
+      request = request.query(boolQuery);
+
+      if (!$scope.panel.locked) {
+        request = request.filter(filterSrv.getBoolFilter(filterSrv.ids()));
+      }
+
+      request = request.size(0);
+
+      $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
+
+      results = request.doSearch();
+
+      results.then(function(results) {
+        $scope.panelMeta.loading = false;
+        var complete  = results.hits.total;
+        var remaining = $scope.panel.query.goal - complete;
+        $scope.data = [
+          { label : 'Complete', data : complete, color: querySrv.colors[parseInt($scope.$id, 16)%8] },
+          { data : remaining, color: Chromath.lighten(querySrv.colors[parseInt($scope.$id, 16)%8],0.70).toString() }
+        ];
+        $scope.$emit('render');
+      });
+    };
+
+  });
+
+  module.directive('goal', function(querySrv) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem) {
+
+        elem.html('<center><img src="img/load_big.gif"></center>');
+
+        // Receive render events
+        scope.$on('render',function(){
+          render_panel();
+        });
+
+        // Or if the window is resized
+        angular.element(window).bind('resize', function(){
+          render_panel();
+        });
+
+        // Function for rendering panel
+        function render_panel() {
+          // IE doesn't work without this
+          elem.css({height:scope.row.height});
+
+          var label;
+
+          label = {
+            show: scope.panel.labels,
+            radius: 0,
+            formatter: function(label, series){
+              var font = parseInt(scope.row.height.replace('px',''),10)/8 + String('px');
+              if(!(_.isUndefined(label))) {
+                return '<div style="font-size:'+font+';font-weight:bold;text-align:center;padding:2px;color:#fff;">'+
+                Math.round(series.percent)+'%</div>';
+              } else {
+                return '';
+              }
+            },
+          };
+
+          var pie = {
+            series: {
+              pie: {
+                innerRadius: scope.panel.donut ? 0.45 : 0,
+                tilt: scope.panel.tilt ? 0.45 : 1,
+                radius: 1,
+                show: true,
+                combine: {
+                  color: '#999',
+                  label: 'The Rest'
+                },
+                label: label,
+                stroke: {
+                  width: 0
+                }
+              }
+            },
+            //grid: { hoverable: true, clickable: true },
+            grid:   {
+              backgroundColor: null,
+              hoverable: true,
+              clickable: true
+            },
+            legend: { show: false },
+            colors: querySrv.colors
+          };
+
+          // Populate legend
+          if(elem.is(":visible")){
+            require(['jquery.flot.pie'], function(){
+              scope.legend = $.plot(elem, scope.data, pie).getData();
+              if(!scope.$$phase) {
+                scope.$apply();
+              }
+            });
+          }
+
+        }
+
+        var $tooltip = $('<div>');
+        elem.bind('plothover', function (event, pos, item) {
+          if (item) {
+            $tooltip
+              .html([
+                kbn.query_color_dot(item.series.color, 15),
+                (item.series.label || ''),
+                parseFloat(item.series.percent).toFixed(1) + '%'
+              ].join(' '))
+              .place_tt(pos.pageX, pos.pageY, {
+                offset: 10
+              });
+          } else {
+            $tooltip.remove();
+          }
+        });
+
+      }
+    };
+  });
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/editor.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/editor.html b/opensoc-ui/lib/public/app/panels/histogram/editor.html
new file mode 100755
index 0000000..9557e5d
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/editor.html
@@ -0,0 +1,51 @@
+<div class="editor-row">
+  <div class="section">
+    <h5>Values</h5>
+    <div class="editor-option">
+      <label class="small">Chart value</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']"></select>
+    </div>
+    <div class="editor-option" ng-show="panel.mode != 'count'">
+      <label class="small">Value Field <tip>This field must contain a numeric value</tip></label>
+        <input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-medium" ng-model="panel.value_field">
+    </div>
+  </div>
+  <div class="section">
+    <h5>Transform Series</h5>
+    <div class="editor-option" ng-show="panel.mode != 'count'">
+      <label class="small">Scale</label>
+        <input type="text" class="input-mini" ng-model="panel.scale">
+    </div>
+    <div class="editor-option">
+      <label class="small">Seconds <tip>Normalize intervals to per-second</tip></label><input type="checkbox" ng-model="panel.scaleSeconds" ng-checked="panel.scaleSeconds">
+    </div>
+    <div class="editor-option">
+      <label class="small">Derivative <tip>Plot the change per interval in the series</tip></label><input type="checkbox" ng-model="panel.derivative" ng-checked="panel.derivative" ng-change="set_refresh(true)">
+    </div>
+    <div class="editor-option">
+      <label class="small">Zero fill <tip>Fills zeros in gaps.</tip></label><input type="checkbox" ng-model="panel.zerofill" ng-checked="panel.zerofill" ng-change="set_refresh(true)">
+    </div>
+  </div>
+  <div class="section">
+  <h5>Time Options</h5>
+    <div class="editor-option">
+      <label class="small">Time Field</label>
+        <input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
+    </div>
+    <div class="editor-option">
+      <label class="small">Time correction</label>
+      <select ng-model="panel.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
+    </div>
+    <div class="editor-option">
+      <label class="small">Auto-interval</label><input type="checkbox" ng-model="panel.auto_int" ng-checked="panel.auto_int" />
+    </div>
+    <div class="editor-option" ng-show='panel.auto_int'>
+      <label class="small">Resolution <tip>Shoot for this many data points, rounding to sane intervals</tip></label>
+      <input type="number" class='input-mini' ng-model="panel.resolution" ng-change='set_refresh(true)'/>
+    </div>
+    <div class="editor-option" ng-hide='panel.auto_int'>
+      <label class="small">Interval <tip>Use Elasticsearch date math format (eg 1m, 5m, 1d, 2w, 1y)</tip></label>
+      <input type="text" class='input-mini' ng-model="panel.interval" ng-change='set_refresh(true)'/>
+    </div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/interval.js
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/interval.js b/opensoc-ui/lib/public/app/panels/histogram/interval.js
new file mode 100755
index 0000000..ed5ae01
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/interval.js
@@ -0,0 +1,57 @@
+define([
+  'kbn'
+],
+function (kbn) {
+  'use strict';
+
+  /**
+   * manages the interval logic
+   * @param {[type]} interval_string  An interval string in the format '1m', '1y', etc
+   */
+  function Interval(interval_string) {
+    this.string = interval_string;
+
+    var info = kbn.describe_interval(interval_string);
+    this.type = info.type;
+    this.ms = Math.ceil(info.sec * 1000 * info.count);
+
+    // does the length of the interval change based on the current time?
+    if (this.type === 'y' || this.type === 'M') {
+      // we will just modify this time object rather that create a new one constantly
+      this.get = this.get_complex;
+      this.date = new Date(0);
+    } else {
+      this.get = this.get_simple;
+    }
+  }
+
+  Interval.prototype = {
+    toString: function () {
+      return this.string;
+    },
+    after: function(current_ms) {
+      return this.get(current_ms, 1);
+    },
+    before: function (current_ms) {
+      return this.get(current_ms, -1);
+    },
+    get_complex: function (current, delta) {
+      this.date.setTime(current);
+      switch(this.type) {
+      case 'M':
+        this.date.setUTCMonth(this.date.getUTCMonth() + delta);
+        break;
+      case 'y':
+        this.date.setUTCFullYear(this.date.getUTCFullYear() + delta);
+        break;
+      }
+      return this.date.getTime();
+    },
+    get_simple: function (current, delta) {
+      return current + (delta * this.ms);
+    }
+  };
+
+  return Interval;
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/module.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/module.html b/opensoc-ui/lib/public/app/panels/histogram/module.html
new file mode 100755
index 0000000..e1a24ad
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/module.html
@@ -0,0 +1,108 @@
+<div ng-controller='histogram' ng-init="init()" style="min-height:{{panel.height || row.height}}">
+  <style>
+    .histogram-legend {
+      display:inline-block;
+      padding-right:5px
+    }
+    .histogram-legend-dot {
+      display:inline-block;
+      height:10px;
+      width:10px;
+      border-radius:5px;
+    }
+    .histogram-legend-item {
+      display:inline-block;
+    }
+    .histogram-chart {
+      position:relative;
+    }
+    .histogram-options {
+      padding: 5px;
+      margin-right: 15px;
+      margin-bottom: 0px;
+    }
+    .histogram-options label {
+      margin: 0px 0px 0px 10px !important;
+    }
+    .histogram-options span {
+      white-space: nowrap;
+    }
+
+    /* this is actually should be in bootstrap */
+    .form-inline .checkbox {
+        display: inline-block;
+    }
+
+    .histogram-marker {
+      display: block;
+      width: 20px;
+      height: 21px;
+      background-image: url('img/annotation-icon.png');
+      background-repeat: no-repeat;
+    }
+
+  </style>
+  <div>
+    <span ng-show='panel.options'>
+      <a class="link small" ng-show='panel.options' ng-click="options=!options">
+        View <i ng-show="!options" class="icon-caret-right"></i><i ng-show="options" class="icon-caret-down"></i>
+      </a> |&nbsp
+    </span>
+    <span ng-show='panel.zoomlinks'>
+      <!--<a class='small' ng-click='zoom(0.5)'><i class='icon-zoom-in'></i> Zoom In</a>-->
+      <a class='small' ng-click='zoom(2)'><i class='icon-zoom-out'></i> Zoom Out</a> |&nbsp
+    </span>
+    <span ng-show="panel.legend" ng-repeat='series in legend' class="histogram-legend">
+      <i class='icon-circle' ng-style="{color: series.query.color}"></i>
+      <span class='small histogram-legend-item'>
+        <span ng-if="panel.show_query">{{series.query.alias || series.query.query}}</span>
+        <span ng-if="!panel.show_query">{{series.query.alias}}</span>
+        <span ng-show="panel.legend_counts"> ({{series.hits}})</span>
+      </span>
+    </span>
+    <span ng-show="panel.legend" class="small"><span ng-show="panel.derivative"> change in </span><span class="strong" ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong ng-hide="panel.scaleSeconds">{{panel.interval}}</strong><strong ng-show="panel.scaleSeconds">1s</strong> | (<strong>{{hits}}</strong> hits)</span>
+  </div>
+  <form class="form-inline bordered histogram-options" ng-show="options">
+    <span>
+      <div class="checkbox">
+        <label class="small">
+          <input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
+          Bars
+        </label>
+      </div>
+      <div class="checkbox">
+        <label class="small">
+          <input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
+          Lines
+        </label>
+      </div>
+      <div class="checkbox">
+        <label class="small">
+          <input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
+          Stack
+        </label>
+      </div>
+    </span>
+    <span ng-show="panel.stack">
+      <div class="checkbox">
+        <label style="white-space:nowrap" class="small">
+          <input type="checkbox"  ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
+          Percent
+        </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>
+  </form>
+  <center><img ng-show='panel.loading' src="img/load_big.gif"></center>
+  <div histogram-chart class="pointer histogram-chart" params="{{panel}}"></div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/module.js
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/module.js b/opensoc-ui/lib/public/app/panels/histogram/module.js
new file mode 100755
index 0000000..c74d0b8
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/module.js
@@ -0,0 +1,826 @@
+/** @scratch /panels/5
+ *
+ * include::panels/histogram.asciidoc[]
+ */
+
+/** @scratch /panels/histogram/0
+ *
+ * == Histogram
+ * Status: *Stable*
+ *
+ * The histogram panel allow for the display of time charts. It includes several modes and tranformations
+ * to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
+ * fields.
+ *
+ */
+define([
+  'angular',
+  'app',
+  'jquery',
+  'lodash',
+  'kbn',
+  'moment',
+  './timeSeries',
+  'numeral',
+  'jquery.flot',
+  'jquery.flot.events',
+  'jquery.flot.selection',
+  'jquery.flot.time',
+  'jquery.flot.byte',
+  'jquery.flot.stack',
+  'jquery.flot.stackpercent'
+],
+function (angular, app, $, _, kbn, moment, timeSeries, numeral) {
+
+  'use strict';
+
+  var module = angular.module('kibana.panels.histogram', []);
+  app.useModule(module);
+
+  module.controller('histogram', function($scope, querySrv, dashboard, filterSrv) {
+    $scope.panelMeta = {
+      modals : [
+        {
+          description: "Inspect",
+          icon: "icon-info-sign",
+          partial: "app/partials/inspector.html",
+          show: $scope.panel.spyable
+        }
+      ],
+      editorTabs : [
+        {
+          title:'Style',
+          src:'app/panels/histogram/styleEditor.html'
+        },
+        {
+          title:'Queries',
+          src:'app/panels/histogram/queriesEditor.html'
+        },
+      ],
+      status  : "Stable",
+      description : "A bucketed time series chart of the current query or queries. Uses the "+
+        "Elasticsearch date_histogram facet. If using time stamped indices this panel will query"+
+        " them sequentially to attempt to apply the lighest possible load to your Elasticsearch cluster"
+    };
+
+    // Set and populate defaults
+    var _d = {
+      /** @scratch /panels/histogram/3
+       *
+       * === Parameters
+       * ==== Axis options
+       * mode:: Value to use for the y-axis. For all modes other than count, +value_field+ must be
+       * defined. Possible values: count, mean, max, min, total.
+       */
+      mode          : 'count',
+      /** @scratch /panels/histogram/3
+       * time_field:: x-axis field. This must be defined as a date type in Elasticsearch.
+       */
+      time_field    : '@timestamp',
+      /** @scratch /panels/histogram/3
+       * value_field:: y-axis field if +mode+ is set to mean, max, min or total. Must be numeric.
+       */
+      value_field   : null,
+      /** @scratch /panels/histogram/3
+       * x-axis:: Show the x-axis
+       */
+      'x-axis'      : true,
+      /** @scratch /panels/histogram/3
+       * y-axis:: Show the y-axis
+       */
+      'y-axis'      : true,
+      /** @scratch /panels/histogram/3
+       * scale:: Scale the y-axis by this factor
+       */
+      scale         : 1,
+      /** @scratch /panels/histogram/3
+       * y_format:: 'none','bytes','short '
+       */
+      y_format    : 'none',
+      /** @scratch /panels/histogram/5
+       * grid object:: Min and max y-axis values
+       * grid.min::: Minimum y-axis value
+       * grid.max::: Maximum y-axis value
+       */
+      grid          : {
+        max: null,
+        min: 0
+      },
+      /** @scratch /panels/histogram/5
+       *
+       * ==== Queries
+       * queries object:: This object describes the queries to use on this panel.
+       * queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
+       * queries.ids::: In +selected+ mode, which query ids are selected.
+       */
+      queries     : {
+        mode        : 'all',
+        ids         : []
+      },
+      /*
+       * locked:: whether to lock the query, preventing it from being affected by filters
+       */
+      locked: false,
+      /** @scratch /panels/histogram/3
+       *
+       * ==== Annotations
+       * annotate object:: A query can be specified, the results of which will be displayed as markers on
+       * the chart. For example, for noting code deploys.
+       * annotate.enable::: Should annotations, aka markers, be shown?
+       * annotate.query::: Lucene query_string syntax query to use for markers.
+       * annotate.size::: Max number of markers to show
+       * annotate.field::: Field from documents to show
+       * annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
+       */
+      annotate      : {
+        enable      : false,
+        query       : "*",
+        size        : 20,
+        field       : '_type',
+        sort        : ['_score','desc']
+      },
+      /** @scratch /panels/histogram/3
+       * ==== Interval options
+       * auto_int:: Automatically scale intervals?
+       */
+      auto_int      : true,
+      /** @scratch /panels/histogram/3
+       * resolution:: If auto_int is true, shoot for this many bars.
+       */
+      resolution    : 100,
+      /** @scratch /panels/histogram/3
+       * interval:: If auto_int is set to false, use this as the interval.
+       */
+      interval      : '5m',
+      /** @scratch /panels/histogram/3
+       * interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
+       */
+      intervals     : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
+      /** @scratch /panels/histogram/3
+       * ==== Drawing options
+       * lines:: Show line chart
+       */
+      lines         : false,
+      /** @scratch /panels/histogram/3
+       * fill:: Area fill factor for line charts, 1-10
+       */
+      fill          : 0,
+      /** @scratch /panels/histogram/3
+       * linewidth:: Weight of lines in pixels
+       */
+      linewidth     : 3,
+      /** @scratch /panels/histogram/3
+       * points:: Show points on chart
+       */
+      points        : false,
+      /** @scratch /panels/histogram/3
+       * pointradius:: Size of points in pixels
+       */
+      pointradius   : 5,
+      /** @scratch /panels/histogram/3
+       * bars:: Show bars on chart
+       */
+      bars          : true,
+      /** @scratch /panels/histogram/3
+       * stack:: Stack multiple series
+       */
+      stack         : true,
+      /** @scratch /panels/histogram/3
+       * spyable:: Show inspect icon
+       */
+      spyable       : true,
+      /** @scratch /panels/histogram/3
+       * zoomlinks:: Show `Zoom Out' link
+       */
+      zoomlinks     : true,
+      /** @scratch /panels/histogram/3
+       * options:: Show quick view options section
+       */
+      options       : true,
+      /** @scratch /panels/histogram/3
+       * legend:: Display the legond
+       */
+      legend        : true,
+      /** @scratch /panels/histogram/3
+       * show_query:: If no alias is set, should the query be displayed?
+       */
+      show_query    : true,
+      /** @scratch /panels/histogram/3
+       * interactive:: Enable click-and-drag to zoom functionality
+       */
+      interactive   : true,
+      /** @scratch /panels/histogram/3
+       * legend_counts:: Show counts in legend
+       */
+      legend_counts : true,
+      /** @scratch /panels/histogram/3
+       * ==== Transformations
+       * timezone:: Correct for browser timezone?. Valid values: browser, utc
+       */
+      timezone      : 'browser', // browser or utc
+      /** @scratch /panels/histogram/3
+       * percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
+       * queries
+       */
+      percentage    : false,
+      /** @scratch /panels/histogram/3
+       * zerofill:: Improves the accuracy of line charts at a small performance cost.
+       */
+      zerofill      : true,
+      /** @scratch /panels/histogram/3
+       * derivative:: Show each point on the x-axis as the change from the previous point
+       */
+
+      derivative    : false,
+      /** @scratch /panels/histogram/3
+       * tooltip object::
+       * tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
+       * tooltip.query_as_alias::: If no alias is set, should the query be displayed?
+       */
+      tooltip       : {
+        value_type: 'cumulative',
+        query_as_alias: true
+      }
+    };
+
+    _.defaults($scope.panel,_d);
+    _.defaults($scope.panel.tooltip,_d.tooltip);
+    _.defaults($scope.panel.annotate,_d.annotate);
+    _.defaults($scope.panel.grid,_d.grid);
+
+
+
+    $scope.init = function() {
+      // Hide view options by default
+      $scope.options = false;
+
+      // Always show the query if an alias isn't set. Users can set an alias if the query is too
+      // long
+      $scope.panel.tooltip.query_as_alias = true;
+
+      $scope.get_data();
+
+    };
+
+    $scope.set_interval = function(interval) {
+      if(interval !== 'auto') {
+        $scope.panel.auto_int = false;
+        $scope.panel.interval = interval;
+      } else {
+        $scope.panel.auto_int = true;
+      }
+    };
+
+    $scope.interval_label = function(interval) {
+      return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
+    };
+
+    /**
+     * The time range effecting the panel
+     * @return {[type]} [description]
+     */
+    $scope.get_time_range = function () {
+      var range = $scope.range = filterSrv.timeRange('last');
+      return range;
+    };
+
+    $scope.get_interval = function () {
+      var interval = $scope.panel.interval,
+                      range;
+      if ($scope.panel.auto_int) {
+        range = $scope.get_time_range();
+        if (range) {
+          interval = kbn.secondsToHms(
+            kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
+          );
+        }
+      }
+      $scope.panel.interval = interval || '10m';
+      return $scope.panel.interval;
+    };
+
+    /**
+     * Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
+     * need to be consulted (like timestamped logstash indicies)
+     *
+     * The results of this function are stored on the scope's data property. This property will be an
+     * array of objects with the properties info, time_series, and hits. These objects are used in the
+     * render_panel function to create the historgram.
+     *
+     * @param {number} segment   The segment count, (0 based)
+     * @param {number} query_id  The id of the query, generated on the first run and passed back when
+     *                            this call is made recursively for more segments
+     */
+    $scope.get_data = function(data, segment, query_id) {
+      var
+        _range,
+        _interval,
+        request,
+        queries,
+        results;
+
+      if (_.isUndefined(segment)) {
+        segment = 0;
+      }
+      delete $scope.panel.error;
+
+      // Make sure we have everything for the request to complete
+      if(dashboard.indices.length === 0) {
+        return;
+      }
+      _range = $scope.get_time_range();
+      _interval = $scope.get_interval(_range);
+
+      if ($scope.panel.auto_int) {
+        $scope.panel.interval = kbn.secondsToHms(
+          kbn.calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000);
+      }
+
+      $scope.panelMeta.loading = true;
+      request = $scope.ejs.Request().indices(dashboard.indices[segment]);
+      if (!$scope.panel.annotate.enable) {
+        request.searchType("count");
+      }
+
+      $scope.panel.queries.ids = querySrv.idsByMode($scope.panel.queries);
+
+      queries = querySrv.getQueryObjs($scope.panel.queries.ids);
+
+      // Build the query
+      _.each(queries, function(q) {
+        var query = $scope.ejs.FilteredQuery(
+          querySrv.toEjsObj(q),
+          $scope.panel.locked ? null : filterSrv.getBoolFilter(filterSrv.ids())
+        );
+
+        var facet = $scope.ejs.DateHistogramFacet(q.id);
+
+        if($scope.panel.mode === 'count') {
+          facet = facet.field($scope.panel.time_field).global(true);
+        } 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.panel.time_field).valueField($scope.panel.value_field).global(true);
+        }
+        facet = facet.interval(_interval).facetFilter($scope.ejs.QueryFilter(query));
+        request = request.facet(facet)
+          .size($scope.panel.annotate.enable ? $scope.panel.annotate.size : 0);
+      });
+
+      if($scope.panel.annotate.enable) {
+        var query = $scope.ejs.FilteredQuery(
+          $scope.ejs.QueryStringQuery($scope.panel.annotate.query || '*'),
+          $scope.panel.locked ? null : filterSrv.getBoolFilter(filterSrv.idsByType('time'))
+        );
+        request = request.query(query);
+
+        // This is a hack proposed by @boaz to work around the fact that we can't get
+        // to field data values directly, and we need timestamps as normalized longs
+        request = request.sort([
+          $scope.ejs.Sort($scope.panel.annotate.sort[0]).order($scope.panel.annotate.sort[1]),
+          $scope.ejs.Sort($scope.panel.time_field).desc()
+        ]);
+      }
+
+      // Populate the inspector panel
+      $scope.populate_modal(request);
+
+      // Then run it
+      results = request.doSearch();
+
+      // Populate scope when we have results
+      return results.then(function(results) {
+        $scope.panelMeta.loading = false;
+        if(segment === 0) {
+          $scope.legend = [];
+          $scope.hits = 0;
+          data = [];
+          $scope.annotations = [];
+          query_id = $scope.query_id = new Date().getTime();
+        }
+
+        // Check for error and abort if found
+        if(!(_.isUndefined(results.error))) {
+          $scope.panel.error = $scope.parse_error(results.error);
+        }
+        // Make sure we're still on the same query/queries
+        else if($scope.query_id === query_id) {
+
+          var i = 0,
+            time_series,
+            hits,
+            counters; // Stores the bucketed hit counts.
+
+          _.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(data[i]) || segment === 0) {
+              var tsOpts = {
+                interval: _interval,
+                start_date: _range && _range.from,
+                end_date: _range && _range.to,
+                fill_style: $scope.panel.derivative ? 'null' : $scope.panel.zerofill ? 'minimal' : 'no'
+              };
+              time_series = new timeSeries.ZeroFilled(tsOpts);
+              hits = 0;
+              counters = {};
+            } else {
+              time_series = data[i].time_series;
+              hits = data[i].hits;
+              counters = data[i].counters;
+            }
+
+            // push each entry into the time series, while incrementing counters
+            _.each(query_results.entries, function(entry) {
+              var value;
+
+              hits += entry.count; // The series level hits counter
+              $scope.hits += entry.count; // Entire dataset level hits counter
+              counters[entry.time] = (counters[entry.time] || 0) + entry.count;
+
+              if($scope.panel.mode === 'count') {
+                value = (time_series._data[entry.time] || 0) + entry.count;
+              } else if ($scope.panel.mode === 'mean') {
+                // Compute the ongoing mean by
+                // multiplying the existing mean by the existing hits
+                // plus the new mean multiplied by the new hits
+                // divided by the total hits
+                value = (((time_series._data[entry.time] || 0)*(counters[entry.time]-entry.count)) +
+                  entry.mean*entry.count)/(counters[entry.time]);
+              } else if ($scope.panel.mode === 'min'){
+                if(_.isUndefined(time_series._data[entry.time])) {
+                  value = entry.min;
+                } else {
+                  value = time_series._data[entry.time] < entry.min ? time_series._data[entry.time] : entry.min;
+                }
+              } else if ($scope.panel.mode === 'max'){
+                if(_.isUndefined(time_series._data[entry.time])) {
+                  value = entry.max;
+                } else {
+                  value = time_series._data[entry.time] > entry.max ? time_series._data[entry.time] : entry.max;
+                }
+              } else if ($scope.panel.mode === 'total'){
+                value = (time_series._data[entry.time] || 0) + entry.total;
+              }
+              time_series.addValue(entry.time, value);
+            });
+
+            $scope.legend[i] = {query:q,hits:hits};
+
+            data[i] = {
+              info: q,
+              time_series: time_series,
+              hits: hits,
+              counters: counters
+            };
+
+            i++;
+          });
+
+          if($scope.panel.annotate.enable) {
+            $scope.annotations = $scope.annotations.concat(_.map(results.hits.hits, function(hit) {
+              var _p = _.omit(hit,'_source','sort','_score');
+              var _h = _.extend(kbn.flatten_json(hit._source),_p);
+              return  {
+                min: hit.sort[1],
+                max: hit.sort[1],
+                eventType: "annotation",
+                title: null,
+                description: "<small><i class='icon-tag icon-flip-vertical'></i> "+
+                  _h[$scope.panel.annotate.field]+"</small><br>"+
+                  moment(hit.sort[1]).format('YYYY-MM-DD HH:mm:ss'),
+                score: hit.sort[0]
+              };
+            }));
+            // Sort the data
+            $scope.annotations = _.sortBy($scope.annotations, function(v){
+              // Sort in reverse
+              return v.score*($scope.panel.annotate.sort[1] === 'desc' ? -1 : 1);
+            });
+            // And slice to the right size
+            $scope.annotations = $scope.annotations.slice(0,$scope.panel.annotate.size);
+          }
+        }
+
+        // Tell the histogram directive to render.
+        $scope.$emit('render', data);
+
+        // If we still have segments left, get them
+        if(segment < dashboard.indices.length-1) {
+          $scope.get_data(data,segment+1,query_id);
+        }
+      });
+    };
+
+    // function $scope.zoom
+    // factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
+    $scope.zoom = function(factor) {
+      var _range = filterSrv.timeRange('last');
+      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).toDate(),
+        to:moment.utc(_to).toDate(),
+        field:$scope.panel.time_field
+      });
+    };
+
+    // I really don't like this function, too much dom manip. Break out into directive?
+    $scope.populate_modal = function(request) {
+      $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
+    };
+
+    $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.render = function() {
+      $scope.$emit('render');
+    };
+
+  });
+
+  module.directive('histogramChart', function(dashboard, filterSrv) {
+    return {
+      restrict: 'A',
+      template: '<div></div>',
+      link: function(scope, elem) {
+        var data, plot;
+
+        scope.$on('refresh',function(){
+          scope.get_data();
+        });
+
+        // Receive render events
+        scope.$on('render',function(event,d){
+          data = d || data;
+          render_panel(data);
+        });
+
+        scope.$watch('panel.span', function(){
+          render_panel(data);
+        });
+
+        // Re-render if the window is resized
+        angular.element(window).bind('resize', function(){
+          render_panel(data);
+        });
+
+        var scale = function(series,factor) {
+          return _.map(series,function(p) {
+            return [p[0],p[1]*factor];
+          });
+        };
+
+        var scaleSeconds = function(series,interval) {
+          return _.map(series,function(p) {
+            return [p[0],p[1]/kbn.interval_to_seconds(interval)];
+          });
+        };
+
+        var derivative = function(series) {
+          return _.map(series, function(p,i) {
+            var _v;
+            if(i === 0 || p[1] === null) {
+              _v = [p[0],null];
+            } else {
+              _v = series[i-1][1] === null ? [p[0],null] : [p[0],p[1]-(series[i-1][1])];
+            }
+            return _v;
+          });
+        };
+
+        // Function for rendering panel
+        function render_panel(data) {
+          // IE doesn't work without this
+          try {
+            elem.css({height:scope.row.height});
+          } catch(e) {return;}
+
+          // Populate from the query service
+          try {
+            _.each(data, function(series) {
+              series.label = series.info.alias;
+              series.color = series.info.color;
+            });
+          } catch(e) {return;}
+
+          // Set barwidth based on specified interval
+          var barwidth = kbn.interval_to_ms(scope.panel.interval);
+
+          var stack = scope.panel.stack ? true : null;
+
+          // Populate element
+          try {
+            var options = {
+              legend: { show: false },
+              series: {
+                stackpercent: scope.panel.stack ? scope.panel.percentage : false,
+                stack: scope.panel.percentage ? null : stack,
+                lines:  {
+                  show: scope.panel.lines,
+                  // Silly, but fixes bug in stacked percentages
+                  fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
+                  lineWidth: scope.panel.linewidth,
+                  steps: false
+                },
+                bars:   {
+                  show: scope.panel.bars,
+                  fill: 1,
+                  barWidth: barwidth/1.5,
+                  zero: false,
+                  lineWidth: 0
+                },
+                points: {
+                  show: scope.panel.points,
+                  fill: 1,
+                  fillColor: false,
+                  radius: scope.panel.pointradius
+                },
+                shadowSize: 1
+              },
+              yaxis: {
+                show: scope.panel['y-axis'],
+                min: scope.panel.grid.min,
+                max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
+              },
+              xaxis: {
+                timezone: scope.panel.timezone,
+                show: scope.panel['x-axis'],
+                mode: "time",
+                min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
+                max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
+                timeformat: time_format(scope.panel.interval),
+                label: "Datetime",
+                ticks: elem.width()/100
+              },
+              grid: {
+                backgroundColor: null,
+                borderWidth: 0,
+                hoverable: true,
+                color: '#c8c8c8'
+              }
+            };
+
+            if (scope.panel.y_format === 'bytes') {
+              options.yaxis.mode = "byte";
+              options.yaxis.tickFormatter = function (val, axis) {
+                return kbn.byteFormat(val, 0, axis.tickSize);
+              };
+            }
+
+            if (scope.panel.y_format === 'short') {
+              options.yaxis.tickFormatter = function (val, axis) {
+                return kbn.shortFormat(val, 0, axis.tickSize);
+              };
+            }
+
+            if(scope.panel.annotate.enable) {
+              options.events = {
+                clustering: true,
+                levels: 1,
+                data: scope.annotations,
+                types: {
+                  'annotation': {
+                    level: 1,
+                    icon: {
+                      width: 20,
+                      height: 21,
+                      icon: "histogram-marker"
+                    }
+                  }
+                }
+                //xaxis: int    // the x axis to attach events to
+              };
+            }
+
+            if(scope.panel.interactive) {
+              options.selection = { mode: "x", color: '#666' };
+            }
+
+            // when rendering stacked bars, we need to ensure each point that has data is zero-filled
+            // so that the stacking happens in the proper order
+            var required_times = [];
+            if (data.length > 1) {
+              required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
+                return query.time_series.getOrderedTimes();
+              }));
+              required_times = _.uniq(required_times.sort(function (a, b) {
+                // decending numeric sort
+                return a-b;
+              }), true);
+            }
+
+
+            for (var i = 0; i < data.length; i++) {
+              var _d = data[i].time_series.getFlotPairs(required_times);
+              if(scope.panel.derivative) {
+                _d = derivative(_d);
+              }
+              if(scope.panel.scale !== 1) {
+                _d = scale(_d,scope.panel.scale);
+              }
+              if(scope.panel.scaleSeconds) {
+                _d = scaleSeconds(_d,scope.panel.interval);
+              }
+              data[i].data = _d;
+            }
+
+            plot = $.plot(elem, data, options);
+
+          } catch(e) {
+            // Nothing to do here
+          }
+        }
+
+        function time_format(interval) {
+          var _int = kbn.interval_to_seconds(interval);
+          if(_int >= 2628000) {
+            return "%Y-%m";
+          }
+          if(_int >= 86400) {
+            return "%Y-%m-%d";
+          }
+          if(_int >= 60) {
+            return "%H:%M<br>%m-%d";
+          }
+
+          return "%H:%M:%S";
+        }
+
+        var $tooltip = $('<div>');
+        elem.bind("plothover", function (event, pos, item) {
+          var group, value, timestamp, interval;
+          interval = " per " + (scope.panel.scaleSeconds ? '1s' : scope.panel.interval);
+          if (item) {
+            if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
+              group = '<small style="font-size:0.9em;">' +
+                '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
+                (item.series.info.alias || item.series.info.query)+
+              '</small><br>';
+            } else {
+              group = kbn.query_color_dot(item.series.color, 15) + ' ';
+            }
+            value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
+              item.datapoint[1] - item.datapoint[2] :
+              item.datapoint[1];
+            if(scope.panel.y_format === 'bytes') {
+              value = kbn.byteFormat(value,2);
+            }
+            if(scope.panel.y_format === 'short') {
+              value = kbn.shortFormat(value,2);
+            } else {
+              value = numeral(value).format('0,0[.]000');
+            }
+            timestamp = scope.panel.timezone === 'browser' ?
+              moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
+              moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
+            $tooltip
+              .html(
+                group + value + interval + " @ " + timestamp
+              )
+              .place_tt(pos.pageX, pos.pageY);
+          } else {
+            $tooltip.detach();
+          }
+        });
+
+        elem.bind("plotselected", function (event, ranges) {
+          filterSrv.set({
+            type  : 'time',
+            from  : moment.utc(ranges.xaxis.from).toDate(),
+            to    : moment.utc(ranges.xaxis.to).toDate(),
+            field : scope.panel.time_field
+          });
+        });
+      }
+    };
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/queriesEditor.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/queriesEditor.html b/opensoc-ui/lib/public/app/panels/histogram/queriesEditor.html
new file mode 100755
index 0000000..414de27
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/queriesEditor.html
@@ -0,0 +1,43 @@
+<h4>Charted</h4>
+<div ng-include src="'app/partials/querySelect.html'"></div>
+
+<div class="editor-row">
+  <h4>Markers</h4>
+
+  <div class="small">
+    Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
+  </div>
+  <style>
+    .querySelect .query {
+      margin-right: 5px;
+    }
+    .querySelect .selected {
+      border: 3px solid;
+    }
+    .querySelect .unselected {
+      border: 0px solid;
+    }
+  </style>
+  <p>
+  <div class="editor-option">
+    <label class="small">Enable</label>
+    <input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
+  </div>
+  <div class="editor-option" ng-show="panel.annotate.enable">
+    <label class="small">Marker Query</label>
+    <input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
+  </div>
+  <div class="editor-option" ng-show="panel.annotate.enable">
+    <label class="small">Tooltip field</label>
+    <input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
+  </div>
+  <div class="editor-option" ng-show="panel.annotate.enable">
+    <label class="small">Limit <tip>Max markers on the chart</tip></label>
+    <input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
+  </div>
+  <div class="editor-option" ng-show="panel.annotate.enable">
+    <label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
+    <input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
+    <i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/styleEditor.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/styleEditor.html b/opensoc-ui/lib/public/app/panels/histogram/styleEditor.html
new file mode 100755
index 0000000..874a226
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/styleEditor.html
@@ -0,0 +1,88 @@
+<div class="editor-row">
+  <div class="section">
+    <h5>Chart Options</h5>
+    <div class="editor-option">
+      <label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars">
+    </div>
+    <div class="editor-option">
+      <label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines">
+    </div>
+    <div class="editor-option">
+      <label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points">
+    </div>
+    <div class="editor-option">
+      <label class="small">Selectable</label><input type="checkbox" ng-model="panel.interactive" ng-checked="panel.interactive">
+    </div>
+    <div class="editor-option">
+      <label class="small">xAxis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']"></div>
+    <div class="editor-option">
+      <label class="small">yAxis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']"></div>
+    <div class="editor-option" ng-show="panel.lines">
+      <label class="small">Line Fill</label>
+      <select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
+    </div>
+    <div class="editor-option" ng-show="panel.lines">
+      <label class="small">Line Width</label>
+      <select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"></select>
+    </div>
+    <div class="editor-option" ng-show="panel.points">
+      <label class="small">Point Radius</label>
+      <select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]"></select>
+    </div>
+    <div class="editor-option">
+      <label class="small">Y Format <tip>Y-axis formatting</tip></label>
+      <select class="input-small" ng-model="panel.y_format" ng-options="f for f in ['none','short','bytes']"></select>
+    </div>
+  </div>
+  <div class="section">
+    <h5>Multiple Series</h5>
+    <div class="editor-option">
+      <label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack">
+    </div>
+    <div class="editor-option" ng-show="panel.stack">
+      <label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
+      <input type="checkbox"  ng-model="panel.percentage" ng-checked="panel.percentage">
+    </div>
+    <div class="editor-option" ng-show="panel.stack">
+      <label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
+      <select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']"></select>
+    </div>
+  </div>
+</div>
+
+<div class="editor-row">
+  <div class="section">
+    <h5>Header<h5>
+    <div class="editor-option">
+      <label class="small">Zoom</label><input type="checkbox" ng-model="panel.zoomlinks" ng-checked="panel.zoomlinks" />
+    </div>
+    <div class="editor-option">
+      <label class="small">View</label><input type="checkbox" ng-model="panel.options" ng-checked="panel.options" />
+    </div>
+  </div>
+  <div class="section">
+    <h5>Legend<h5>
+    <div class="editor-option">
+      <label class="small">Legend</label><input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend">
+    </div>
+    <div ng-show="panel.legend" class="editor-option">
+      <label class="small">Query <tip>If no alias is set, show the query in the legend</tip></label><input type="checkbox" ng-model="panel.show_query" ng-checked="panel.show_query">
+    </div>
+    <div ng-show="panel.legend" class="editor-option">
+      <label class="small">Counts</label><input type="checkbox" ng-model="panel.legend_counts" ng-checked="panel.legend_counts">
+    </div>
+  </div>
+
+  <div class="section">
+    <h5>Grid<h5>
+    <div class="editor-option">
+      <label class="small">Min / <a href='' ng-click="panel.grid.min = _.toggle(panel.grid.min,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
+      <input type="number" class="input-small" ng-model="panel.grid.min"/>
+    </div>
+    <div class="editor-option">
+      <label class="small">Max / <a ref='' ng-click="panel.grid.max = _.toggle(panel.grid.max,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
+      <input type="number" class="input-small" ng-model="panel.grid.max"/>
+    </div>
+  </div>
+
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/histogram/timeSeries.js
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/histogram/timeSeries.js b/opensoc-ui/lib/public/app/panels/histogram/timeSeries.js
new file mode 100755
index 0000000..4f94735
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/histogram/timeSeries.js
@@ -0,0 +1,235 @@
+define([
+  './interval',
+  'lodash'
+],
+function (Interval, _) {
+  'use strict';
+
+  var ts = {};
+
+  // map compatable parseInt
+  function base10Int(val) {
+    return parseInt(val, 10);
+  }
+
+  // trim the ms off of a time, but return it with empty ms.
+  function getDatesTime(date) {
+    return Math.floor(date.getTime() / 1000)*1000;
+  }
+
+  /**
+   * Certain graphs require 0 entries to be specified for them to render
+   * properly (like the line graph). So with this we will caluclate all of
+   * the expected time measurements, and fill the missing ones in with 0
+   * @param {object} opts  An object specifying some/all of the options
+   *
+   * OPTIONS:
+   * @opt   {string}   interval    The interval notion describing the expected spacing between
+   *                                each data point.
+   * @opt   {date}     start_date  (optional) The start point for the time series, setting this and the
+   *                                end_date will ensure that the series streches to resemble the entire
+   *                                expected result
+   * @opt   {date}     end_date    (optional) The end point for the time series, see start_date
+   * @opt   {string}   fill_style  Either "minimal", or "all" describing the strategy used to zero-fill
+   *                                the series.
+   */
+  ts.ZeroFilled = function (opts) {
+    opts = _.defaults(opts, {
+      interval: '10m',
+      start_date: null,
+      end_date: null,
+      fill_style: 'minimal'
+    });
+
+    // the expected differenece between readings.
+    this.interval = new Interval(opts.interval);
+
+    // will keep all values here, keyed by their time
+    this._data = {};
+    // For each bucket in _data, store a corresponding counter of how many times it was written to.
+    this._counters = {};
+    this.start_time = opts.start_date && getDatesTime(opts.start_date);
+    this.end_time = opts.end_date && getDatesTime(opts.end_date);
+    this.opts = opts;
+  };
+
+  /**
+   * Add a row
+   * @param {int}  time  The time for the value, in
+   * @param {any}  value The value at this time
+   */
+  ts.ZeroFilled.prototype.addValue = function (time, value) {
+    this._counters[time] = (this._counters[time] || 0) + 1;
+    if (time instanceof Date) {
+      time = getDatesTime(time);
+    } else {
+      time = base10Int(time);
+    }
+    if (!isNaN(time)) {
+      this._data[time] = (_.isUndefined(value) ? 0 : value);
+    }
+    this._cached_times = null;
+  };
+
+  /**
+   * Get an array of the times that have been explicitly set in the series
+   * @param  {array} include (optional) list of timestamps to include in the response
+   * @return {array} An array of integer times.
+   */
+  ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
+    var times = _.map(_.keys(this._data), base10Int);
+    if (_.isArray(include)) {
+      times = times.concat(include);
+    }
+    return _.uniq(times.sort(function (a, b) {
+      // decending numeric sort
+      return a - b;
+    }), true);
+  };
+
+  /**
+   * return the rows in the format:
+   * [ [time, value], [time, value], ... ]
+   *
+   * Heavy lifting is done by _get(Min|Default|All)FlotPairs()
+   * @param  {array} required_times  An array of timestamps that must be in the resulting pairs
+   * @return {array}
+   */
+  ts.ZeroFilled.prototype.getFlotPairs = function (required_times) {
+    var times = this.getOrderedTimes(required_times),
+      strategy,
+      pairs;
+
+    if(this.opts.fill_style === 'all') {
+      strategy = this._getAllFlotPairs;
+    } else if(this.opts.fill_style === 'null') {
+      strategy = this._getNullFlotPairs;
+    } else if(this.opts.fill_style === 'no') {
+      strategy = this._getNoZeroFlotPairs;
+    } else {
+      strategy = this._getMinFlotPairs;
+    }
+
+    pairs = _.reduce(
+      times,    // what
+      strategy, // how
+      [],       // where
+      this      // context
+    );
+
+    // if the first or last pair is inside either the start or end time,
+    // add those times to the series with null values so the graph will stretch to contain them.
+    // Removing, flot 0.8.1's max/min params satisfy this
+    /*
+    if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
+      pairs.unshift([this.start_time, null]);
+    }
+    if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
+      pairs.push([this.end_time, null]);
+    }
+    */
+
+    return pairs;
+  };
+
+  /**
+   * ** called as a reduce stragegy in getFlotPairs() **
+   * Fill zero's on either side of the current time, unless there is already a measurement there or
+   * we are looking at an edge.
+   * @return {array} An array of points to plot with flot
+   */
+  ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
+    var next, expected_next, prev, expected_prev;
+
+    // check for previous measurement
+    if (i > 0) {
+      prev = times[i - 1];
+      expected_prev = this.interval.before(time);
+      if (prev < expected_prev) {
+        result.push([expected_prev, 0]);
+      }
+    }
+
+    // add the current time
+    result.push([ time, this._data[time] || 0]);
+
+    // check for next measurement
+    if (times.length > i) {
+      next = times[i + 1];
+      expected_next = this.interval.after(time);
+      if (next > expected_next) {
+        result.push([expected_next, 0]);
+      }
+    }
+
+    return result;
+  };
+
+  /**
+   * ** called as a reduce stragegy in getFlotPairs() **
+   * Fill zero's to the right of each time, until the next measurement is reached or we are at the
+   * last measurement
+   * @return {array}  An array of points to plot with flot
+   */
+  ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
+    var next, expected_next;
+
+    result.push([ times[i], this._data[times[i]] || 0 ]);
+    next = times[i + 1];
+    expected_next = this.interval.after(time);
+    for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
+      result.push([expected_next, 0]);
+    }
+
+    return result;
+  };
+
+  /**
+   * ** called as a reduce stragegy in getFlotPairs() **
+   * Same as min, but fills with nulls
+   * @return {array}  An array of points to plot with flot
+   */
+  ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
+    var next, expected_next, prev, expected_prev;
+
+    // check for previous measurement
+    if (i > 0) {
+      prev = times[i - 1];
+      expected_prev = this.interval.before(time);
+      if (prev < expected_prev) {
+        result.push([expected_prev, null]);
+      }
+    }
+
+    // add the current time
+    result.push([ time, this._data[time] || null]);
+
+    // check for next measurement
+    if (times.length > i) {
+      next = times[i + 1];
+      expected_next = this.interval.after(time);
+      if (next > expected_next) {
+        result.push([expected_next, null]);
+      }
+    }
+
+    return result;
+  };
+
+  /**
+   * ** called as a reduce stragegy in getFlotPairs() **
+   * Not fill zero's on either side of the current time, only the current time
+   * @return {array}  An array of points to plot with flot
+   */
+  ts.ZeroFilled.prototype._getNoZeroFlotPairs = function (result, time) {
+
+    // add the current time
+    if(this._data[time]){
+      result.push([ time, this._data[time]]);
+    }
+
+    return result;
+  };
+
+  return ts;
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/hits/editor.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/hits/editor.html b/opensoc-ui/lib/public/app/panels/hits/editor.html
new file mode 100755
index 0000000..084631f
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/hits/editor.html
@@ -0,0 +1,29 @@
+<div class="editor-row">
+  <div class="section">
+    <div class="editor-option">
+      <label class="small">Style</label>
+      <select class="input-small" ng-model="panel.chart" ng-options="f for f in ['bar','pie','list','total']"></select></span>
+    </div>
+    <div class="editor-option" ng-show="panel.chart == 'total' || panel.chart == 'list'">
+      <label class="small">Font Size</label>
+      <select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
+    </div>
+    <div class="editor-option" ng-show="panel.chart == 'bar' || panel.chart == 'pie'">
+      <label class="small">Legend</label>
+      <select class="input-small" ng-model="panel.counter_pos" ng-options="f for f in ['above','below','none']"></select></span>
+    </div>
+    <div class="editor-option" ng-show="panel.chart != 'total' && panel.counter_pos != 'none'">
+      <label class="small" >List Format</label>
+      <select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
+    </div>
+    <div class="editor-option" ng-show="panel.chart == 'pie'">
+      <label class="small">Donut</label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
+    </div>
+    <div class="editor-option" ng-show="panel.chart == 'pie'">
+      <label class="small">Tilt</label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
+    </div>
+    <div class="editor-option" ng-show="panel.chart == 'pie'">
+      <label class="small">Labels</label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/hits/module.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/hits/module.html b/opensoc-ui/lib/public/app/panels/hits/module.html
new file mode 100755
index 0000000..92283db
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/hits/module.html
@@ -0,0 +1,44 @@
+<div ng-controller='hits' ng-init="init()">
+  <div ng-show="panel.counter_pos == 'above' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
+    <!-- vertical legend -->
+    <table class="small" ng-show="panel.arrangement == 'vertical'">
+      <tr ng-repeat="query in data">
+        <td><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
+      </tr>
+    </table>
+
+    <!-- horizontal legend -->
+    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
+     <span><i class="icon-circle" ng-style="{color:query.info.color}"></i> {{query.info.alias}} ({{query.data[0][1]}}) </span>
+    </div><br>
+
+  </div>
+
+  <div style="clear:both"></div>
+
+  <div ng-show="panel.chart == 'pie' || panel.chart == 'bar'" hits-chart params="{{panel}}" style="position:relative"></div>
+
+  <div ng-show="panel.counter_pos == 'below' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
+    <!-- vertical legend -->
+    <table class="small" ng-show="panel.arrangement == 'vertical'">
+      <tr ng-repeat="query in data">
+        <td><i class="icon-circle" ng-style="{color:query.info.color}"></i></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
+      </tr>
+    </table>
+
+    <!-- horizontal legend -->
+    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
+     <span><i class="icon-circle" ng-style="{color:query.info.color}"></i></span> {{query.info.alias}} ({{query.data[0][1]}}) </span>
+    </div><br>
+
+  </div>
+
+  <div ng-show="panel.chart == 'total'"><div ng-style="panel.style" style="line-height:{{panel.style['font-size']}}">{{hits}}</div></div>
+
+  <span ng-show="panel.chart == 'list'">
+    <div ng-style="panel.style" style="display:inline-block;line-height:{{panel.style['font-size']}}" ng-repeat="query in data">
+      <i class="icon-circle" style="color:{{query.info.color}}"></i> {{query.info.alias}} ({{query.hits}})
+    </div>
+  </span><br ng-show="panel.arrangement == 'vertical' && panel.chart == 'list'">
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/hits/module.js
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/hits/module.js b/opensoc-ui/lib/public/app/panels/hits/module.js
new file mode 100755
index 0000000..a1b32b4
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/hits/module.js
@@ -0,0 +1,303 @@
+/** @scratch /panels/5
+ *
+ * include::panels/hits.asciidoc[]
+ */
+
+/** @scratch /panels/hits/0
+ *
+ * == Hits
+ * Status: *Stable*
+ *
+ * The hits panel displays the number of hits for each of the queries on the dashboard in a
+ * configurable format specified by the `chart' property.
+ *
+ */
+define([
+  'angular',
+  'app',
+  'lodash',
+  'jquery',
+  'kbn',
+
+  'jquery.flot',
+  'jquery.flot.pie'
+], function (angular, app, _, $, kbn) {
+  'use strict';
+
+  var module = angular.module('kibana.panels.hits', []);
+  app.useModule(module);
+
+  module.controller('hits', function($scope, querySrv, dashboard, filterSrv) {
+    $scope.panelMeta = {
+      modals : [
+        {
+          description: "Inspect",
+          icon: "icon-info-sign",
+          partial: "app/partials/inspector.html",
+          show: $scope.panel.spyable
+        }
+      ],
+      editorTabs : [
+        {title:'Queries', src:'app/partials/querySelect.html'}
+      ],
+      status  : "Stable",
+      description : "The total hits for a query or set of queries. Can be a pie chart, bar chart, "+
+        "list, or absolute total of all queries combined"
+    };
+
+    // Set and populate defaults
+    var _d = {
+      style   : { "font-size": '10pt'},
+      /** @scratch /panels/hits/3
+       *
+       * === Parameters
+       *
+       * arrangement:: The arrangement of the legend. horizontal or vertical
+       */
+      arrangement : 'horizontal',
+      /** @scratch /panels/hits/3
+       * chart:: bar, pie or none
+       */
+      chart       : 'bar',
+      /** @scratch /panels/hits/3
+       * counter_pos:: The position of the legend, above or below
+       */
+      counter_pos : 'above',
+      /** @scratch /panels/hits/3
+       * donut:: If the chart is set to pie, setting donut to true will draw a hole in the midle of it
+       */
+      donut   : false,
+      /** @scratch /panels/hits/3
+       * tilt:: If the chart is set to pie, setting tilt to true will tilt it back into an oval
+       */
+      tilt    : false,
+      /** @scratch /panels/hits/3
+       * labels:: If the chart is set to pie, setting labels to true will draw labels in the slices
+       */
+      labels  : true,
+      /** @scratch /panels/hits/3
+       * spyable:: Setting spyable to false disables the inspect icon.
+       */
+      spyable : true,
+      /** @scratch /panels/hits/5
+       *
+       * ==== Queries
+       * queries object:: This object describes the queries to use on this panel.
+       * queries.mode::: Of the queries available, which to use. Options: +all, pinned, unpinned, selected+
+       * queries.ids::: In +selected+ mode, which query ids are selected.
+       */
+      queries     : {
+        mode        : 'all',
+        ids         : []
+      },
+      /*
+       * locked:: whether to lock the query, preventing it from being affected by filters
+       */
+      locked: false
+    };
+    _.defaults($scope.panel,_d);
+
+    $scope.init = function () {
+      $scope.hits = 0;
+
+      $scope.$on('refresh',function(){
+        $scope.get_data();
+      });
+      $scope.get_data();
+
+    };
+
+    $scope.get_data = function(segment,query_id) {
+      delete $scope.panel.error;
+      $scope.panelMeta.loading = true;
+
+      // Make sure we have everything for the request to complete
+      if(dashboard.indices.length === 0) {
+        return;
+      }
+
+      var _segment = _.isUndefined(segment) ? 0 : segment;
+      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(queries, function(q) {
+        var _q = $scope.ejs.FilteredQuery(
+          querySrv.toEjsObj(q),
+          $scope.panel.locked ? null : filterSrv.getBoolFilter(filterSrv.ids()));
+
+        request = request
+          .facet($scope.ejs.QueryFacet(q.id)
+            .query(_q)
+          ).size(0);
+      });
+
+      // Populate the inspector panel
+      $scope.inspector = angular.toJson(JSON.parse(request.toString()),true);
+
+      // Then run it
+      var results = request.doSearch();
+
+      // Populate scope when we have results
+      results.then(function(results) {
+        $scope.panelMeta.loading = false;
+        if(_segment === 0) {
+          $scope.hits = 0;
+          $scope.data = [];
+          query_id = $scope.query_id = new Date().getTime();
+        }
+
+        // Check for error and abort if found
+        if(!(_.isUndefined(results.error))) {
+          $scope.panel.error = $scope.parse_error(results.error);
+          return;
+        }
+
+        // Make sure we're still on the same query/queries
+        if($scope.query_id === query_id) {
+          var i = 0;
+          _.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: q,
+              id: q.id,
+              hits: hits,
+              data: [[i,hits]]
+            };
+
+            i++;
+          });
+          $scope.$emit('render');
+          if(_segment < dashboard.indices.length-1) {
+            $scope.get_data(_segment+1,query_id);
+          }
+
+        }
+      });
+    };
+
+    $scope.set_refresh = function (state) {
+      $scope.refresh = state;
+    };
+
+    $scope.close_edit = function() {
+      if($scope.refresh) {
+        $scope.get_data();
+      }
+      $scope.refresh =  false;
+      $scope.$emit('render');
+    };
+  });
+
+
+  module.directive('hitsChart', function(querySrv) {
+    return {
+      restrict: 'A',
+      link: function(scope, elem) {
+
+        // Receive render events
+        scope.$on('render',function(){
+          render_panel();
+        });
+
+        // Re-render if the window is resized
+        angular.element(window).bind('resize', function(){
+          render_panel();
+        });
+
+        // Function for rendering panel
+        function render_panel() {
+          // IE doesn't work without this
+          elem.css({height:scope.row.height});
+
+          try {
+            _.each(scope.data,function(series) {
+              series.label = series.info.alias;
+              series.color = series.info.color;
+            });
+          } catch(e) {return;}
+
+          // Populate element
+          try {
+            // Add plot to scope so we can build out own legend
+            if(scope.panel.chart === 'bar') {
+              scope.plot = $.plot(elem, scope.data, {
+                legend: { show: false },
+                series: {
+                  lines:  { show: false, },
+                  bars:   { show: true,  fill: 1, barWidth: 0.8, horizontal: false },
+                  shadowSize: 1
+                },
+                yaxis: { show: true, min: 0, color: "#c8c8c8" },
+                xaxis: { show: false },
+                grid: {
+                  borderWidth: 0,
+                  borderColor: '#eee',
+                  color: "#eee",
+                  hoverable: true,
+                },
+                colors: querySrv.colors
+              });
+            }
+            if(scope.panel.chart === 'pie') {
+              scope.plot = $.plot(elem, scope.data, {
+                legend: { show: false },
+                series: {
+                  pie: {
+                    innerRadius: scope.panel.donut ? 0.4 : 0,
+                    tilt: scope.panel.tilt ? 0.45 : 1,
+                    radius: 1,
+                    show: true,
+                    combine: {
+                      color: '#999',
+                      label: 'The Rest'
+                    },
+                    stroke: {
+                      width: 0
+                    },
+                    label: {
+                      show: scope.panel.labels,
+                      radius: 2/3,
+                      formatter: function(label, series){
+                        return '<div ng-click="build_search(panel.query.field,\''+label+'\')'+
+                          ' "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
+                          label+'<br/>'+Math.round(series.percent)+'%</div>';
+                      },
+                      threshold: 0.1
+                    }
+                  }
+                },
+                //grid: { hoverable: true, clickable: true },
+                grid:   { hoverable: true, clickable: true },
+                colors: querySrv.colors
+              });
+            }
+          } catch(e) {
+            elem.text(e);
+          }
+        }
+
+        var $tooltip = $('<div>');
+        elem.bind("plothover", function (event, pos, item) {
+          if (item) {
+            var value = scope.panel.chart === 'bar' ?
+              item.datapoint[1] : item.datapoint[1][0][1];
+            $tooltip
+              .html(kbn.query_color_dot(item.series.color, 20) + ' ' + item.series.label + " (" + value.toFixed(0) + ")")
+              .place_tt(pos.pageX, pos.pageY);
+          } else {
+            $tooltip.remove();
+          }
+        });
+
+      }
+    };
+  });
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/05e188ba/opensoc-ui/lib/public/app/panels/map/editor.html
----------------------------------------------------------------------
diff --git a/opensoc-ui/lib/public/app/panels/map/editor.html b/opensoc-ui/lib/public/app/panels/map/editor.html
new file mode 100755
index 0000000..6648585
--- /dev/null
+++ b/opensoc-ui/lib/public/app/panels/map/editor.html
@@ -0,0 +1,15 @@
+  <div class="editor-row">
+    <div class="editor-option">
+      <form>
+        <h6>Field <tip>2 letter country or state code</tip></h6>
+        <input bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.field" ng-change="set_refresh(true)">
+      </form>
+    </div>
+    <div class="editor-option">
+      <h6>Max <tip>Maximum countries to plot</tip></h6>
+      <input class="input-mini" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
+    </div>
+    <div class="editor-option"><h6>Map</h6>
+      <select ng-change="$emit('render')" class="input-small" ng-model="panel.map" ng-options="f for f in ['world','europe','usa']"></select>
+    </div>
+  </div>