Quellcode durchsuchen

Added pulldowns for query and filter. Added filter notifications, moved timepicker to navbar, reimplemented auto refresh

Rashid Khan vor 12 Jahren
Ursprung
Commit
e54c868e7f

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

@@ -295,7 +295,6 @@ function($, _, moment) {
       } else if (c === '-') {
         type = 2;
       } else {
-        console.log("no type");
         return false;
       }
 
@@ -311,7 +310,6 @@ function($, _, moment) {
       if (type === 0) {
         // rounding is only allowed on whole numbers
         if (num !== 1) {
-          console.log("nbad rounding");
           return false;
         }
       }
@@ -373,7 +371,6 @@ function($, _, moment) {
         }
         break;
       default:
-        console.log("unknown unit");
         return false;
       }
     }

+ 2 - 1
src/app/controllers/all.js

@@ -1,5 +1,6 @@
 define([
   './dash',
   './dashLoader',
-  './row'
+  './row',
+  './pulldown'
 ], function () {});

+ 8 - 1
src/app/controllers/dashLoader.js

@@ -11,13 +11,20 @@ function (angular, _) {
     $scope.loader = dashboard.current.loader;
 
     $scope.init = function() {
+      $scope.advancedLoad = false;
+      $scope.advancedSave = false;
+
       $scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
       $scope.gist = $scope.gist || {};
       $scope.elasticsearch = $scope.elasticsearch || {};
     };
 
     $scope.showDropdown = function(type) {
-      var _l = $scope.loader;
+      if(_.isUndefined(dashboard.current.loader)) {
+        return true;
+      }
+
+      var _l = dashboard.current.loader;
       if(type === 'load') {
         return (_l.load_elasticsearch || _l.load_gist || _l.load_local);
       }

+ 44 - 0
src/app/controllers/pulldown.js

@@ -0,0 +1,44 @@
+define([
+  'angular',
+  'app',
+  'underscore'
+],
+function (angular, app, _) {
+  'use strict';
+
+  var module = angular.module('kibana.controllers');
+
+  module.controller('PulldownCtrl', function($scope, $rootScope, $timeout,ejsResource, querySrv) {
+      var _d = {
+        collapse: false,
+        notice: false,
+      };
+
+      _.defaults($scope.pulldown,_d);
+
+      $scope.init = function() {
+        $scope.querySrv = querySrv;
+
+        // Provide a combined skeleton for panels that must interact with panel and row.
+        // This might create name spacing issues.
+        $scope.panel = $scope.pulldown;
+        $scope.row = $scope.pulldown;
+      };
+
+      $scope.toggle_pulldown = function(pulldown) {
+        pulldown.collapse = pulldown.collapse ? false : true;
+        if (!pulldown.collapse) {
+          $timeout(function() {
+            $scope.$broadcast('render');
+          });
+        } else {
+          $scope.row.notice = false;
+        }
+      };
+
+      $scope.init();
+
+    }
+  );
+
+});

+ 78 - 19
src/app/dashboards/default.json

@@ -13,7 +13,9 @@
           "query": "*",
           "alias": "",
           "color": "#7EB26D",
-          "id": 0
+          "id": 0,
+          "pin": false,
+          "type": "lucene"
         }
       },
       "ids": [
@@ -22,24 +24,12 @@
     },
     "filter": {
       "idQueue": [
+        0,
         1,
         2
       ],
-      "list": {
-        "0": {
-          "from": "2013-07-27T22:08:06.800Z",
-          "to": "2013-07-27T23:08:06.801Z",
-          "field": "@timestamp",
-          "type": "time",
-          "mandate": "must",
-          "active": true,
-          "alias": "",
-          "id": 0
-        }
-      },
-      "ids": [
-        0
-      ]
+      "list": {},
+      "ids": []
     }
   },
   "rows": [
@@ -77,7 +67,8 @@
           "style": {},
           "status": "Stable"
         }
-      ]
+      ],
+      "notice": false
     }
   ],
   "editable": false,
@@ -85,5 +76,73 @@
     "interval": "none",
     "pattern": "[logstash-]YYYY.MM.DD",
     "default": "_all"
