You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by si...@apache.org on 2016/01/14 18:03:03 UTC
[08/85] [partial] incubator-metron git commit: Rename all OpenSOC
files to Metron
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/dasheditor.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/dasheditor.html b/metron-ui/lib/public/app/partials/dasheditor.html
new file mode 100755
index 0000000..e9aaa8c
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/dasheditor.html
@@ -0,0 +1,187 @@
+<div class="modal-body">
+ <div class="pull-right editor-title">Dashboard settings</div>
+
+ <div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
+ <div ng-repeat="tab in ['General','Index','Rows','Controls']" data-title="{{tab}}">
+ </div>
+ <div ng-repeat="tab in dashboard.current.nav|editable" data-title="{{tab.title || tab.type}}">
+ </div>
+ </div>
+
+ <div ng-if="editor.index == 0">
+ <div class="editor-row">
+ <div class="section">
+ <div class="editor-option">
+ <label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.current.title'></input>
+ </div>
+ <div class="editor-option">
+ <label class="small">Style</label><select class="input-small" ng-model="dashboard.current.style" ng-options="f for f in ['dark','light']"></select>
+ </div>
+ <div class="editor-option">
+ <label class="small"> Editable </label><input type="checkbox" ng-model="dashboard.current.editable" ng-checked="dashboard.current.editable" />
+ </div>
+ <div class="editor-option">
+ <label class="small"> Hints <tip>Show 'Add panel' hints in empty spaces</tip></label><input type="checkbox" ng-model="dashboard.current.panel_hints" ng-checked="dashboard.current.panel_hints" />
+ </div>
+ <!-- <div class="editor-option">
+ <label class="small"> Real-Time <tip>Enables real-time alerts</tip></label><input type="checkbox" ng-model="" ng-checked="dashboard.current.realtime" />
+ </div> -->
+ </div>
+ </div>
+ </div>
+ <div ng-if="editor.index == 1">
+ <div class="editor-row">
+ <div class="section">
+ <h5>Index Settings</h5>
+ <div ng-show="dashboard.current.index.interval != 'none'" class="row-fluid">
+ <div class="editor-option">
+ <p class="small">
+ Time stamped indices use your selected time range to create a list of
+ indices that match a specified timestamp pattern. This can be very
+ efficient for some data sets (eg, logs) For example, to match the
+ default logstash index pattern you might use
+ <code>[logstash-]YYYY.MM.DD</code>. The [] in "[logstash-]" are
+ important as they instruct Kibana not to treat those letters as a
+ pattern. You may also specify multiple indices by seperating them with a comma(,).
+ For example <code>[web-]YYYY.MM.DD,[mail-]YYYY.MM.DD</code>
+ Please also note that indices should rollover at midnight <strong>UTC</strong>.
+ </p>
+ <p class="small">
+ See <a href="http://momentjs.com/docs/#/displaying/format/">http://momentjs.com/docs/#/displaying/format/</a>
+ for documentation on date formatting.
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="editor-row">
+ <div class="section">
+ <div class="editor-option">
+ <h6>Timestamping</h6><select class="input-small" ng-model="dashboard.current.index.interval" ng-options="f for f in ['none','hour','day','week','month','year']"></select>
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.index.interval != 'none'">
+ <h6>Index pattern <small>Absolutes in []</small></h6>
+ <input type="text" class="input-large" ng-model="dashboard.current.index.pattern">
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.index.interval != 'none'">
+ <h6>Failover <i class="icon-question-sign" bs-tooltip="'If no indices match the pattern, failover to default index *NOT RECOMMENDED*'"></i></h6>
+ <input type="checkbox" ng-model="dashboard.current.failover" ng-checked="dashboard.current.failover" />
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.failover || dashboard.current.index.interval == 'none'">
+ <h6>Default Index <small ng-show="dashboard.current.index.interval != 'none'">If index not found</small></h6>
+ <input type="text" class="input-medium" ng-model="dashboard.current.index.default">
+ </div>
+ <div class="editor-option">
+ <h6>Preload Fields <i class="icon-question-sign" bs-tooltip="'Preload available fields for the purpose of autocomplete. Turn this off if you have many fields'"></i></h6>
+ <input type="checkbox" ng-model="dashboard.current.index.warm_fields" ng-checked="dashboard.current.index.warm_fields" />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div ng-if="editor.index == 2">
+ <div class="row-fluid">
+ <div class="span8">
+ <h4>Rows</h4>
+ <table class="table table-striped">
+ <thead>
+ <th width="1%"></th>
+ <th width="1%"></th>
+ <th width="1%"></th>
+ <th width="97%">Title</th>
+ </thead>
+ <tr ng-repeat="row in dashboard.current.rows">
+ <td><i ng-click="_.move(dashboard.current.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
+ <td><i ng-click="_.move(dashboard.current.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
+ <td><i ng-click="dashboard.current.rows = _.without(dashboard.current.rows,row)" class="pointer icon-remove"></i></td>
+ <td>{{row.title||'Untitled'}}</td>
+ </tr>
+ </table>
+ </div>
+ <div class="span4">
+ <h4>Add Row</h4>
+ <label class="small">Title</label>
+ <input type="text" class="input-medium" ng-model='row.title' placeholder="New row"></input>
+ <label class="small">Height</label>
+ <input type="text" class="input-mini" ng-model='row.height'></input>
+ </div>
+ </div>
+ <div class="row-fluid">
+
+ </div>
+ </div>
+
+ <div ng-if="editor.index == 3" ng-controller="dashLoader">
+ <div class="editor-row">
+ <div class="section">
+ <h5>Save to</h5>
+ <div class="editor-option">
+ <label class="small">Export</label><input type="checkbox" ng-model="dashboard.current.loader.save_local" ng-checked="dashboard.current.loader.save_local">
+ </div>
+ <div class="editor-option">
+ <label class="small">Browser</label><input type="checkbox" ng-model="dashboard.current.loader.save_default" ng-checked="dashboard.current.loader.save_default">
+ </div>
+ <div class="editor-option">
+ <label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_gist" ng-checked="dashboard.current.loader.save_gist">
+ </div>
+ <div class="editor-option">
+ <label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.save_elasticsearch" ng-checked="dashboard.current.loader.save_elasticsearch">
+ </div>
+ </div>
+ <div class="section">
+ <h5>Load from</h5>
+ <div class="editor-option">
+ <label class="small">Local file</label><input type="checkbox" ng-model="dashboard.current.loader.load_local" ng-checked="dashboard.current.loader.load_local">
+ </div>
+ <div class="editor-option">
+ <label class="small">Gist</label><input type="checkbox" ng-model="dashboard.current.loader.load_gist" ng-checked="dashboard.current.loader.load_gist">
+ </div>
+ <div class="editor-option">
+ <label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.load_elasticsearch" ng-checked="dashboard.current.loader.load_elasticsearch">
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.loader.load.elasticsearch">
+ <label class="small">ES list size</label><input class="input-mini" type="number" ng-model="dashboard.current.loader.load_elasticsearch_size">
+ </div>
+ </div>
+ <div class="section">
+ <h5>Sharing</h5>
+ <div class="editor-option" >
+ <label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp" ng-checked="dashboard.current.loader.save_temp">
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.loader.save_temp">
+ <label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp_ttl_enable">
+ </div>
+ <div class="editor-option" ng-show="dashboard.current.loader.save_temp && dashboard.current.loader.save_temp_ttl_enable">
+ <label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="dashboard.current.loader.save_temp_ttl">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div ng-if="editor.index == 3">
+ <div class="editor-row">
+ <div class="section">
+ <h5>Pulldowns</h5>
+ <div class="editor-option" ng-repeat="pulldown in dashboard.current.pulldowns">
+ <label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
+ </div>
+ <div class="editor-option" ng-repeat="pulldown in dashboard.current.nav|editable">
+ <label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div ng-repeat="pulldown in dashboard.current.nav|editable" ng-controller="PulldownCtrl" ng-show="editor.index == 4+$index">
+ <ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
+ <button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
+ </div>
+
+
+</div>
+
+<div class="modal-footer">
+ <button type="button" ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-info" ng-show="editor.index == 2">Create Row</button>
+ <button type="button" class="btn btn-success" ng-click="editor.index=0;editSave(dashboard);dismiss();reset_panel();dashboard.refresh()">Save</button>
+ <button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Cancel</button>
+</div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/inspector.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/inspector.html b/metron-ui/lib/public/app/partials/inspector.html
new file mode 100755
index 0000000..0b2ebe6
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/inspector.html
@@ -0,0 +1,15 @@
+<div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3>Last Elasticsearch Query</h3>
+</div>
+<div class="modal-body">
+
+ <div>
+ <pre>curl -XGET '{{config.elasticsearch}}/{{dashboard.indices|stringify}}/_search?pretty' -d '{{inspector}}'
+ </pre>
+ </div>
+
+</div>
+<div class="modal-footer">
+ <button type="button" class="btn btn-success" ng-click="dismiss()">Close</button>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/load.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/load.html b/metron-ui/lib/public/app/partials/load.html
new file mode 100755
index 0000000..27d012b
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/load.html
@@ -0,0 +1,4 @@
+<div style="margin-top:50px" ng-controller="dashcontrol">
+ <strong>type: </strong>{{type}} <br>
+ <strong>id: </strong>{{id}} <br>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/modal.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/modal.html b/metron-ui/lib/public/app/partials/modal.html
new file mode 100755
index 0000000..38d86cd
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/modal.html
@@ -0,0 +1,12 @@
+<div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3>{{modal.title}}</h3>
+</div>
+<div class="modal-body">
+
+ <div ng-bind-html='modal.body'></div>
+
+</div>
+<div class="modal-footer">
+ <button type="button" class="btn btn-danger" ng-click="dismiss()">Close</button>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/paneladd.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/paneladd.html b/metron-ui/lib/public/app/partials/paneladd.html
new file mode 100755
index 0000000..5b780fb
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/paneladd.html
@@ -0,0 +1,6 @@
+ <div ng-include="'app/partials/panelgeneral.html'"></div>
+ <div ng-include="edit_path(panel.type)"></div>
+ <div ng-repeat="tab in panelMeta.editorTabs">
+ <h5>{{tab.title}}</h5>
+ <div ng-include="tab.src"></div>
+ </div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/paneleditor.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/paneleditor.html b/metron-ui/lib/public/app/partials/paneleditor.html
new file mode 100755
index 0000000..d66070b
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/paneleditor.html
@@ -0,0 +1,24 @@
+<div bindonce class="modal-body">
+ <div class="pull-right editor-title" bo-text="panel.type+' settings'"></div>
+ <div ng-model="editor.index" bs-tabs>
+ <div ng-repeat="tab in setEditorTabs(panelMeta)" data-title="{{tab}}">
+ </div>
+ </div>
+ <div ng-show="editorTabs[editor.index] == 'General'">
+ <div ng-include src="'app/partials/panelgeneral.html'"></div>
+ </div>
+
+ <div ng-show="editorTabs[editor.index] == 'Panel'">
+ <div ng-include src="edit_path(panel.type)"></div>
+ </div>
+
+ <div ng-repeat="tab in panelMeta.editorTabs" ng-show="editorTabs[editor.index] == tab.title">
+ <div ng-include src="tab.src"></div>
+ </div>
+</div>
+
+<div class="modal-footer">
+ <!-- close_edit() is provided here to allow for a scope to perform action on dismiss -->
+ <button type="button" class="btn btn-success" ng-click="editor.index=0;editSave(panel);close_edit();dismiss()">Save</button>
+ <button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss()">Cancel</button>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/panelgeneral.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/panelgeneral.html b/metron-ui/lib/public/app/partials/panelgeneral.html
new file mode 100755
index 0000000..22e7494
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/panelgeneral.html
@@ -0,0 +1,24 @@
+ <div class="editor-row">
+ <div class="section">
+ <strong>{{panelMeta.status}}</strong> // <span ng-bind-html="panelMeta.description"></span>
+ </div>
+ </div>
+ <div class="editor-row">
+ <div class="section">
+ <div class="editor-option">
+ <label class="small">Title</label><input type="text" class="input-medium" ng-model='panel.title'></input>
+ </div>
+ <div class="editor-option" ng-hide="panel.sizeable == false">
+ <label class="small">Span</label> <select class="input-mini" ng-model="panel.span" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]"></select>
+ </div>
+ <div class="editor-option">
+ <label class="small">Editable</label><input type="checkbox" ng-model="panel.editable" ng-checked="panel.editable">
+ </div>
+ <div class="editor-option" ng-show="!_.isUndefined(panel.spyable)">
+ <label class="small">
+ Inspect <i class="icon-question-sign" bs-tooltip="'Allow query reveal via <i class=icon-info-sign></i>'"></i>
+ </label>
+ <input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
+ </div>
+ </div>
+ </div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/querySelect.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/querySelect.html b/metron-ui/lib/public/app/partials/querySelect.html
new file mode 100755
index 0000000..a580a48
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/querySelect.html
@@ -0,0 +1,28 @@
+ <div class="row-fluid">
+ <style>
+ .querySelect .query {
+ margin-right: 5px;
+ }
+ .querySelect .selected {
+ border: 3px solid;
+ }
+ .querySelect .unselected {
+ border: 0px solid;
+ }
+ </style>
+ <div class="span2" style="margin-left:0px">
+ <label class="small">Queries</label>
+ <select class="input-small" ng-change="set_refresh(true);" ng-model="panel.queries.mode" ng-options="f for f in ['all','pinned','unpinned','selected']"></select>
+ </div>
+ <div class="span2">
+ <label class="small">Locked</label>
+ <input type="checkbox" ng-change="set_refresh(true);" ng-model="panel.locked">
+ </div>
+ <div class="span7 querySelect" ng-show="panel.queries.mode == 'selected'">
+ <label class="small">Selected Queries</label>
+ <span ng-style="{'border-color': querySrv.list()[id].color}" ng-class="{selected:_.contains(panel.queries.ids,id),unselected:!_.contains(panel.queries.ids,id)}" ng-repeat="id in querySrv.ids()" ng-click="panel.queries.ids = _.toggleInOut(panel.queries.ids,id);set_refresh(true);" class="query pointer badge">
+ <i class="icon-circle" ng-style="{color: querySrv.list()[id].color}"></i>
+ <span> {{querySrv.list()[id].alias || querySrv.list()[id].query}}</span>
+ </span>
+ </div>
+ </div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/partials/roweditor.html
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/partials/roweditor.html b/metron-ui/lib/public/app/partials/roweditor.html
new file mode 100755
index 0000000..b2240e7
--- /dev/null
+++ b/metron-ui/lib/public/app/partials/roweditor.html
@@ -0,0 +1,67 @@
+<div class="modal-body">
+ <div class="pull-right editor-title">Row settings</div>
+
+ <div ng-model="editor.index" bs-tabs>
+ <div ng-repeat="tab in ['General','Panels','Add Panel']" data-title="{{tab}}">
+ </div>
+ </div>
+
+ <div class="editor-row" ng-if="editor.index == 0">
+ <div class="editor-option">
+ <label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
+ </div>
+ <div class="editor-option">
+ <label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
+ </div>
+ <div class="editor-option">
+ <label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
+ </div>
+ <div class="editor-option">
+ <label class="small"> Collapsable </label><input type="checkbox" ng-model="row.collapsable" ng-checked="row.collapsable" />
+ </div>
+ </div>
+ <div class="row-fluid" ng-if="editor.index == 1">
+ <div class="span12">
+ <h4>Panels</h4>
+ <table class="table table-condensed table-striped">
+ <thead>
+ <th>Title</th>
+ <th>Type</th>
+ <th>Span <span class="small">({{rowSpan(row)}}/12)</span></th>
+ <th>Delete</th>
+ <th>Move</th>
+ <th></th>
+ <th>Hide</th>
+ </thead>
+ <tr ng-repeat="panel in row.panels">
+ <td>{{panel.title}}</td>
+ <td>{{panel.type}}</td>
+ <td><select ng-hide="panel.sizeable == false" class="input-mini" ng-model="panel.span" ng-options="size for size in [1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
+ <td><i ng-click="row.panels = _.without(row.panels,panel)" class="pointer icon-remove"></i></td>
+ <td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
+ <td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
+ <td><input type="checkbox" ng-model="panel.hide" ng-checked="panel.hide"></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="row-fluid" ng-if="editor.index == 2">
+ <h4>Select Panel Type</h4>
+ <form class="form-inline">
+ <select class="input-medium" ng-model="panel.type" ng-options="panelType for panelType in dashboard.availablePanels|stringSort"></select>
+ <small ng-show="rowSpan(row) > 11">
+ Note: This row is full, new panels will wrap to a new line. You should add another row.
+ </small>
+ </form>
+
+ <div ng-show="!(_.isUndefined(panel.type))">
+ <div add-panel="{{panel.type}}"></div>
+ </div>
+ </div>
+</div>
+<div class="modal-footer">
+ <button ng-show="editor.index == 1" ng-click="editor.index = 2;" class="btn btn-info" ng-disabled="panel.loadingEditor">Add Panel</button>
+ <button ng-show="panel.type && editor.index == 2" ng-click="editSave(row);add_panel(row,panel);reset_panel();editor.index = 0;dismiss();" class="btn btn-success" ng-disabled="panel.loadingEditor">Save</button>
+ <button ng-hide="panel.type && editor.index == 2" ng-click="editor.index=0;editSave(row);dismiss();reset_panel();close_edit()" class="btn btn-success">Save</button>
+ <button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();close_edit()">Cancel</button>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/alertSrv.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/alertSrv.js b/metron-ui/lib/public/app/services/alertSrv.js
new file mode 100755
index 0000000..8d3d095
--- /dev/null
+++ b/metron-ui/lib/public/app/services/alertSrv.js
@@ -0,0 +1,49 @@
+define([
+ 'angular',
+ 'lodash'
+],
+function (angular, _) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('alertSrv', function($timeout) {
+ var self = this;
+
+ // List of all alert objects
+ this.list = [];
+
+ this.set = function(title,text,severity,timeout) {
+ var
+ _a = {
+ title: title || '',
+ text: text || '',
+ severity: severity || 'info',
+ },
+ _ca = angular.toJson(_a),
+ _clist = _.map(self.list,function(alert){return angular.toJson(alert);});
+
+ // If we already have this alert, remove it and add a new one
+ // Why do this instead of skipping the add because it resets the timer
+ if(_.contains(_clist,_ca)) {
+ _.remove(self.list,_.indexOf(_clist,_ca));
+ }
+
+ self.list.push(_a);
+ if (timeout > 0) {
+ $timeout(function() {
+ self.list = _.without(self.list,_a);
+ }, timeout);
+ }
+ return(_a);
+ };
+
+ this.clear = function(alert) {
+ self.list = _.without(self.list,alert);
+ };
+
+ this.clearAll = function() {
+ self.list = [];
+ };
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/all.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/all.js b/metron-ui/lib/public/app/services/all.js
new file mode 100755
index 0000000..fdc5408
--- /dev/null
+++ b/metron-ui/lib/public/app/services/all.js
@@ -0,0 +1,12 @@
+define([
+ './alertSrv',
+ './dashboard',
+ './fields',
+ './filterSrv',
+ './kbnIndex',
+ './querySrv',
+ './timer',
+ './panelMove',
+ './esVersion'
+],
+function () {});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/dashboard.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/dashboard.js b/metron-ui/lib/public/app/services/dashboard.js
new file mode 100755
index 0000000..6b4634c
--- /dev/null
+++ b/metron-ui/lib/public/app/services/dashboard.js
@@ -0,0 +1,529 @@
+define([
+ 'angular',
+ 'jquery',
+ 'kbn',
+ 'lodash',
+ 'config',
+ 'moment',
+ 'modernizr',
+ 'filesaver',
+ 'blob'
+],
+function (angular, $, kbn, _, config, moment, Modernizr) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('dashboard', function(
+ $routeParams, $http, $rootScope, $injector, $location, $timeout,
+ ejsResource, timer, kbnIndex, alertSrv, esVersion, esMinVersion
+ ) {
+ // A hash of defaults to use when loading a dashboard
+
+ var _dash = {
+ title: "",
+ style: "dark",
+ editable: true,
+ realtime: true, // defaults to false
+ failover: false,
+ panel_hints: true,
+ rows: [],
+ pulldowns: [
+ {
+ type: 'query',
+ },
+ {
+ type: 'filtering'
+ }
+ ],
+ nav: [
+ {
+ type: 'timepicker'
+ }
+ ],
+ services: {},
+ loader: {
+ save_gist: false,
+ save_elasticsearch: true,
+ save_local: true,
+ save_default: true,
+ save_temp: true,
+ save_temp_ttl_enable: true,
+ save_temp_ttl: '30d',
+ load_gist: false,
+ load_elasticsearch: true,
+ load_elasticsearch_size: 20,
+ load_local: false,
+ hide: false
+ },
+ index: {
+ interval: 'none',
+ pattern: '_all',
+ default: 'INDEX_MISSING',
+ warm_fields: true
+ },
+ refresh: false
+ };
+
+ // An elasticJS client to use
+ var ejs = ejsResource(config.elasticsearch);
+
+ var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
+
+ // Store a reference to this
+ var self = this;
+ var filterSrv,querySrv;
+
+ this.current = _.clone(_dash);
+ this.last = {};
+ this.availablePanels = [];
+
+ $rootScope.$on('$routeChangeSuccess',function(){
+ // Clear the current dashboard to prevent reloading
+ self.current = {};
+ self.indices = [];
+ esVersion.isMinimum().then(function(isMinimum) {
+ if(isMinimum) {
+ route();
+ } else {
+ alertSrv.set('Upgrade Required',"Your version of Elasticsearch is too old. Kibana requires" +
+ " Elasticsearch " + esMinVersion + " or above.", "error");
+ }
+ });
+ });
+
+ var route = function() {
+ // Is there a dashboard type and id in the URL?
+ if(!(_.isUndefined($routeParams.kbnType)) && !(_.isUndefined($routeParams.kbnId))) {
+ var _type = $routeParams.kbnType;
+ var _id = $routeParams.kbnId;
+
+ switch(_type) {
+ case ('elasticsearch'):
+ self.elasticsearch_load('dashboard',_id);
+ break;
+ case ('temp'):
+ self.elasticsearch_load('temp',_id);
+ break;
+ case ('file'):
+ self.file_load(_id);
+ break;
+ case('script'):
+ self.script_load(_id);
+ break;
+ case('local'):
+ self.local_load();
+ break;
+ default:
+ $location.path(config.default_route);
+ }
+ // No dashboard in the URL
+ } else {
+ // Check if browser supports localstorage, and if there's an old dashboard. If there is,
+ // inform the user that they should save their dashboard to Elasticsearch and then set that
+ // as their default
+ if (Modernizr.localstorage) {
+ if(!(_.isUndefined(window.localStorage['dashboard'])) && window.localStorage['dashboard'] !== '') {
+ $location.path(config.default_route);
+ alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+
+ ' Click <a href="#/dashboard/local/deprecated">here</a> to load your old dashboard anyway.');
+ } else if(!(_.isUndefined(window.localStorage.kibanaDashboardDefault))) {
+ $location.path(window.localStorage.kibanaDashboardDefault);
+ } else {
+ $location.path(config.default_route);
+ }
+ // No? Ok, grab the default route, its all we have now
+ } else {
+ $location.path(config.default_route);
+ }
+ }
+ };
+
+ // Since the dashboard is responsible for index computation, we can compute and assign the indices
+ // here before telling the panels to refresh
+ this.refresh = function() {
+ if(self.current.index.interval !== 'none') {
+ if(_.isUndefined(filterSrv)) {
+ return;
+ }
+ if(filterSrv.idsByType('time').length > 0) {
+ var _range = filterSrv.timeRange('last');
+ kbnIndex.indices(_range.from,_range.to,
+ self.current.index.pattern,self.current.index.interval
+ ).then(function (p) {
+ if(p.length > 0) {
+ self.indices = p;
+ } else {
+ // Option to not failover
+ if(self.current.failover) {
+ self.indices = [self.current.index.default];
+ } else {
+ // Do not issue refresh if no indices match. This should be removed when panels
+ // properly understand when no indices are present
+ alertSrv.set('No results','There were no results because no indices were found that match your'+
+ ' selected time span','info',5000);
+ return false;
+ }
+ }
+ // 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];
+ querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');});
+ } else {
+ alertSrv.set("No time filter",
+ 'Timestamped indices are configured without a failover. Waiting for time filter.',
+ 'info',5000);
+ }
+ }
+ } else {
+ self.indices = [self.current.index.default];
+ querySrv.resolve().then(function(){$rootScope.$broadcast('refresh');});
+ }
+ };
+
+ var dash_defaults = function(dashboard) {
+ _.defaults(dashboard,_dash);
+ _.defaults(dashboard.index,_dash.index);
+ _.defaults(dashboard.loader,_dash.loader);
+ return _.cloneDeep(dashboard);
+ };
+
+ this.dash_load = function(dashboard) {
+ // Cancel all timers
+ timer.cancel_all();
+
+ // Make sure the dashboard being loaded has everything required
+ dashboard = dash_defaults(dashboard);
+
+ // If not using time based indices, use the default index
+ if(dashboard.index.interval === 'none') {
+ self.indices = [dashboard.index.default];
+ }
+
+ // Set the current dashboard
+ self.current = _.clone(dashboard);
+
+ // Delay this until we're sure that querySrv and filterSrv are ready
+ $timeout(function() {
+ // Ok, now that we've setup the current dashboard, we can inject our services
+ if(!_.isUndefined(self.current.services.query)) {
+ querySrv = $injector.get('querySrv');
+ querySrv.init();
+ }
+ if(!_.isUndefined(self.current.services.filter)) {
+ filterSrv = $injector.get('filterSrv');
+ filterSrv.init();
+ }
+ },0).then(function() {
+ // Call refresh to calculate the indices and notify the panels that we're ready to roll
+ self.refresh();
+ });
+
+ if(dashboard.refresh) {
+ self.set_interval(dashboard.refresh);
+ }
+
+ // Set the available panels for the "Add Panel" drop down
+ self.availablePanels = _.difference(config.panel_names,
+ _.pluck(_.union(self.current.nav,self.current.pulldowns),'type'));
+
+ // Take out any that we're not allowed to add from the gui.
+ self.availablePanels = _.difference(self.availablePanels,config.hidden_panels);
+
+ return true;
+ };
+
+ this.gist_id = function(string) {
+ if(self.is_gist(string)) {
+ return string.match(gist_pattern)[0].replace(/.*\//, '');
+ }
+ };
+
+ this.is_gist = function(string) {
+ if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
+ return string.match(gist_pattern).length > 0 ? true : false;
+ } else {
+ return false;
+ }
+ };
+
+ this.to_file = function() {
+ var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
+ // from filesaver.js
+ window.saveAs(blob, self.current.title+"-"+new Date().getTime());
+ return true;
+ };
+
+ this.set_default = function(route) {
+ if (Modernizr.localstorage) {
+ // Purge any old dashboards
+ if(!_.isUndefined(window.localStorage['dashboard'])) {
+ delete window.localStorage['dashboard'];
+ }
+ window.localStorage.kibanaDashboardDefault = route;
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ this.purge_default = function() {
+ if (Modernizr.localstorage) {
+ // Purge any old dashboards
+ if(!_.isUndefined(window.localStorage['dashboard'])) {
+
+ delete window.localStorage['dashboard'];
+ }
+ delete window.localStorage.kibanaDashboardDefault;
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
+ this.share_link = function(title,type,id) {
+ return {
+ location : window.location.href.replace(window.location.hash,""),
+ type : type,
+ id : id,
+ link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+encodeURIComponent(id),
+ title : title
+ };
+ };
+
+ var renderTemplate = function(json,params) {
+ var _r;
+ _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
+ var template = _.template(json);
+ var rendered = template({ARGS:params});
+ try {
+ _r = angular.fromJson(rendered);
+ } catch(e) {
+ _r = false;
+ }
+ return _r;
+ };
+
+ this.local_load = function() {
+ var dashboard = JSON.parse(window.localStorage['dashboard']);
+ dashboard.rows.unshift({
+ height: "30",
+ title: "Deprecation Notice",
+ panels: [
+ {
+ title: 'WARNING: Legacy dashboard',
+ type: 'text',
+ span: 12,
+ mode: 'html',
+ content: 'This dashboard has been loaded from the browsers local cache. If you use '+
+ 'another brower or computer you will not be able to access it! '+
+ '\n\n <h4>Good news!</h4> Kibana'+
+ ' now stores saved dashboards in Elasticsearch. Click the <i class="icon-save"></i> '+
+ 'button in the top left to save this dashboard. Then select "Set as Home" from'+
+ ' the "advanced" sub menu to automatically use the stored dashboard as your Kibana '+
+ 'landing page afterwards'+
+ '<br><br><strong>Tip:</strong> You may with to remove this row before saving!'
+ }
+ ]
+ });
+ self.dash_load(dashboard);
+ };
+
+ this.file_load = function(file) {
+ return $http({
+ url: "app/dashboards/"+file.replace(/\.(?!json)/,"/")+'?' + new Date().getTime(),
+ method: "GET",
+ transformResponse: function(response) {
+ return renderTemplate(response,$routeParams);
+ }
+ }).then(function(result) {
+ if(!result) {
+ return false;
+ }
+ self.dash_load(dash_defaults(result.data));
+ return true;
+ },function() {
+ alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
+ return false;
+ });
+ };
+
+ this.elasticsearch_load = function(type,id) {
+ var successcb = function(data) {
+ var response = renderTemplate(angular.fromJson(data)._source.dashboard, $routeParams);
+ self.dash_load(response);
+ };
+ var errorcb = function(data, status) {
+ if(status === 0) {
+ alertSrv.set('Error',"Could not contact Elasticsearch at "+ejs.config.server+
+ ". Please ensure that Elasticsearch is reachable from your system." ,'error');
+ } else {
+ alertSrv.set('Error',"Could not find "+id+". If you"+
+ " are using a proxy, ensure it is configured correctly",'error');
+ }
+ return false;
+ };
+
+ ejs.client.get(
+ "/" + config.kibana_index + "/"+type+"/"+id+'?' + new Date().getTime(),
+ null, successcb, errorcb);
+
+ };
+
+ this.script_load = function(file) {
+ return $http({
+ url: "app/dashboards/"+file.replace(/\.(?!js)/,"/"),
+ method: "GET",
+ transformResponse: function(response) {
+ /*jshint -W054 */
+ var _f = new Function('ARGS','kbn','_','moment','window','document','angular','require','define','$','jQuery',response);
+ return _f($routeParams,kbn,_,moment);
+ }
+ }).then(function(result) {
+ if(!result) {
+ return false;
+ }
+ self.dash_load(dash_defaults(result.data));
+ return true;
+ },function() {
+ alertSrv.set('Error',
+ "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard" ,
+ 'error');
+ return false;
+ });
+ };
+
+ this.elasticsearch_save = function(type,title,ttl) {
+ // Clone object so we can modify it without influencing the existing obejct
+ var save = _.clone(self.current);
+ var id;
+
+ // Change title on object clone
+ if (type === 'dashboard') {
+ id = save.title = _.isUndefined(title) ? self.current.title : title;
+ }
+
+ // Create request with id as title. Rethink this.
+ var request = ejs.Document(config.kibana_index,type,id).source({
+ user: 'guest',
+ group: 'guest',
+ title: save.title,
+ dashboard: angular.toJson(save)
+ });
+
+ request = type === 'temp' && ttl ? request.ttl(ttl) : request;
+
+ return request.doIndex(
+ // Success
+ function(result) {
+ if(type === 'dashboard') {
+ $location.path('/dashboard/elasticsearch/'+title);
+ }
+ return result;
+ },
+ // Failure
+ function() {
+ return false;
+ }
+ );
+ };
+
+ this.elasticsearch_delete = function(id) {
+ return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
+ // Success
+ function(result) {
+ return result;
+ },
+ // Failure
+ function() {
+ return false;
+ }
+ );
+ };
+
+ this.elasticsearch_list = function(query,count) {
+ var request = ejs.Request().indices(config.kibana_index).types('dashboard');
+ return request.query(
+ ejs.QueryStringQuery(query || '*')
+ ).size(count).doSearch(
+ // Success
+ function(result) {
+ return result;
+ },
+ // Failure
+ function() {
+ return false;
+ }
+ );
+ };
+
+ this.save_gist = function(title,dashboard) {
+ var save = _.clone(dashboard || self.current);
+ save.title = title || self.current.title;
+ return $http({
+ url: "https://api.github.com/gists",
+ method: "POST",
+ data: {
+ "description": save.title,
+ "public": false,
+ "files": {
+ "kibana-dashboard.json": {
+ "content": angular.toJson(save,true)
+ }
+ }
+ }
+ }).then(function(data) {
+ return data.data.html_url;
+ }, function() {
+ return false;
+ });
+ };
+
+ this.gist_list = function(id) {
+ return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
+ ).then(function(response) {
+ var files = [];
+ _.each(response.data.data.files,function(v) {
+ try {
+ var file = JSON.parse(v.content);
+ files.push(file);
+ } catch(e) {
+ return false;
+ }
+ });
+ return files;
+ }, function() {
+ return false;
+ });
+ };
+
+ this.start_scheduled_refresh = function (after_ms) {
+ timer.cancel(self.refresh_timer);
+ self.refresh_timer = timer.register($timeout(function () {
+ self.start_scheduled_refresh(after_ms);
+ self.refresh();
+ }, after_ms));
+ };
+
+ this.cancel_scheduled_refresh = function () {
+ timer.cancel(self.refresh_timer);
+ };
+
+ this.set_interval = function (interval) {
+ self.current.refresh = interval;
+ if (interval) {
+ var _i = kbn.interval_to_ms(interval);
+ this.start_scheduled_refresh(_i);
+ } else {
+ this.cancel_scheduled_refresh();
+ }
+ };
+
+
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/esVersion.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/esVersion.js b/metron-ui/lib/public/app/services/esVersion.js
new file mode 100755
index 0000000..1a31e85
--- /dev/null
+++ b/metron-ui/lib/public/app/services/esVersion.js
@@ -0,0 +1,195 @@
+define([
+ 'angular',
+ 'lodash',
+ 'config'
+],
+function (angular, _, config) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('esVersion', function($http, alertSrv, esMinVersion, $q, ejsResource) {
+
+ this.versions = [];
+
+ var ejs = ejsResource(config.elasticsearch);
+
+
+ // save a reference to this
+ var self = this,
+ defer = $q.defer();
+
+ this.init = function() {
+ getVersions();
+ };
+
+ var getVersions = function() {
+ if(self.versions.length !== 0) {
+ defer.resolve(self.versions);
+ return defer.promise;
+ } else {
+ var nodeInfo = ejs.client.get('/_nodes',
+ undefined, undefined, function(data, status) {
+ if(status === 0) {
+ alertSrv.set('Error',"Could not contact Elasticsearch at "+ejs.client.server()+
+ ". Please ensure that Elasticsearch is reachable from your system." ,'error');
+ } else {
+ alertSrv.set('Error',"Could not reach "+ejs.client.server()+"/_nodes. If you"+
+ " are using a proxy, ensure it is configured correctly",'error');
+ }
+ return;
+ });
+
+ return nodeInfo.then(function(p) {
+ _.each(p.nodes, function(v) {
+ self.versions.push(v.version.split('-')[0]);
+ });
+ self.versions = sortVersions(_.uniq(self.versions));
+ return self.versions;
+ });
+ }
+
+ };
+
+ // Get the max version in this cluster
+ this.max = function(versions) {
+ return _.last(versions);
+ };
+
+ // Return the lowest version in the cluster
+ this.min = function(versions) {
+ return _.first(versions);
+ };
+
+ // Sort versions from lowest to highest
+ var sortVersions = function(versions) {
+ var _versions = _.clone(versions),
+ _r = [];
+
+ while(_r.length < versions.length) {
+ var _h = "0";
+ /*jshint -W083 */
+ _.each(_versions,function(v){
+ if(self.compare(_h,v)) {
+ _h = v;
+ }
+ });
+ _versions = _.without(_versions,_h);
+ _r.push(_h);
+ }
+ return _r.reverse();
+ };
+
+ /*
+ Takes a version string with one of the following optional comparison prefixes: >,>=,<.<=
+ and evaluates if the cluster meets the requirement. If the prefix is omitted exact match
+ is assumed
+ */
+ this.is = function(equation) {
+ var _v = equation,
+ _cf;
+
+ if(_v.charAt(0) === '>') {
+ _cf = _v.charAt(1) === '=' ? self.gte(_v.slice(2)) : self.gt(_v.slice(1));
+ } else if (_v.charAt(0) === '<') {
+ _cf = _v.charAt(1) === '=' ? self.lte(_v.slice(2)) : self.lt(_v.slice(1));
+ } else {
+ _cf = self.eq(_v);
+ }
+
+ return _cf;
+ };
+
+ this.isMinimum = function() {
+ return self.gte(esMinVersion);
+ };
+
+ // check if lowest version in cluster = `version`
+ this.eq = function(version) {
+ return getVersions().then(function(v) {
+ return version === self.min(v) ? true : false;
+ });
+
+ };
+
+ // version > lowest version in cluster?
+ this.gt = function(version) {
+ return getVersions().then(function(v) {
+ return version === self.min(v) ? false : self.gte(version);
+ });
+
+ };
+
+ // version < highest version in cluster?
+ this.lt = function(version) {
+ return getVersions().then(function(v) {
+ return version === self.max(v) ? false : self.lte(version);
+ });
+
+ };
+
+ // Check if the lowest version in the cluster is >= to `version`
+ this.gte = function(version) {
+ return getVersions().then(function(v) {
+ return self.compare(version,self.min(v));
+ });
+
+ };
+
+ // Check if the highest version in the cluster is <= to `version`
+ this.lte = function(version) {
+ return getVersions().then(function(v) {
+ return self.compare(self.max(v),version);
+ });
+ };
+
+ // Determine if a specific version is greater than or equal to another
+ this.compare = function (required,installed) {
+ if(!required || !installed) {
+ return undefined;
+ }
+
+ var a = installed.split('.');
+ var b = required.split('.');
+ var i;
+
+ // leave suffixes as is ("RC1 or -SNAPSHOT")
+ for (i = 0; i < Math.min(a.length, 3); ++i) {
+ a[i] = Number(a[i]);
+ }
+ for (i = 0; i < Math.min(b.length, 3); ++i) {
+ b[i] = Number(b[i]);
+ }
+ if (a.length === 2) {
+ a[2] = 0;
+ }
+
+ if (a[0] > b[0]){return true;}
+ if (a[0] < b[0]){return false;}
+
+ if (a[1] > b[1]){return true;}
+ if (a[1] < b[1]){return false;}
+
+ if (a[2] > b[2]){return true;}
+ if (a[2] < b[2]){return false;}
+
+ if (a.length > 3) {
+ // rc/beta suffix
+ if (b.length <= 3) {
+ return false;
+ } // no suffix on b -> a<b
+ return a[3] >= b[3];
+ }
+ if (b.length > 3) {
+ // b has a suffix but a not -> a>b
+ return true;
+ }
+
+ return true;
+ };
+
+ this.init();
+
+ });
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/fields.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/fields.js b/metron-ui/lib/public/app/services/fields.js
new file mode 100755
index 0000000..3d01490
--- /dev/null
+++ b/metron-ui/lib/public/app/services/fields.js
@@ -0,0 +1,111 @@
+define([
+ 'angular',
+ 'lodash',
+ 'config'
+],
+function (angular, _, config) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('fields', function(dashboard, $rootScope, $http, esVersion, alertSrv, ejsResource) {
+
+ // Save a reference to this
+ var self = this;
+
+ var ejs = ejsResource(config.elasticsearch);
+
+
+ this.list = ['_type'];
+ this.indices = [];
+
+ // Stop tracking the full mapping, too expensive, instead we only remember the index names
+ // we've already seen.
+ //
+ $rootScope.$watch(function(){return dashboard.indices;},function(n) {
+ if(!_.isUndefined(n) && n.length && dashboard.current.index.warm_fields) {
+ // Only get the mapping for indices we don't know it for
+ var indices = _.difference(n,_.keys(self.indices));
+ // Only get the mapping if there are new indices
+ if(indices.length > 0) {
+ self.map(indices).then(function(result) {
+ self.indices = _.union(self.indices,_.keys(result));
+ self.list = mapFields(result);
+ });
+ }
+ }
+ });
+
+ var mapFields = function (m) {
+ var fields = [];
+ _.each(m, function(types) {
+ _.each(types, function(type) {
+ fields = _.difference(_.union(fields,_.keys(type)),
+ ['_parent','_routing','_size','_ttl','_all','_uid','_version','_boost','_source']);
+ });
+ });
+ return fields;
+ };
+
+ this.map = function(indices) {
+ var request = ejs.client.get('/' + indices.join(',') + "/_mapping",
+ undefined, undefined, function(data, status) {
+ if(status === 0) {
+ alertSrv.set('Error',"Could not contact Elasticsearch at "+ejs.config.server+
+ ". Please ensure that Elasticsearch is reachable from your system." ,'error');
+ } else {
+ alertSrv.set('Error',"No index found at "+ejs.config.server+"/" +
+ indices.join(',')+"/_mapping. Please create at least one index." +
+ "If you're using a proxy ensure it is configured correctly.",'error');
+ }
+ });
+
+ // Flatten the mapping of each index into dot notated keys.
+ return request.then(function(p) {
+ var mapping = {};
+ return esVersion.gte('1.0.0.RC1').then(function(version) {
+ _.each(p, function(indexMap,index) {
+ mapping[index] = {};
+ _.each((version ? indexMap.mappings : indexMap), function (typeMap,type) {
+ mapping[index][type] = flatten(typeMap);
+ });
+ });
+ return mapping;
+ });
+ });
+ };
+
+ // This should understand both the 1.0 format and the 0.90 format for mappings. Ugly.
+ var flatten = function(obj,prefix) {
+ var propName = (prefix) ? prefix : '',
+ dot = (prefix) ? '.':'',
+ ret = {};
+ for(var attr in obj){
+ if(attr === 'dynamic_templates' || attr === '_default_') {
+ continue;
+ }
+ // For now only support multi field on the top level
+ // and if there is a default field set.
+ if(obj[attr]['type'] === 'multi_field') {
+ ret[attr] = obj[attr]['fields'][attr] || obj[attr];
+ var keys = _.without(_.keys(obj[attr]['fields']),attr);
+ for(var key in keys) {
+ ret[attr+'.'+keys[key]] = obj[attr]['fields'][keys[key]];
+ }
+ } else if (attr === 'properties' || attr ==='fields') {
+ _.extend(ret,flatten(obj[attr], propName));
+ } else if(typeof obj[attr] === 'object' &&
+ (!_.isUndefined(obj[attr].type) || !_.isUndefined(obj[attr].properties))){
+ _.extend(ret,flatten(obj[attr], propName + dot + attr));
+ } else {
+ if(propName !== '') {
+ ret[propName] = obj;
+ }
+ }
+ }
+ return ret;
+ };
+
+ });
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/filterSrv.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/filterSrv.js b/metron-ui/lib/public/app/services/filterSrv.js
new file mode 100755
index 0000000..7201946
--- /dev/null
+++ b/metron-ui/lib/public/app/services/filterSrv.js
@@ -0,0 +1,249 @@
+define([
+ 'angular',
+ 'lodash',
+ 'config',
+ 'kbn'
+], function (angular, _, config, kbn) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('filterSrv', function(dashboard, ejsResource, $rootScope, $timeout) {
+ // Create an object to hold our service state on the dashboard
+ dashboard.current.services.filter = dashboard.current.services.filter || {};
+
+ // Defaults for it
+ var _d = {
+ list : {},
+ ids : []
+ };
+
+ // For convenience
+ var ejs = ejsResource(config.elasticsearch);
+
+ // Save a reference to this
+ var self = this;
+
+ // Call this whenever we need to reload the important stuff
+ this.init = function() {
+ // Populate defaults
+ _.defaults(dashboard.current.services.filter,_d);
+
+ _.each(dashboard.current.services.filter.list,function(f) {
+ self.set(f,f.id,true);
+ });
+
+ };
+
+ this.ids = function() {
+ return dashboard.current.services.filter.ids;
+ };
+
+ this.list = function() {
+ return dashboard.current.services.filter.list;
+ };
+
+ // This is used both for adding filters and modifying them.
+ // If an id is passed, the filter at that id is updated
+ this.set = function(filter,id,noRefresh) {
+ var _r;
+
+ _.defaults(filter,{
+ mandate:'must',
+ active: true
+ });
+
+ if(!_.isUndefined(id)) {
+ if(!_.isUndefined(dashboard.current.services.filter.list[id])) {
+ _.extend(dashboard.current.services.filter.list[id],filter);
+ _r = id;
+ } else {
+ _r = false;
+ }
+ } else {
+ if(_.isUndefined(filter.type)) {
+ _r = false;
+ } else {
+ var _id = nextId();
+ var _filter = {
+ alias: '',
+ id: _id,
+ mandate: 'must'
+ };
+ _.defaults(filter,_filter);
+ dashboard.current.services.filter.list[_id] = filter;
+ dashboard.current.services.filter.ids.push(_id);
+ _r = _id;
+ }
+ }
+ if(!$rootScope.$$phase) {
+ $rootScope.$apply();
+ }
+ if(noRefresh !== true) {
+ $timeout(function(){
+ dashboard.refresh();
+ },0);
+ }
+ dashboard.current.services.filter.ids = dashboard.current.services.filter.ids =
+ _.intersection(_.map(dashboard.current.services.filter.list,
+ function(v,k){return parseInt(k,10);}),dashboard.current.services.filter.ids);
+ $rootScope.$broadcast('filter');
+
+ return _r;
+ };
+
+ this.remove = function(id,noRefresh) {
+ var _r;
+ if(!_.isUndefined(dashboard.current.services.filter.list[id])) {
+ delete dashboard.current.services.filter.list[id];
+ // This must happen on the full path also since _.without returns a copy
+ dashboard.current.services.filter.ids = dashboard.current.services.filter.ids = _.without(dashboard.current.services.filter.ids,id);
+ _r = true;
+ } else {
+ _r = false;
+ }
+ if(!$rootScope.$$phase) {
+ $rootScope.$apply();
+ }
+ if(noRefresh !== true) {
+ $timeout(function(){
+ dashboard.refresh();
+ },0);
+ }
+ $rootScope.$broadcast('filter');
+ return _r;
+ };
+
+ this.removeByType = function(type,noRefresh) {
+ var ids = self.idsByType(type);
+ _.each(ids,function(id) {
+ self.remove(id,true);
+ });
+ if(noRefresh !== true) {
+ $timeout(function(){
+ dashboard.refresh();
+ },0);
+ }
+ return ids;
+ };
+
+ this.getBoolFilter = function(ids) {
+ var bool = ejs.BoolFilter();
+ // there is no way to introspect the BoolFilter and find out if it has a filter. We must keep note.
+ var added_a_filter = false;
+
+ _.each(ids,function(id) {
+ if(dashboard.current.services.filter.list[id].active) {
+ added_a_filter = true;
+
+ switch(dashboard.current.services.filter.list[id].mandate)
+ {
+ case 'mustNot':
+ bool.mustNot(self.getEjsObj(id));
+ break;
+ case 'either':
+ bool.should(self.getEjsObj(id));
+ break;
+ default:
+ bool.must(self.getEjsObj(id));
+ }
+ }
+ });
+ // add a match filter so we'd get some data
+ if (!added_a_filter) {
+ bool.must(ejs.MatchAllFilter());
+ }
+ return bool;
+ };
+
+ this.getEjsObj = function(id) {
+ return self.toEjsObj(dashboard.current.services.filter.list[id]);
+ };
+
+ this.toEjsObj = function (filter) {
+ if(!filter.active) {
+ return false;
+ }
+ switch(filter.type)
+ {
+ case 'time':
+ var _f = ejs.RangeFilter(filter.field).from(kbn.parseDate(filter.from).valueOf());
+ if(!_.isUndefined(filter.to)) {
+ _f = _f.to(kbn.parseDate(filter.to).valueOf());
+ }
+ return _f;
+ case 'range':
+ return ejs.RangeFilter(filter.field)
+ .from(filter.from)
+ .to(filter.to);
+ case 'querystring':
+ return ejs.QueryFilter(ejs.QueryStringQuery(filter.query)).cache(true);
+ case 'field':
+ return ejs.QueryFilter(ejs.QueryStringQuery(filter.field+":("+filter.query+")")).cache(true);
+ case 'terms':
+ return ejs.TermsFilter(filter.field,filter.value);
+ case 'exists':
+ return ejs.ExistsFilter(filter.field);
+ case 'missing':
+ return ejs.MissingFilter(filter.field);
+ default:
+ return false;
+ }
+ };
+
+ this.getByType = function(type,inactive) {
+ return _.pick(dashboard.current.services.filter.list,self.idsByType(type,inactive));
+ };
+
+ this.idsByType = function(type,inactive) {
+ var _require = inactive ? {type:type} : {type:type,active:true};
+ return _.pluck(_.where(dashboard.current.services.filter.list,_require),'id');
+ };
+
+ // TOFIX: Error handling when there is more than one field
+ this.timeField = function() {
+ return _.pluck(self.getByType('time'),'field');
+ };
+
+ // Parse is used when you need to know about the raw filter
+ this.timeRange = function(parse) {
+ var _t = _.last(_.where(dashboard.current.services.filter.list,{type:'time',active:true}));
+ if(_.isUndefined(_t)) {
+ return false;
+ }
+ if(parse === false) {
+ return {
+ from: _t.from,
+ to: _t.to
+ };
+ } else {
+ var
+ _from = _t.from,
+ _to = _t.to || new Date();
+
+ return {
+ from : kbn.parseDate(_from),
+ to : kbn.parseDate(_to)
+ };
+ }
+ };
+
+ var nextId = function() {
+ var idCount = dashboard.current.services.filter.ids.length;
+ if(idCount > 0) {
+ // Make a sorted copy of the ids array
+ var ids = _.sortBy(_.clone(dashboard.current.services.filter.ids),function(num){
+ return num;
+ });
+ return kbn.smallestMissing(ids);
+ } else {
+ // No ids currently in list
+ return 0;
+ }
+ };
+
+ // Now init
+ self.init();
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/kbnIndex.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/kbnIndex.js b/metron-ui/lib/public/app/services/kbnIndex.js
new file mode 100755
index 0000000..8dbdb85
--- /dev/null
+++ b/metron-ui/lib/public/app/services/kbnIndex.js
@@ -0,0 +1,115 @@
+define([
+ 'angular',
+ 'lodash',
+ 'config',
+ 'moment'
+],
+function (angular, _, config, moment) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('kbnIndex', function($http, alertSrv, ejsResource) {
+ // returns a promise containing an array of all indices matching the index
+ // pattern that exist in a given range
+ this.indices = function(from,to,pattern,interval) {
+ var possible = [];
+ _.each(expand_range(from,to,interval),function(d){
+ possible.push(d.utc().format(pattern));
+ });
+
+ return resolve_indices(possible).then(function(p) {
+ // an extra intersection
+ var indices = _.uniq(_.flatten(_.map(possible,function(possibleIndex) {
+ return _.intersection(possibleIndex.split(','),p);
+ })));
+ indices.reverse();
+ return indices;
+ });
+ };
+
+ var ejs = ejsResource(config.elasticsearch);
+
+
+ // returns a promise containing an array of all indices in an elasticsearch
+ // cluster
+ function resolve_indices(indices) {
+ var something;
+ indices = _.uniq(_.map(indices, encodeURIComponent));
+
+ something = ejs.client.get("/" + indices.join(",") + "/_aliases?ignore_missing=true",
+ undefined, undefined, function (data, p) {
+ if (p === 404) {
+ return [];
+ }
+ else if(p === 0) {
+ alertSrv.set('Error',"Could not contact Elasticsearch at "+ejs.config.server+
+ ". Please ensure that Elasticsearch is reachable from your system." ,'error');
+ } else {
+ alertSrv.set('Error',"Could not reach "+ejs.config.server+"/_aliases. If you"+
+ " are using a proxy, ensure it is configured correctly",'error');
+ }
+ return [];
+ });
+
+ return something.then(function(p) {
+
+ var indices = [];
+ _.each(p, function(v,k) {
+ indices.push(k);
+ // Also add the aliases. Could be expensive on systems with a lot of them
+ _.each(v.aliases, function(v, k) {
+ indices.push(k);
+ });
+ });
+ return indices;
+ });
+ }
+
+ /*
+ // this is stupid, but there is otherwise no good way to ensure that when
+ // I extract the date from an object that I get the UTC date. Stupid js.
+ // I die a little inside every time I call this function.
+ // Update: I just read this again. I died a little more inside.
+ // Update2: More death.
+ function fake_utc(date) {
+ date = moment(date).clone().toDate();
+ return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
+ }
+ */
+
+ // Create an array of date objects by a given interval
+ function expand_range(start, end, interval) {
+ if(_.contains(['hour','day','week','month','year'],interval)) {
+ var range;
+ start = moment(start).clone();
+ range = [];
+ while (start.isBefore(end)) {
+ range.push(start.clone());
+ switch (interval) {
+ case 'hour':
+ start.add('hours',1);
+ break;
+ case 'day':
+ start.add('days',1);
+ break;
+ case 'week':
+ start.add('weeks',1);
+ break;
+ case 'month':
+ start.add('months',1);
+ break;
+ case 'year':
+ start.add('years',1);
+ break;
+ }
+ }
+ range.push(moment(end).clone());
+ return range;
+ } else {
+ return false;
+ }
+ }
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/panelMove.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/panelMove.js b/metron-ui/lib/public/app/services/panelMove.js
new file mode 100755
index 0000000..e2d9b04
--- /dev/null
+++ b/metron-ui/lib/public/app/services/panelMove.js
@@ -0,0 +1,68 @@
+define([
+ 'angular',
+ 'lodash'
+],
+function (angular, _) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('panelMove', function(dashboard, $rootScope) {
+
+ /* each of these can take event,ui,data parameters */
+
+ this.onStart = function() {
+ dashboard.panelDragging = true;
+ $rootScope.$apply();
+ };
+
+ this.onOver = function() {
+ $rootScope.$apply();
+ };
+
+ this.onOut = function() {
+ $rootScope.$apply();
+ };
+
+ /*
+ Use our own drop logic. the $parent.$parent this is ugly.
+ */
+ this.onDrop = function(event,ui,data) {
+ var
+ dragRow = data.draggableScope.$parent.$parent.row.panels,
+ dropRow = data.droppableScope.$parent.$parent.row.panels,
+ dragIndex = data.dragSettings.index,
+ dropIndex = data.dropSettings.index;
+
+
+ // Remove panel from source row
+ dragRow.splice(dragIndex,1);
+
+ // Add to destination row
+ if(!_.isUndefined(dropRow)) {
+ dropRow.splice(dropIndex,0,data.dragItem);
+ }
+
+ dashboard.panelDragging = false;
+ // Cleanup nulls/undefined left behind
+ cleanup();
+ $rootScope.$apply();
+ $rootScope.$broadcast('render');
+ };
+
+ this.onStop = function() {
+ dashboard.panelDragging = false;
+ cleanup();
+ $rootScope.$apply();
+ };
+
+ var cleanup = function () {
+ _.each(dashboard.current.rows, function(row) {
+ row.panels = _.without(row.panels,{});
+ row.panels = _.compact(row.panels);
+ });
+ };
+
+ });
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/querySrv.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/querySrv.js b/metron-ui/lib/public/app/services/querySrv.js
new file mode 100755
index 0000000..d883de6
--- /dev/null
+++ b/metron-ui/lib/public/app/services/querySrv.js
@@ -0,0 +1,284 @@
+define([
+ 'angular',
+ 'lodash',
+ 'config',
+ 'kbn'
+],
+function (angular, _, config, kbn) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('querySrv', function(dashboard, ejsResource, filterSrv, esVersion, $q) {
+
+ // Save a reference to this
+ var self = this;
+
+ // Create an object to hold our service state on the dashboard
+ dashboard.current.services.query = dashboard.current.services.query || {};
+ _.defaults(dashboard.current.services.query,{
+ list : {},
+ ids : [],
+ });
+
+ this.colors = [
+ "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
+ "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
+ "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
+ "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
+ "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
+ "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
+ "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
+ ];
+
+ // For convenience
+ var ejs = ejsResource(config.elasticsearch);
+
+ // Holds all actual queries, including all resolved abstract queries
+ var resolvedQueries = [];
+
+ // Defaults for generic query object
+ var _query = {
+ alias: '',
+ pin: false,
+ type: 'lucene',
+ enable: true
+ };
+
+ // 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.12",
+ 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();
+ // Like the regex and lucene queries, this returns a promise
+ 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+':"'+kbn.addslashes('' + t.term)+'"'+suffix,
+ alias : t.term + (q.alias ? " ("+q.alias+")" : ""),
+ type : 'lucene',
+ color : _colors[i],
+ parent : q.id
+ });
+ });
+ });
+ }
+ }
+ };
+
+ self.types = [];
+ _.each(self.queryTypes,function(type,name){
+ esVersion.is(type.require).then(function(is) {
+ if(is) {
+ self.types.push(name);
+ }
+ });
+ });
+
+
+ this.list = function () {
+ return dashboard.current.services.query.list;
+ };
+
+ this.ids = function () {
+ return dashboard.current.services.query.ids;
+ };
+
+ this.init = function() {
+
+ dashboard.current.services.query.ids =
+ _.intersection(_.map(dashboard.current.services.query.list,
+ function(v,k){return parseInt(k,10);}),self.ids());
+
+ // Check each query object, populate its defaults
+ _.each(dashboard.current.services.query.list,function(query) {
+ query = self.defaults(query);
+ });
+
+ if (dashboard.current.services.query.ids.length === 0) {
+ self.set({});
+ }
+ };
+
+ // This is used both for adding queries and modifying them. If an id is passed,
+ // the query at that id is updated
+ this.set = function(query,id) {
+ if(!_.isUndefined(id)) {
+ if(!_.isUndefined(dashboard.current.services.query.list[id])) {
+ _.extend(dashboard.current.services.query.list[id],query);
+ return id;
+ } else {
+ return false;
+ }
+ } else {
+ // 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);
+ dashboard.current.services.query.list[query.id] = query;
+ dashboard.current.services.query.ids.push(query.id);
+ return query.id;
+ }
+ };
+
+ this.defaults = function(query) {
+ _.defaults(query,_query);
+ _.defaults(query,_dTypes[query.type]);
+ query.color = query.color || colorAt(query.id);
+ return query;
+ };
+
+ this.remove = function(id) {
+ if(!_.isUndefined(dashboard.current.services.query.list[id])) {
+ delete dashboard.current.services.query.list[id];
+ // This must happen on the full path also since _.without returns a copy
+ dashboard.current.services.query.ids = _.without(dashboard.current.services.query.ids,id);
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+
+ // These are the only query types that can be returned by a compound query.
+ this.toEjsObj = function (q) {
+ switch(q.type)
+ {
+ case 'lucene':
+ return ejs.QueryStringQuery(q.query || '*');
+ case 'regex':
+ return ejs.RegexpQuery('_all',q.query);
+ default:
+ return false;
+ }
+ };
+
+ //
+ 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)
+ {
+ case 'all':
+ return _.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id');
+ case 'pinned':
+ return _.pluck(_.where(dashboard.current.services.query.list,{pin:true,enable:true}),'id');
+ case 'unpinned':
+ return _.pluck(_.where(dashboard.current.services.query.list,{pin:false,enable:true}),'id');
+ case 'selected':
+ return _.intersection(_.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id'),config.ids);
+ default:
+ return _.pluck(_.where(dashboard.current.services.query.list,{enable:true}),'id');
+ }
+ };
+
+ // 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(dashboard.current.services.query.ids,function(q) {
+ return self.queryTypes[dashboard.current.services.query.list[q].type].resolve(
+ _.clone(dashboard.current.services.query.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() {
+ var idCount = dashboard.current.services.query.ids.length;
+ if(idCount > 0) {
+ // Make a sorted copy of the ids array
+ var ids = _.sortBy(_.clone(dashboard.current.services.query.ids),function(num){
+ return num;
+ });
+ return kbn.smallestMissing(ids);
+ } else {
+ // No ids currently in list
+ return 0;
+ }
+ };
+
+ var colorAt = function(id) {
+ return self.colors[id % self.colors.length];
+ };
+
+ self.init();
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/app/services/timer.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/app/services/timer.js b/metron-ui/lib/public/app/services/timer.js
new file mode 100755
index 0000000..128b3f4
--- /dev/null
+++ b/metron-ui/lib/public/app/services/timer.js
@@ -0,0 +1,34 @@
+define([
+ 'angular',
+ 'lodash'
+],
+function (angular, _) {
+ 'use strict';
+
+ var module = angular.module('kibana.services');
+
+ module.service('timer', function($timeout) {
+ // This service really just tracks a list of $timeout promises to give us a
+ // method for cancelling them all when we need to
+
+ var timers = [];
+
+ this.register = function(promise) {
+ timers.push(promise);
+ return promise;
+ };
+
+ this.cancel = function(promise) {
+ timers = _.without(timers,promise);
+ $timeout.cancel(promise);
+ };
+
+ this.cancel_all = function() {
+ _.each(timers, function(t){
+ $timeout.cancel(t);
+ });
+ timers = [];
+ };
+ });
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/0648a447/metron-ui/lib/public/config.js
----------------------------------------------------------------------
diff --git a/metron-ui/lib/public/config.js b/metron-ui/lib/public/config.js
new file mode 100755
index 0000000..bc6ac42
--- /dev/null
+++ b/metron-ui/lib/public/config.js
@@ -0,0 +1,82 @@
+/** @scratch /configuration/config.js/1
+ *
+ * == Configuration
+ * config.js is where you will find the core Kibana configuration. This file contains parameter that
+ * must be set before kibana is run for the first time.
+ */
+define(['settings'],
+function (Settings) {
+ "use strict";
+
+ /** @scratch /configuration/config.js/2
+ *
+ * === Parameters
+ */
+ return new Settings({
+
+ /** @scratch /configuration/config.js/5
+ *
+ * ==== elasticsearch
+ *
+ * The URL to your elasticsearch server. You almost certainly don't
+ * want +http://localhost:9200+ here. Even if Kibana and Elasticsearch are on
+ * the same host. By default this will attempt to reach ES at the same host you have
+ * kibana installed on. You probably want to set it to the FQDN of your
+ * elasticsearch host
+ *
+ * Note: this can also be an object if you want to pass options to the http client. For example:
+ *
+ * +elasticsearch: {server: "http://localhost:9200", withCredentials: true}+
+ *
+ */
+ elasticsearch: location.protocol + '//' + location.host + '/__es',
+
+ /** @scratch /configuration/config.js/5
+ *
+ * ==== default_route
+ *
+ * This is the default landing page when you don't specify a dashboard to load. You can specify
+ * files, scripts or saved dashboards here. For example, if you had saved a dashboard called
+ * `WebLogs' to elasticsearch you might use:
+ *
+ * default_route: '/dashboard/elasticsearch/WebLogs',
+ */
+ default_route : '/dashboard/file/default.json',
+ // default_route: '/dashboard/elasticsearch/Your Basic Dashboard',
+
+ /** @scratch /configuration/config.js/5
+ *
+ * ==== kibana-int
+ *
+ * The default ES index to use for storing Kibana specific object
+ * such as stored dashboards
+ */
+ kibana_index: "kibana-int",
+
+ /** @scratch /configuration/config.js/5
+ *
+ * ==== panel_name
+ *
+ * An array of panel modules available. Panels will only be loaded when they are defined in the
+ * dashboard, but this list is used in the "add panel" interface.
+ */
+ panel_names: [
+ 'histogram',
+ 'map',
+ 'goal',
+ 'table',
+ 'filtering',
+ 'timepicker',
+ 'text',
+ 'pcap',
+ 'hits',
+ 'column',
+ 'trends',
+ 'bettermap',
+ 'query',
+ 'terms',
+ 'stats',
+ 'sparklines'
+ ]
+ });
+});