-  }
-}
+  },
+  "style": "dark",
+  "failover": false,
+  "panel_hints": true,
+  "pulldowns": [
+    {
+      "type": "query",
+      "collapse": true,
+      "notice": false,
+      "query": "*",
+      "pinned": true,
+      "history": [],
+      "remember": 10
+    },
+    {
+      "type": "filtering",
+      "collapse": true,
+      "notice": false
+    }
+  ],
+  "nav": [
+    {
+      "type": "timepicker2",
+      "collapse": false,
+      "notice": false,
+      "status": "Stable",
+      "time_options": [
+        "5m",
+        "15m",
+        "1h",
+        "6h",
+        "12h",
+        "24h",
+        "2d",
+        "7d",
+        "30d"
+      ],
+      "refresh_intervals": [
+        "5s",
+        "10s",
+        "30s",
+        "1m",
+        "5m",
+        "15m",
+        "30m",
+        "1h",
+        "2h",
+        "1d"
+      ],
+      "timefield": "@timestamp",
+      "now": true,
+      "filter_id": 0
+    }
+  ],
+  "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": true,
+    "load_elasticsearch": true,
+    "load_elasticsearch_size": 20,
+    "load_local": true,
+    "hide": false
+  },
+  "refresh": false
+}

+ 6 - 49
src/app/dashboards/logstash.js

@@ -1,4 +1,4 @@
-/* global _, kbn */
+/* global _ */
 
 /*
  * Complex scripted Logstash dashboard
@@ -29,7 +29,7 @@ var dashboard, queries, _d_timespan;
 var ARGS;
 
 // Set a default timespan if one isn't specified
-_d_timespan = '1h';
+_d_timespan = '1d';
 
 // Intialize a skeleton with nothing but a rows array and service object
 dashboard = {
@@ -85,12 +85,11 @@ dashboard.services.query = {
 };
 
 // Lets also add a default time filter, the value of which can be specified by the user
-// This isn't strictly needed, but it gets rid of the info alert about the missing time filter
 dashboard.services.filter = {
   list: {
     0: {
-      from: kbn.time_ago(ARGS.from||_d_timespan),
-      to: new Date(),
+      from: "now-"+(ARGS.from||_d_timespan),
+      to: "now",
       field: ARGS.timefield||"@timestamp",
       type: "time",
       active: true,
@@ -102,19 +101,6 @@ dashboard.services.filter = {
 
 // Ok, lets make some rows. The Filters row is collapsed by default
 dashboard.rows = [
-  {
-    title: "Time span",
-    height: "30px"
-  },
-  {
-    title: "Query",
-    height: "30px"
-  },
-  {
-    title: "Filters",
-    height: "100px",
-    collapse: true
-  },
   {
     title: "Chart",
     height: "300px"
@@ -125,37 +111,8 @@ dashboard.rows = [
   }
 ];
 
-// Setup some panels. A query panel and a filter panel on the same row
-dashboard.rows[0].panels = [
-  {
-    title: "Set time filter",
-    type: 'timepicker',
-    span: 6,
-    timespan: ARGS.from||_d_timespan
-  }
-];
-
-// Add a filtering panel to the 3rd row
-dashboard.rows[1].panels = [
-  {
-    title: 'search',
-    type: 'query',
-    span: 12
-  }
-];
-
-
-// Add a filtering panel to the 3rd row
-dashboard.rows[2].panels = [
-  {
-    title: 'filters (applied globally)',
-    type: 'filtering',
-    span: 12
-  }
-];
-
 // And a histogram that allows the user to specify the interval and time field
-dashboard.rows[3].panels = [
+dashboard.rows[0].panels = [
   {
     title: 'events over time',
     type: 'histogram',
@@ -166,7 +123,7 @@ dashboard.rows[3].panels = [
 ];
 
 // And a table row where you can specify field and sort order
-dashboard.rows[4].panels = [
+dashboard.rows[1].panels = [
   {
     title: 'all events',
     type: 'table',

+ 103 - 94
src/app/dashboards/logstash.json

@@ -10,7 +10,9 @@
           "query": "{{ARGS.query || '*'}}",
           "alias": "",
           "color": "#7EB26D",
-          "id": 0
+          "id": 0,
+          "pin": false,
+          "type": "lucene"
         }
       },
       "ids": [
@@ -23,10 +25,10 @@
       ],
       "list": {
         "0": {
-          "from": "2013-07-30T18:58:13.977Z",
-          "to": "2013-07-30T19:58:13.977Z",
-          "field": "@timestamp",
           "type": "time",
+          "field": "@timestamp",
+          "from": "now-{{ARGS.from || '24h'}}",
+          "to": "now",
           "mandate": "must",
           "active": true,
           "alias": "",
@@ -39,90 +41,6 @@
     }
   },
   "rows": [
-    {
-      "title": "Options",
-      "height": "50px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "title": "Set time span",
-          "error": "",
-          "span": 6,
-          "editable": true,
-          "group": [
-            "default"
-          ],
-          "type": "timepicker",
-          "mode": "relative",
-          "time_options": [
-            "5m",
-            "15m",
-            "1h",
-            "6h",
-            "12h",
-            "24h",
-            "2d",
-            "7d",
-            "30d"
-          ],
-          "timespan": "{{ARGS.from || '1h'}}",
-          "timefield": "@timestamp",
-          "timeformat": "",
-          "refresh": {
-            "enable": false,
-            "interval": 30,
-            "min": 3
-          },
-          "filter_id": 0,
-          "status": "Stable"
-        }
-      ]
-    },
-    {
-      "title": "Query",
-      "height": "50px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "title": "Search",
-          "error": false,
-          "span": 12,
-          "editable": true,
-          "group": [
-            "default"
-          ],
-          "type": "query",
-          "label": "Search",
-          "history": [],
-          "remember": 10,
-          "pinned": true,
-          "query": "*"
-        }
-      ]
-    },
-    {
-      "title": "Filters",
-      "height": "50px",
-      "editable": true,
-      "collapse": true,
-      "collapsable": true,
-      "panels": [
-        {
-          "title": "dashboard filters",
-          "error": false,
-          "span": 12,
-          "editable": true,
-          "group": [
-            "default"
-          ],
-          "type": "filtering"
-        }
-      ]
-    },
     {
       "title": "Graph",
       "height": "350px",
@@ -142,7 +60,7 @@
           "value_field": null,
           "auto_int": true,
           "resolution": 100,
-          "interval": "30s",
+          "interval": "10m",
           "fill": 3,
           "linewidth": 3,
           "timezone": "browser",
@@ -163,9 +81,30 @@
               0
             ]
           },
-          "title": "Events over time"
+          "title": "Events over time",
+          "intervals": [
+            "auto",
+            "1s",
+            "1m",
+            "5m",
+            "10m",
+            "30m",
+            "1h",
+            "3h",
+            "12h",
+            "1d",
+            "1w",
+            "1M",
+            "1y"
+          ],
+          "options": true,
+          "tooltip": {
+            "value_type": "cumulative",
+            "query_as_alias": false
+          }
         }
-      ]
+      ],
+      "notice": false
     },
     {
       "title": "Events",
@@ -207,9 +146,12 @@
             ]
           },
           "field_list": true,
-          "status": "Stable"
+          "status": "Stable",
+          "trimFactor": 300,
+          "normTimes": true
         }
-      ]
+      ],
+      "notice": false
     }
   ],
   "editable": true,
@@ -218,5 +160,72 @@
     "interval": "day",
     "pattern": "[logstash-]YYYY.MM.DD",
     "default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED"
-  }
+  },
+  "style": "dark",
+  "panel_hints": true,
+  "pulldowns": [
+    {
+      "type": "query",
+      "collapse": false,
+      "notice": false,
+      "query": "*",
+      "pinned": true,
+      "history": [],
+      "remember": 10
+    },
+    {
+      "type": "filtering",
+      "collapse": true,
+      "notice": false
+    }
+  ],
+  "nav": [
+    {
+      "type": "timepicker2",
+      "collapse": false,
+      "notice": false,
+      "status": "Stable",
+      "time_options": [
+        "5m",
+        "15m",
+        "1h",
+        "6h",
+        "12h",
+        "24h",
+        "2d",
+        "7d",
+        "30d"
+      ],
+      "refresh_intervals": [
+        "5s",
+        "10s",
+        "30s",
+        "1m",
+        "5m",
+        "15m",
+        "30m",
+        "1h",
+        "2h",
+        "1d"
+      ],
+      "timefield": "@timestamp",
+      "now": true,
+      "filter_id": 0
+    }
+  ],
+  "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": true,
+    "load_elasticsearch": true,
+    "load_elasticsearch_size": 20,
+    "load_local": true,
+    "hide": false
+  },
+  "refresh": false
 }

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

@@ -10,6 +10,7 @@ function (angular) {
       return {
         restrict: 'E',
         link: function($scope, elem, attr) {
+
           // once we have the template, scan it for controllers and
           // load the module.js if we have any
 

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

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

+ 26 - 24
src/app/panels/histogram/module.html

@@ -50,30 +50,32 @@
     <span ng-show="panel.legend" class="small"><span ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong>{{panel.interval}}</strong> | (<strong>{{hits}}</strong> hits)</span>
   </div>
   <form class="form-inline bordered histogram-options" ng-show="options">
-    <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.points" ng-checked="panel.points" ng-change="render()">
-        Points
-      </label>
-    </div>
-    <div class="checkbox">
-      <label class="small">
-        <input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
-        Stack
-      </label>
-    </div>
+    <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.points" ng-checked="panel.points" ng-change="render()">
+          Points
+        </label>
+      </div>
+      <div class="checkbox">
+        <label class="small">
+          <input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
+          Stack
+        </label>
+      </div>
+    </span>
     <span ng-show="panel.stack">
       <div class="checkbox">
         <label style="white-space:nowrap" class="small">

+ 2 - 2
src/app/panels/histogram/module.js

@@ -308,8 +308,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       }
       filterSrv.set({
         type:'time',
-        from:moment.utc(_from),
-        to:moment.utc(_to),
+        from:moment.utc(_from).toDate(),
+        to:moment.utc(_to).toDate(),
         field:$scope.panel.time_field
       });
     };

+ 2 - 2
src/app/panels/table/module.html

@@ -49,7 +49,7 @@
           </th>
 
         </thead>
-        <tbody ng-repeat="event in data | slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
+        <tbody ng-repeat="event in data| slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
           <tr ng-click="toggle_details(event)" class="pointer">
             <td ng-show="panel.fields.length<1">{{event._source|stringify|tableTruncate:panel.trimFactor:1}}</td>
             <td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields" ng-bind-html-unsafe="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></td>
@@ -69,7 +69,7 @@
                   <th>Action</th>
                   <th>Value</th>
                 </thead>
-                <tr ng-repeat="(key,value) in event.kibana._source" ng-class-odd="'odd'">
+                <tr ng-repeat="(key,value) in event.kibana._source track by $index" ng-class-odd="'odd'">
                   <td>{{key}}</td>
                   <td style="white-space:nowrap">
                     <i class='icon-search pointer' ng-click="build_search(key,value)" bs-tooltip="'Add filter to match this value'"></i>

+ 2 - 2
src/app/panels/timepicker2/custom.html

@@ -39,7 +39,7 @@
         <div class="timepicker-from-column">
           <label class="small">From</label>
           <div class="fake-input timepicker-input">
-            <input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
+            <input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
             <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
             <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
             <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
@@ -53,7 +53,7 @@
 
           <div class="fake-input timepicker-input">
             <div ng-hide="panel.now">
-              <input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
+              <input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
               <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
               <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
               <input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.

+ 6 - 2
src/app/panels/timepicker2/editor.html

@@ -1,9 +1,13 @@
   <div class="row-fluid">
-    <div class="span6">
+    <div class="span4">
       <label class="small">Relative time options <small>comma seperated</small></label>
       <input type="text" array-join class="input-large" ng-model="panel.time_options">
     </div>
-    <div class="span3">
+    <div class="span4">
+      <label class="small">Auto-refresh options <small>comma seperated</small></label>
+      <input type="text" array-join class="input-large" ng-model="panel.refresh_intervals">
+    </div>
+    <div class="span2">
       <label class="small">Time Field</label>
       <input type="text" class="input-small" ng-model="panel.timefield">
     </div>

+ 17 - 3
src/app/panels/timepicker2/module.html

@@ -14,9 +14,10 @@
   <form name="input" style="margin:3px 0 0 0">
     <ul class="nav nav-pills timepicker-dropdown">
       <li class="dropdown" bs-tooltip="(time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' to ' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss')" data-placement="bottom" ng-click="dismiss();">
+
         <a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="#">
 
-          <span class="small" ng-show="filterSrv.idsByType('time').length">
+          <span ng-show="filterSrv.idsByType('time').length">
             <span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
             <span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
             to
@@ -24,14 +25,27 @@
             <span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
           </span>
           <span ng-hide="filterSrv.idsByType('time').length">Set a time filter</span>
-          <i class="small icon-caret-down"></i>
+          <span ng-show="dashboard.current.refresh" class="text-warning">refreshed every {{dashboard.current.refresh}} </span>
+          <i class="icon-caret-down"></i>
         </a>
+
         <ul class="dropdown-menu">
-          <li ng-repeat='timespan in panel.time_options'>
+          <!-- Relative time options -->
+          <li ng-repeat='timespan in panel.time_options track by $index'>
             <a ng-click="setRelativeFilter(timespan)">Last {{timespan}}</a>
           </li>
+
+          <!-- Auto refresh submenu -->
+          <li class="dropdown-submenu">
+            <a href="#">Auto-Refresh</a>
+            <ul class="dropdown-menu">
+              <li><a ng-click="dashboard.set_interval(false)">Off</a></li>
+              <li ng-repeat="interval in panel.refresh_intervals track by $index"><a ng-click="dashboard.set_interval(interval)">Every {{interval}}</a></li>
+            </ul>
+          </li>
           <li><a ng-click="customTime()">Custom</a></li>
         </ul>
+
       </li>
     </ul>
 

+ 3 - 11
src/app/panels/timepicker2/module.js

@@ -36,16 +36,10 @@ function (angular, app, _, moment, kbn) {
     // Set and populate defaults
     var _d = {
       status        : "Stable",
-      mode          : "relative",
       time_options  : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
-      timespan      : '15m',
-      timefield     : '@timestamp',
-      timeformat    : "",
-      refresh       : {
-        enable  : false,
-        interval: 30,
-        min     : 3
-      }
+      refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
+
+      timefield     : '@timestamp'
     };
     _.defaults($scope.panel,_d);
 
@@ -212,9 +206,7 @@ function (angular, app, _, moment, kbn) {
 
     // Do not use the results of this function unless you plan to use setHour/Minutes/etc on the result
     var datepickerToLocal = function(date) {
-      console.log(date);
       date = moment(date).clone().toDate();
-      console.log(moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate());
       return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate();
     };
 

+ 76 - 48
src/app/partials/dashLoader.html

@@ -1,38 +1,23 @@
-<li ng-controller="RowCtrl"><kibana-simple-panel type="'timepicker2'" ng-cloak></kibana-simple-panel></li>
+<style>
+  .noarrow>a:after {
+    display: none !important;
+  }
+</style>
 
-<li><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
+<li ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl"><kibana-simple-panel type="pulldown.type" panel="pulldown" ng-cloak></kibana-simple-panel></li>
 
+<li><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
 <li class="dropdown" bs-tooltip="'Load'" data-placement="bottom" ng-show="showDropdown('load')" >
   <a href="#" class="dropdown-toggle" data-toggle="dropdown" ng-click="elasticsearch_dblist('title:'+elasticsearch.query+'*')">
     <i class='icon-folder-open'></i>
   </a>
+
+
   <ul class="dropdown-menu" style="padding:10px">
-    <li ng-show='loader.load_local'>
-      <h5>Local File <tip>Load dashboard JSON layout from file</tip></h5>
-      <form>
-        <input type="file" id="dashupload" dash-upload /><br>
-      </form>
-    </li>
-    <li ng-show='loader.load_gist'>
-      <h5>Gist <tip>Enter a gist number or url</tip></h5>
-      <form>
-        <input type="text" ng-model="gist.url"/ placeholder="Gist number or URL"><br>
-        <button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
-        <h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
-        <h6 ng-hide="gist.files.length">No gist dashboards found</h6>
-        <table class="table table-condensed table-striped">
-          <tr ng-repeat="file in gist.files">
-            <td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
-          </tr>
-        </table>
-      </form>
-    </li>
-    <li ng-show='loader.load_elasticsearch'>
-      <h5>Elasticsearch</h5>
-      <form>
+    <li ng-show='dashboard.current.loader.load_elasticsearch'>
+      <form class="nomargin">
         <input type="text" ng-model="elasticsearch.query" ng-change="elasticsearch_dblist('title:'+elasticsearch.query+'*')" placeholder="Type to filter"/>
       </form>
-      <h6 ng-show="elasticsearch.dashboards.length">Elasticsearch stored dashboards</h6>
       <h6 ng-hide="elasticsearch.dashboards.length">No dashboards matching your query found</h6>
       <table class="table table-condensed table-striped">
         <tr ng-repeat="row in elasticsearch.dashboards | orderBy:['_id']">
@@ -42,38 +27,81 @@
         </tr>
       </table>
     </li>
+
+    <li class="dropdown-submenu noarrow">
+      <a tabindex="-1" href="#" class="small" style="padding:0"><i class="icon-caret-left"></i> Advanced</a>
+      <ul class="dropdown-menu" style="padding:10px">
+        <li ng-show='dashboard.current.loader.load_local'>
+          <h5>Local File <tip>Load dashboard JSON layout from file</tip></h5>
+          <form>
+            <input type="file" id="dashupload" dash-upload /><br>
+          </form>
+        </li>
+        <li ng-show='dashboard.current.loader.load_gist'>
+          <h5>Gist <tip>Enter a gist number or url</tip></h5>
+          <form>
+            <input type="text" ng-model="gist.url"/ placeholder="Gist number or URL"><br>
+            <button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
+            <h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
+            <h6 ng-hide="gist.files.length || !gist.url.length">No gist dashboards found</h6>
+            <table class="table table-condensed table-striped">
+              <tr ng-repeat="file in gist.files">
+                <td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
+              </tr>
+            </table>
+          </form>
+        </li>
+      </ul>
+    </li>
+
   </ul>
+
+
 </li>
 <li class="dropdown" bs-tooltip="'Save'" data-placement="bottom" ng-show="showDropdown('save')">
   <a href="#" class="dropdown-toggle" data-toggle="dropdown">
     <i class='icon-save'></i>
   </a>
+
+
   <ul class="dropdown-menu" style="padding:10px">
-    <li ng-show="loader.save_default || loader.save_local">
-      <h5>Locally</h5>
-      <ul class="unstyled">
-        <li><a class="link" ng-show="loader.save_local" ng-click="dashboard.to_file()"><i class="icon-download"></i> Export to File</a> <tip>Export layout, not data, to file</tip></li>
-        <li><a class="link" ng-show="loader.save_default" ng-click="set_default()"><i class="icon-bookmark"></i> Set as Browser Default</a> <tip>Store dashboard preference to browser's localStorage</tip></li>
-        <li><a class="link" ng-show="loader.save_default" ng-click="purge_default()"><i class="icon-ban-circle"></i> Clear Browser Default</a></li>
-      </ul>
-    </li>
-    <li ng-show="loader.save_gist">
-      <h5>Gist</h5>
-      <form class="input-append">
-        <input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
-        <button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
-      </form><br>
-      <small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
-    </li>
-    <li ng-show="loader.save_elasticsearch">
-      <h5>Elasticsearch</h5>
-      <form class="input-append">
-        <input class='input-medium' placeholder="{{dashboard.current.title}}" type="text" ng-model="elasticsearch.title"/>
+
+    <li ng-show="dashboard.current.loader.save_elasticsearch">
+      <form class="input-append nomargin">
+        <input class='input-medium' ng-model="dashboard.current.title" type="text" ng-model="elasticsearch.title"/>
         <button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
       </form>
     </li>
+
+    <li class="dropdown-submenu noarrow">
+      <a tabindex="-1" href="#" class="small" style="padding:0"><i class="icon-caret-left"></i> Advanced</a>
+      <ul class="dropdown-menu">
+
+        <li ng-show="dashboard.current.loader.save_default">
+          <a class="link" ng-click="set_default()">Set as my home</a>
+        </li>
+        <li ng-show="dashboard.current.loader.save_default">
+          <a class="link" ng-click="purge_default()">Clear my home</a>
+        </li>
+        <li ng-show="dashboard.current.loader.save_local">
+          <a class="link" ng-click="dashboard.to_file()">Export schema</a>
+        </li>
+
+        <li ng-show="dashboard.current.loader.save_gist" style="margin:10px">
+          <h6>Gist</h6>
+          <form class="input-append">
+            <input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
+            <button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
+          </form><br>
+          <small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
+        </li>
+      </ul>
+    </li>
   </ul>
+
+
 </li>
-<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',loader.save_temp_ttl)" bs-modal="'app/partials/dashLoaderShare.html'"><i class='icon-share'></i></a></li>
+<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" bs-modal="'app/partials/dashLoaderShare.html'"><i class='icon-share'></i></a></li>
+
+<li ng-show="dashboard.current.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a href='#' bs-modal="'app/partials/dasheditor.html'"><i class='icon-cog pointer'></i></a></li>
 
-<li ng-show="dashboard.current.editable "bs-tooltip="'Configure dashboard'" data-placement="bottom"><a href='#' bs-modal="'app/partials/dasheditor.html'"><i class='icon-cog pointer'></i></a></li>

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

@@ -1,12 +1,12 @@
 <!-- is there a better way to repeat without actually affecting the page? -->
-<nil ng-repeat="row in dashboard.current.pulldowns" ng-controller="RowCtrl">
-  <div class="top-row-open" ng-hide="row.collapse">
-    <kibana-simple-panel type="row.type" ng-cloak></kibana-simple-panel>
+<nil ng-repeat="pulldown in dashboard.current.pulldowns" ng-controller="PulldownCtrl">
+  <div class="top-row-open" ng-hide="pulldown.collapse">
+    <kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
   </div>
-  <div class="top-row-close pointer" ng-click="toggle_row(row);dismiss();" bs-tooltip="'Toggle '+row.type" data-placement="bottom">
-    <span class="small row-text">{{row.type}}</span>
-    <i class="small" ng-class="{'icon-expand':row.collapse,'icon-collapse-top':!row.collapse}"></i>
-    <i class="small icon-circle text-warning" ng-show="row.notice && row.collapse"></i>
+  <div class="top-row-close pointer" ng-click="toggle_pulldown(pulldown);dismiss();" bs-tooltip="'Toggle '+pulldown.type" data-placement="bottom">
+    <span class="small row-text">{{pulldown.type}}</span>
+    <i class="small" ng-class="{'icon-expand':pulldown.collapse,'icon-collapse-top':!pulldown.collapse}"></i>
+    <i class="small icon-star text-warning" ng-show="row.notice && pulldown.collapse"></i>
   </div>
 </nil>
 

+ 20 - 15
src/app/partials/dasheditor.html

@@ -1,9 +1,11 @@
 <div class="modal-body">
   <div class="pull-right editor-title">Dashboard settings</div>
 
-  <div ng-model="editor.index" bs-tabs>
+  <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" data-title="{{tab.type}}">
+    </div>
   </div>
 
   <div ng-show="editor.index == 0">
@@ -103,46 +105,49 @@
     <h5>Allow saving to</h5>
     <div class="row-fluid">
       <div class="span2">
-        <label class="small">File</label><input type="checkbox" ng-model="loader.save_local" ng-checked="loader.save_local">
+        <label class="small">File</label><input type="checkbox" ng-model="dashboard.current.loader.save_local" ng-checked="dashboard.current.loader.save_local">
       </div>
       <div class="span2">
-        <label class="small">Browser</label><input type="checkbox" ng-model="loader.save_default" ng-checked="loader.save_default">
+        <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="span2">
-        <label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="loader.save_gist" ng-checked="loader.save_gist">
+        <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="span2">
-        <label class="small">Elasticsearch</label><input type="checkbox" ng-model="loader.save_elasticsearch" ng-checked="loader.save_elasticsearch">
+        <label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.save_elasticsearch" ng-checked="dashboard.current.loader.save_elasticsearch">
       </div>
     </div>
     <h5>Allow loading from</h5>
     <div class="row-fluid">
       <div class="span2">
-        <label class="small">Local file</label><input type="checkbox" ng-model="loader.load_local" ng-checked="loader.load_local">
+        <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="span2">
-        <label class="small">Gist</label><input type="checkbox" ng-model="loader.load_gist" ng-checked="loader.load_gist">
+        <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="span2">
-        <label class="small">Elasticsearch</label><input type="checkbox" ng-model="loader.load_elasticsearch" ng-checked="loader.load_elasticsearch">
+        <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="span3" ng-show="loader.load.elasticsearch">
-        <label class="small">ES list size</label><input class="input-mini" type="number" ng-model="loader.load_elasticsearch_size">
+      <div class="span3" 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>
     <h5>Sharing</h5>
     <div class="row-fluid">
       <div class="span2" >
-        <label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="loader.save_temp" ng-checked="loader.save_temp">
+        <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="span2" ng-show="loader.save_temp">
-        <label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="loader.save_temp_ttl_enable">
+      <div class="span2" 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="span5" ng-show="loader.save_temp &amp;&amp; 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="loader.save_temp_ttl">
+      <div class="span5" ng-show="dashboard.current.loader.save_temp &amp;&amp; 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 ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-include="'./app/panels/'+pulldown.type+'/editor.html'" ng-show="editor.index == 4+$index">
+  </div>
 </div>
 
 <div class="modal-footer">

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

@@ -13,7 +13,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
 
   var module = angular.module('kibana.services');
 
-  module.service('dashboard', function($routeParams, $http, $rootScope, $injector, $location,
+  module.service('dashboard', function(
+    $routeParams, $http, $rootScope, $injector, $location, $timeout,
     ejsResource, timer, kbnIndex, alertSrv
   ) {
     // A hash of defaults to use when loading a dashboard
@@ -33,6 +34,11 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
           type: 'filtering'
         }
       ],
+      nav: [
+        {
+          type: 'timepicker2'
+        }
+      ],
       services: {},
       loader: {
         save_gist: false,
@@ -42,10 +48,10 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
         save_temp: true,
         save_temp_ttl_enable: true,
         save_temp_ttl: '30d',
-        load_gist: true,
+        load_gist: false,
         load_elasticsearch: true,
         load_elasticsearch_size: 20,
-        load_local: true,
+        load_local: false,
         hide: false
       },
       index: {
@@ -53,6 +59,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
         pattern: '_all',
         default: 'INDEX_MISSING'
       },
+      refresh: false
     };
 
     // An elasticJS client to use
@@ -172,6 +179,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
         self.indices = [dashboard.index.default];
       }
 
+      // Set the current dashboard
       self.current = _.clone(dashboard);
 
       // Ok, now that we've setup the current dashboard, we can inject our services
@@ -182,11 +190,16 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
       querySrv.init();
       filterSrv.init();
 
-      // If there's an index interval set and no existing time filter, send a refresh to set one
+      // If there's an interval set, the indices have not been calculated yet,
+      // so there is no data. Call refresh to calculate the indices and notify the panels.
       if(dashboard.index.interval !== 'none') {
         self.refresh();
       }
 
+      if(dashboard.refresh) {
+        self.set_interval(dashboard.refresh);
+      }
+
       return true;
     };
 
@@ -420,6 +433,23 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
         return false;
       });
     };
+
+    this.set_interval = function (interval) {
+      self.current.refresh = interval;
+      if(interval) {
+        var _i = kbn.interval_to_ms(interval);
+        timer.cancel(self.refresh_timer);
+        self.refresh_timer = timer.register($timeout(function() {
+          self.set_interval(interval);
+          self.refresh();
+        },_i));
+        self.refresh();
+      } else {
+        timer.cancel(self.refresh_timer);
+      }
+    };
+
+
   });
 
 });

+ 1 - 16
src/app/services/panelMove.js

@@ -7,26 +7,20 @@ function (angular, _) {
 
   var module = angular.module('kibana.services');
 
-  module.service('panelMove', function(dashboard, $rootScope, alertSrv) {
+  module.service('panelMove', function(dashboard, $rootScope) {
 
     /* each of these can take event,ui,data parameters */
 
-    var notices = [];
-
     this.onStart = function() {
       dashboard.panelDragging =  true;
-      notices.push(alertSrv.set('Moving','Drop this panel into an available space, or on top of another panel','info'));
       $rootScope.$apply();
     };
 
     this.onOver = function() {
-      notices.push(alertSrv.set('Add panel',
-          'Drop to add panel to this row. Panel will use row height, but retain their span','success'));
       $rootScope.$apply();
     };
 
     this.onOut = function() {
-      clearNotices({severity:'success'});
       $rootScope.$apply();
     };
 
@@ -63,21 +57,12 @@ function (angular, _) {
     };
 
     var cleanup = function () {
-      _.each(notices, function(n){
-        alertSrv.clear(n);
-      });
       _.each(dashboard.current.rows, function(row) {
         row.panels = _.without(row.panels,{});
         row.panels = _.compact(row.panels);
       });
     };
 
-    var clearNotices = function(options) {
-      _.each(_.where(notices,options), function(n) {
-        alertSrv.clear(n);
-      });
-    };
-
   });
 
 });

+ 1 - 1
src/index.html

@@ -26,7 +26,7 @@
     <link rel="stylesheet" href="css/bootstrap-responsive.min.css">
     <link rel="stylesheet" href="css/font-awesome.min.css">
 
-    <div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last" style="position:fixed">
+    <div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
       <button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">&times;</button>
       <strong>{{alert.title}}</strong> <span ng-bind-html-unsafe='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
     </div>