浏览代码

Merge branch 'influxdb' Issue #103, influxdb support ready for testing and feedback

Torkel Ödegaard 11 年之前
父节点
当前提交
125ff957f5

+ 5 - 2
src/app/components/settings.js

@@ -52,14 +52,17 @@ function (_, crypto) {
     if (options.graphiteUrl) {
       settings.datasources = {
         graphite: {
-          name: 'default',
+          type: 'graphite',
           url: options.graphiteUrl,
           default: true
         }
       };
     }
 
-    _.map(settings.datasources, parseBasicAuth);
+    _.each(settings.datasources, function(datasource, key) {
+      datasource.name = key;
+      parseBasicAuth(datasource);
+    });
 
     var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
     settings.elasticsearchBasicAuth = elasticParsed.basicAuth;

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

@@ -8,4 +8,5 @@ define([
   './metricKeys',
   './graphiteTarget',
   './graphiteImport',
+  './influxTargetCtrl',
 ], function () {});

+ 24 - 0
src/app/controllers/influxTargetCtrl.js

@@ -0,0 +1,24 @@
+define([
+  'angular'
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('kibana.controllers');
+
+  module.controller('InfluxTargetCtrl', function($scope) {
+
+    $scope.init = function() {
+      if (!$scope.target.function) {
+        $scope.target.function = 'mean';
+      }
+    };
+
+    $scope.duplicate = function() {
+      var clone = angular.copy($scope.target);
+      $scope.panel.targets.push(clone);
+    };
+
+  });
+
+});

+ 118 - 98
src/app/controllers/row.js

@@ -9,109 +9,129 @@ function (angular, app, _) {
   var module = angular.module('kibana.controllers');
 
   module.controller('RowCtrl', function($scope, $rootScope, $timeout) {
-      var _d = {
-        title: "Row",
-        height: "150px",
-        collapse: false,
-        collapsable: true,
-        editable: true,
-        panels: [],
-        notice: false
-      };
-
-      _.defaults($scope.row,_d);
-
-      $scope.init = function() {
-        $scope.reset_panel();
-      };
-
-      $scope.toggle_row = function(row) {
-        if(!row.collapsable) {
-          return;
-        }
-        row.collapse = row.collapse ? false : true;
-        if (!row.collapse) {
-          $timeout(function() {
-            $scope.$broadcast('render');
-          });
-        } else {
-          row.notice = false;
-        }
-      };
-
-      $scope.rowSpan = function(row) {
-        var panels = _.filter(row.panels, function(p) {
-          return $scope.isPanel(p);
+    var _d = {
+      title: "Row",
+      height: "150px",
+      collapse: false,
+      collapsable: true,
+      editable: true,
+      panels: [],
+      notice: false
+    };
+
+    _.defaults($scope.row,_d);
+
+    $scope.init = function() {
+      $scope.reset_panel();
+    };
+
+    $scope.toggle_row = function(row) {
+      if(!row.collapsable) {
+        return;
+      }
+      row.collapse = row.collapse ? false : true;
+      if (!row.collapse) {
+        $timeout(function() {
+          $scope.$broadcast('render');
         });
-        return _.reduce(_.pluck(panels,'span'), function(p,v) {
-          return p+v;
-        },0);
-      };
-
-      // This can be overridden by individual panels
-      $scope.close_edit = function() {
-        $scope.$broadcast('render');
-      };
-
-      $scope.add_panel = function(row,panel) {
-        $scope.row.panels.push(panel);
-      };
-
-      $scope.remove_panel_from_row = function(row, panel) {
-        if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) {
-          row.panels = _.without(row.panels,panel);
+      } else {
+        row.notice = false;
+      }
+    };
+
+    $scope.rowSpan = function(row) {
+      var panels = _.filter(row.panels, function(p) {
+        return $scope.isPanel(p);
+      });
+      return _.reduce(_.pluck(panels,'span'), function(p,v) {
+        return p+v;
+      },0);
+    };
+
+    // This can be overridden by individual panels
+    $scope.close_edit = function() {
+      $scope.$broadcast('render');
+    };
+
+    $scope.add_panel = function(row,panel) {
+      $scope.row.panels.push(panel);
+    };
+
+    $scope.remove_panel_from_row = function(row, panel) {
+      if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) {
+        row.panels = _.without(row.panels,panel);
+      }
+    };
+
+    $scope.duplicatePanel = function(panel, row) {
+      row = row || $scope.row;
+      var currentRowSpan = $scope.rowSpan(row);
+      if (currentRowSpan <= 9) {
+        row.panels.push(angular.copy(panel));
+      }
+      else {
+        var rowsList = $scope.dashboard.current.rows;
+        var rowIndex = _.indexOf(rowsList, row);
+        if (rowIndex === rowsList.length - 1) {
+          var newRow = angular.copy($scope.row);
+          newRow.panels = [];
+          $scope.dashboard.current.rows.push(newRow);
+          $scope.duplicatePanel(panel, newRow);
         }
+        else {
+          $scope.duplicatePanel(panel, rowsList[rowIndex+1]);
+        }
+      }
+    };
+
+    /** @scratch /panels/0
+     * [[panels]]
+     * = Panels
+     *
+     * [partintro]
+     * --
+     * *Kibana* dashboards are made up of blocks called +panels+. Panels are organized into rows
+     * and can serve many purposes, though most are designed to provide the results of a query or
+     * multiple queries as a visualization. Other panels may show collections of documents or
+     * allow you to insert instructions for your users.
+     *
+     * Panels can be configured easily via the Kibana web interface. For more advanced usage, such
+     * as templated or scripted dashboards, documentation of panel properties is available in this
+     * section. You may find settings here which are not exposed via the web interface.
+     *
+     * Each panel type has its own properties, hover there are several that are shared.
+     *
+    */
+
+    $scope.reset_panel = function(type) {
+      var
+        defaultSpan = 4,
+        _as = 12-$scope.rowSpan($scope.row);
+
+      $scope.panel = {
+        error   : false,
+        /** @scratch /panels/1
+         * span:: A number, 1-12, that describes the width of the panel.
+         */
+        span    : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
+        /** @scratch /panels/1
+         * editable:: Enable or disable the edit button the the panel
+         */
+        editable: true,
+        /** @scratch /panels/1
+         * type:: The type of panel this object contains. Each panel type will require additional
+         * properties. See the panel types list to the right.
+         */
+        type    : type
       };
+    };
 
-      /** @scratch /panels/0
-       * [[panels]]
-       * = Panels
-       *
-       * [partintro]
-       * --
-       * *Kibana* dashboards are made up of blocks called +panels+. Panels are organized into rows
-       * and can serve many purposes, though most are designed to provide the results of a query or
-       * multiple queries as a visualization. Other panels may show collections of documents or
-       * allow you to insert instructions for your users.
-       *
-       * Panels can be configured easily via the Kibana web interface. For more advanced usage, such
-       * as templated or scripted dashboards, documentation of panel properties is available in this
-       * section. You may find settings here which are not exposed via the web interface.
-       *
-       * Each panel type has its own properties, hover there are several that are shared.
-       *
-      */
-
-      $scope.reset_panel = function(type) {
-        var
-          defaultSpan = 4,
-          _as = 12-$scope.rowSpan($scope.row);
-
-        $scope.panel = {
-          error   : false,
-          /** @scratch /panels/1
-           * span:: A number, 1-12, that describes the width of the panel.
-           */
-          span    : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
-          /** @scratch /panels/1
-           * editable:: Enable or disable the edit button the the panel
-           */
-          editable: true,
-          /** @scratch /panels/1
-           * type:: The type of panel this object contains. Each panel type will require additional
-           * properties. See the panel types list to the right.
-           */
-          type    : type
-        };
-      };
-
-      /** @scratch /panels/2
-       * --
-       */
+    /** @scratch /panels/2
+     * --
+     */
 
-      $scope.init();
+    $scope.init();
 
-    }
-  );
+  });
 
 });

+ 1 - 2
src/app/dashboards/default.json

@@ -69,8 +69,7 @@
           "nullPointMode": "connected",
           "steppedLine": false,
           "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
+            "value_type": "cumulative"
           },
           "targets": [
             {

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

@@ -32,5 +32,4 @@ function (angular, app, _) {
         }
       };
     });
-
 });

+ 48 - 0
src/app/directives/bootstrap-tagsinput.js

@@ -82,4 +82,52 @@ function (angular, $) {
         }
       };
     });
+
+  angular
+    .module('kibana.directives')
+    .directive('gfDropdown', function ($parse, $compile, $timeout) {
+
+      function buildTemplate(items, ul) {
+        if (!ul) {
+          ul = [
+            '<ul class="dropdown-menu" role="menu" aria-labelledby="drop1">',
+            '</ul>'
+          ];
+        }
+
+        angular.forEach(items, function (item, index) {
+          if (item.divider) {
+            return ul.splice(index + 1, 0, '<li class="divider"></li>');
+          }
+
+          var li = '<li' + (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') + '>' +
+            '<a tabindex="-1" ng-href="' + (item.href || '') + '"' + (item.click ? '" ng-click="' + item.click + '"' : '') +
+              (item.target ? '" target="' + item.target + '"' : '') + (item.method ? '" data-method="' + item.method + '"' : '') +
+              (item.configModal ? ' config-modal="' + item.configModal + '"' : "") +
+              '>' + (item.text || '') + '</a>';
+
+          if (item.submenu && item.submenu.length) {
+            li += buildTemplate(item.submenu).join('\n');
+          }
+
+          li += '</li>';
+          ul.splice(index + 1, 0, li);
+        });
+        return ul;
+      }
+
+      return {
+        restrict: 'EA',
+        scope: true,
+        link: function postLink(scope, iElement, iAttrs) {
+          var getter = $parse(iAttrs.gfDropdown), items = getter(scope);
+          $timeout(function () {
+            var dropdown = angular.element(buildTemplate(items).join(''));
+            dropdown.insertAfter(iElement);
+            $compile(iElement.next('ul.dropdown-menu'))(scope);
+          });
+          iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
+        }
+      };
+    });
 });

+ 6 - 5
src/app/directives/grafanaGraph.js

@@ -75,9 +75,6 @@ function (angular, $, kbn, moment, _) {
             }
           });
 
-          // Set barwidth based on specified interval
-          var barwidth = kbn.interval_to_ms(scope.interval);
-
           var stack = panel.stack ? true : null;
 
           // Populate element
@@ -96,7 +93,7 @@ function (angular, $, kbn, moment, _) {
               bars:   {
                 show: panel.bars,
                 fill: 1,
-                barWidth: barwidth/1.5,
+                barWidth: 1,
                 zero: false,
                 lineWidth: 0
               },
@@ -128,6 +125,10 @@ function (angular, $, kbn, moment, _) {
             data[i].data = _d;
           }
 
+          if (panel.bars && data.length && data[0].info.timeStep) {
+            options.series.bars.barWidth = data[0].info.timeStep / 1.5;
+          }
+
           addTimeAxis(options);
           addGridThresholds(options, panel);
           addAnnotations(options);
@@ -289,7 +290,7 @@ function (angular, $, kbn, moment, _) {
             seriesInfo = item.series.info;
             format = scope.panel.y_formats[seriesInfo.yaxis - 1];
 
-            if (seriesInfo.alias || scope.panel.tooltip.query_as_alias) {
+            if (seriesInfo.alias) {
               group = '<small style="font-size:0.9em;">' +
                 '<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
                 (seriesInfo.alias || seriesInfo.query)+

+ 128 - 15
src/app/directives/kibanaPanel.js

@@ -1,13 +1,14 @@
 define([
   'angular',
-  'jquery'
+  'jquery',
+  'underscore'
 ],
-function (angular, $) {
+function (angular, $, _) {
   'use strict';
 
   angular
     .module('kibana.directives')
-    .directive('kibanaPanel', function($compile) {
+    .directive('kibanaPanel', function($compile, $timeout, $rootScope) {
 
       var container = '<div class="panel-container"></div>';
       var content = '<div class="panel-content"></div>';
@@ -28,8 +29,8 @@ function (angular, $) {
               '<i class="icon-spinner icon-spin icon-large"></i>' +
             '</span>' +
 
-            '<span ng-if="panelMeta.menuItems" class="dropdown">' +
-              '<span class="panel-text panel-title pointer" bs-dropdown="panelMeta.menuItems" tabindex="1" ' +
+            '<span class="dropdown">' +
+              '<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
               'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
               ' jqyoui-draggable="'+
               '{'+
@@ -44,11 +45,6 @@ function (angular, $) {
               '</span>' +
             '</span>'+
 
-            '<span ng-if="!panelMeta.menuItems" config-modal="./app/partials/paneleditor.html" ' +
-                  ' class="panel-text panel-title pointer" >' +
-              '{{panel.title}}' +
-            '</span>'+
-
           '</div>'+
         '</div>\n'+
       '</div>';
@@ -88,8 +84,7 @@ function (angular, $) {
             var nameAsPath = name.replace(".", "/");
             $scope.require([
               'jquery',
-              'text!panels/'+nameAsPath+'/module.html',
-              'text!panels/'+nameAsPath+'/editor.html'
+              'text!panels/'+nameAsPath+'/module.html'
             ], function ($, moduleTemplate) {
               var $module = $(moduleTemplate);
               // top level controllers
@@ -101,9 +96,7 @@ function (angular, $) {
                 $controllers.first().prepend(panelHeader);
                 $controllers.first().find('.panel-header').nextAll().wrapAll(content);
 
-                $scope.require([
-                  'panels/'+nameAsPath+'/module'
-                ], function() {
+                $scope.require(['panels/' + nameAsPath + '/module'], function() {
                   loadModule($module);
                 });
               } else {
@@ -111,6 +104,126 @@ function (angular, $) {
               }
             });
           });
+
+
+          /*
+          /* Panel base functionality
+          /* */
+          newScope.initPanel = function(scope) {
+
+            scope.updateColumnSpan = function(span) {
+              scope.panel.span = span;
+
+              $timeout(function() {
+                scope.$emit('render');
+              });
+            };
+
+            function enterFullscreenMode(options) {
+              var docHeight = $(window).height();
+              var editHeight = Math.floor(docHeight * 0.3);
+              var fullscreenHeight = Math.floor(docHeight * 0.7);
+              var oldTimeRange = scope.range;
+
+              scope.height = options.edit ? editHeight : fullscreenHeight;
+              scope.editMode = options.edit;
+
+              if (!scope.fullscreen) {
+                var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
+                  scope.editMode = false;
+                  scope.fullscreen = false;
+                  delete scope.height;
+
+                  closeEditMode();
+
+                  $timeout(function() {
+                    if (oldTimeRange !== $scope.range) {
+                      scope.dashboard.refresh();
+                    }
+                    else {
+                      scope.$emit('render');
+                    }
+                  });
+                });
+              }
+
+              $(window).scrollTop(0);
+
+              scope.fullscreen = true;
+
+              $rootScope.$emit('panel-fullscreen-enter');
+
+              $timeout(function() {
+                scope.$emit('render');
+              });
+            }
+
+            scope.toggleFullscreenEdit = function() {
+              if (scope.editMode) {
+                $rootScope.$emit('panel-fullscreen-exit');
+                return;
+              }
+
+              enterFullscreenMode({edit: true});
+            };
+
+            $scope.toggleFullscreen = function() {
+              if (scope.fullscreen && !scope.editMode) {
+                $rootScope.$emit('panel-fullscreen-exit');
+                return;
+              }
+
+              enterFullscreenMode({ edit: false });
+            };
+
+            var menu = [
+              {
+                text: 'Edit',
+                configModal: "app/partials/paneleditor.html",
+                condition: !scope.panelMeta.fullscreenEdit
+              },
+              {
+                text: 'Edit',
+                click: "toggleFullscreenEdit()",
+                condition: scope.panelMeta.fullscreenEdit
+              },
+              {
+                text: "Fullscreen",
+                click: 'toggleFullscreen()',
+                condition: scope.panelMeta.fullscreenView
+              },
+              {
+                text: 'Duplicate',
+                click: 'duplicatePanel(panel)',
+                condition: true
+              },
+              {
+                text: 'Span',
+                submenu: [
+                  { text: '1', click: 'updateColumnSpan(1)' },
+                  { text: '2', click: 'updateColumnSpan(2)' },
+                  { text: '3', click: 'updateColumnSpan(3)' },
+                  { text: '4', click: 'updateColumnSpan(4)' },
+                  { text: '5', click: 'updateColumnSpan(5)' },
+                  { text: '6', click: 'updateColumnSpan(6)' },
+                  { text: '7', click: 'updateColumnSpan(7)' },
+                  { text: '8', click: 'updateColumnSpan(8)' },
+                  { text: '9', click: 'updateColumnSpan(9)' },
+                  { text: '10', click: 'updateColumnSpan(10)' },
+                  { text: '11', click: 'updateColumnSpan(11)' },
+                  { text: '12', click: 'updateColumnSpan(12)' },
+                ],
+                condition: true
+              },
+              {
+                text: 'Remove',
+                click: 'remove_panel_from_row(row, panel)',
+                condition: true
+              }
+            ];
+
+            scope.panelMeta.menu = _.where(menu, { condition: true });
+          };
         }
       };
     });

+ 6 - 27
src/app/panels/graphite/module.html

@@ -2,13 +2,6 @@
       ng-init="init()"
       style="min-height:{{panel.height || row.height}}"
       ng-class="{'panel-fullscreen': fullscreen}">
-  <div>
-    <span ng-show='panel.options'>
-      <a class="link underline small" ng-show='panel.options' ng-click="options=!options">
-        <i ng-show="!options" class="icon-caret-right"></i><i ng-show="options" class="icon-caret-down"></i> View
-      </a> |&nbsp
-    </span>
-  </div>
 
   <div style="position: relative">
 
@@ -27,27 +20,13 @@
   </div>
 
   <div class="panel-full-edit-tabs" ng-if="editMode">
-      <div ng-model="editor.index" bs-tabs>
-        <div ng-repeat="tab in editorTabs" data-title="{{tab}}">
-        </div>
-      </div>
-
-      <div class="tab-content" ng-show="editorTabs[editor.index] == 'General'">
-        <div ng-include src="'app/partials/panelgeneral.html'"></div>
-
-        <div class="editor-row" ng-show="datasources.length > 0">
-          <div class="section">
-            <div class="editor-option">
-              <label class="small">Datasource</label>
-              <select class="input-large" ng-options="obj.value as obj.name for obj in datasources" ng-model="panel.datasource" ng-change="datasourceChanged()"></select>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
-        <div ng-include src="tab.src"></div>
+    <div ng-model="editor.index" bs-tabs>
+      <div ng-repeat="tab in editorTabs" data-title="{{tab}}">
       </div>
+    </div>
 
+    <div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
+      <div ng-include src="tab.src"></div>
+    </div>
   </div>
 </div>

+ 56 - 168
src/app/panels/graphite/module.js

@@ -39,11 +39,14 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
     $scope.panelMeta = {
       modals : [],
       editorTabs: [],
-
       fullEditorTabs : [
         {
-          title:'Targets',
-          src:'app/panels/graphite/editor.html'
+          title: 'General',
+          src:'app/partials/panelgeneral.html'
+        },
+        {
+          title: 'Metrics',
+          src:'app/partials/metrics.html'
         },
         {
           title:'Axes & Grid',
@@ -54,30 +57,9 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
           src:'app/panels/graphite/styleEditor.html'
         }
       ],
-
-      menuItems: [
-        { text: 'Edit',         click: 'openConfigureModal()' },
-        { text: 'Fullscreen',   click: 'toggleFullscreen()' },
-        { text: 'Duplicate',    click: 'duplicate()' },
-        { text: 'Span', submenu: [
-          { text: '1', click: 'updateColumnSpan(1)' },
-          { text: '2', click: 'updateColumnSpan(2)' },
-          { text: '3', click: 'updateColumnSpan(3)' },
-          { text: '4', click: 'updateColumnSpan(4)' },
-          { text: '5', click: 'updateColumnSpan(5)' },
-          { text: '6', click: 'updateColumnSpan(6)' },
-          { text: '7', click: 'updateColumnSpan(7)' },
-          { text: '8', click: 'updateColumnSpan(8)' },
-          { text: '9', click: 'updateColumnSpan(9)' },
-          { text: '10', click: 'updateColumnSpan(10)' },
-          { text: '11', click: 'updateColumnSpan(11)' },
-          { text: '12', click: 'updateColumnSpan(12)' },
-        ]},
-        { text: 'Remove',          click: 'remove_panel_from_row(row, panel)' }
-      ],
-
-      status  : "Unstable",
-      description : "Graphite graphing panel <br /><br />"
+      fullscreenEdit: true,
+      fullscreenView: true,
+      description : "Graphing"
     };
 
     // Set and populate defaults
@@ -217,35 +199,27 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
     }
 
     $scope.init = function() {
-      // Hide view options by default
+      $scope.initPanel($scope);
+
       $scope.fullscreen = false;
-      $scope.options = false;
-      $scope.editor = {index: 1};
-      $scope.editorTabs = _.union(['General'],_.pluck($scope.panelMeta.fullEditorTabs,'title'));
+      $scope.editor = { index: 1 };
+      $scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
       $scope.hiddenSeries = {};
 
       $scope.datasources = datasourceSrv.listOptions();
-      $scope.datasource = datasourceSrv.get($scope.panel.datasource);
-
-      // Always show the query if an alias isn't set. Users can set an alias if the query is too
-      // long
-      $scope.panel.tooltip.query_as_alias = true;
-
-      $scope.get_data();
+      $scope.setDatasource($scope.panel.datasource);
     };
 
-    $scope.datasourceChanged = function() {
-      $scope.datasource = datasourceSrv.get($scope.panel.datasource);
-      $scope.get_data();
-    };
+    $scope.setDatasource = function(datasource) {
+      $scope.panel.datasource = datasource;
+      $scope.datasource = datasourceSrv.get(datasource);
 
-    $scope.remove_panel_from_row = function(row, panel) {
-      if ($scope.fullscreen) {
-        $rootScope.$emit('panel-fullscreen-exit');
-      }
-      else {
-        $scope.$parent.remove_panel_from_row(row, panel);
+      if (!$scope.datasource) {
+        $scope.panel.error = "Cannot find datasource " + datasource;
+        return;
       }
+
+      $scope.get_data();
     };
 
     $scope.removeTarget = function (target) {
@@ -256,25 +230,17 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
     $scope.updateTimeRange = function () {
       $scope.range = filterSrv.timeRange();
       $scope.rangeUnparsed = filterSrv.timeRange(false);
+      $scope.resolution = ($(window).width() / ($scope.panel.span / 12)) / 2;
 
       $scope.interval = '10m';
 
       if ($scope.range) {
         $scope.interval = kbn.secondsToHms(
-          kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.panel.resolution, 0) / 1000
+          kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
         );
       }
     };
 
-    /**
-     * Fetch the data for a chunk of a queries results. Multiple segments occur when several indicies
-     * need to be consulted (like timestamped logstash indicies)
-     *
-     * The results of this function are stored on the scope's data property. This property will be an
-     * array of objects with the properties info, time_series, and hits. These objects are used in the
-     * render_panel function to create the historgram.
-     *
-     */
     $scope.get_data = function() {
       delete $scope.panel.error;
 
@@ -284,22 +250,23 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
 
       var graphiteQuery = {
         range: $scope.rangeUnparsed,
+        interval: $scope.interval,
         targets: $scope.panel.targets,
         format: $scope.panel.renderer === 'png' ? 'png' : 'json',
-        maxDataPoints: $scope.panel.span * 50,
+        maxDataPoints: $scope.resolution,
         datasource: $scope.panel.datasource
       };
 
       $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed);
 
       return $scope.datasource.query(graphiteQuery)
-        .then($scope.receiveGraphiteData)
+        .then($scope.dataHandler)
         .then(null, function(err) {
           $scope.panel.error = err.message || "Graphite HTTP Request Error";
         });
     };
 
-    $scope.receiveGraphiteData = function(results) {
+    $scope.dataHandler = function(results) {
       $scope.panelMeta.loading = false;
       $scope.legend = [];
 
@@ -309,44 +276,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
         return;
       }
 
-      results = results.data;
-      var data = [];
-
       $scope.datapointsWarning = false;
       $scope.datapointsCount = 0;
       $scope.datapointsOutside = false;
 
-      _.each(results, function(targetData) {
-        var datapoints = targetData.datapoints;
-        var alias = targetData.target;
-        var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
-        var yaxis = $scope.panel.aliasYAxis[alias] || 1;
-
-        var seriesInfo = {
-          alias: alias,
-          color:  color,
-          enable: true,
-          yaxis: yaxis
-        };
-
-        var series = new timeSeries.ZeroFilled({
-          datapoints: datapoints,
-          info: seriesInfo,
-        });
-
-        if (datapoints && datapoints.length > 0) {
-          var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
-          var from = moment.utc($scope.range.from);
-          if (last - from < -1000) {
-            $scope.datapointsOutside = true;
-          }
-        }
-
-        $scope.datapointsCount += datapoints.length;
-
-        $scope.legend.push(seriesInfo);
-        data.push(series);
-      });
+      var data = _.map(results.data, $scope.seriesHandler);
 
       $scope.datapointsWarning = $scope.datapointsCount || !$scope.datapointsOutside;
 
@@ -359,52 +293,41 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
         });
     };
 
-    $scope.add_target = function() {
-      $scope.panel.targets.push({target: ''});
-    };
-
-    $scope.enterFullscreenMode = function(options) {
-      var docHeight = $(window).height();
-      var editHeight = Math.floor(docHeight * 0.3);
-      var fullscreenHeight = Math.floor(docHeight * 0.7);
-      var oldTimeRange = $scope.range;
-
-      $scope.height = options.edit ? editHeight : fullscreenHeight;
-      $scope.editMode = options.edit;
-
-      if (!$scope.fullscreen) {
-        var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
-          $scope.editMode = false;
-          $scope.fullscreen = false;
-          delete $scope.height;
+    $scope.seriesHandler = function(seriesData, index) {
+      var datapoints = seriesData.datapoints;
+      var alias = seriesData.target;
+      var color = $scope.panel.aliasColors[alias] || $scope.colors[index];
+      var yaxis = $scope.panel.aliasYAxis[alias] || 1;
+
+      var seriesInfo = {
+        alias: alias,
+        color:  color,
+        enable: true,
+        yaxis: yaxis
+      };
 
-          closeEditMode();
+      $scope.legend.push(seriesInfo);
 
-          $timeout(function() {
-            $scope.$emit('render');
+      var series = new timeSeries.ZeroFilled({
+        datapoints: datapoints,
+        info: seriesInfo,
+      });
 
-            if (oldTimeRange !== $scope.range) {
-              $scope.dashboard.refresh();
-            }
-          });
-        });
+      if (datapoints && datapoints.length > 0) {
+        var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
+        var from = moment.utc($scope.range.from);
+        if (last - from < -10000) {
+          $scope.datapointsOutside = true;
+        }
       }
 
-      $(window).scrollTop(0);
+      $scope.datapointsCount += datapoints.length;
 
-      $scope.fullscreen = true;
-      $rootScope.$emit('panel-fullscreen-enter');
-
-      $timeout($scope.render);
+      return series;
     };
 
-    $scope.openConfigureModal = function() {
-      if ($scope.editMode) {
-        $rootScope.$emit('panel-fullscreen-exit');
-        return;
-      }
-
-      $scope.enterFullscreenMode({edit: true});
+    $scope.add_target = function() {
+      $scope.panel.targets.push({target: ''});
     };
 
     $scope.otherPanelInFullscreenMode = function() {
@@ -421,36 +344,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       $scope.render();
     };
 
-    $scope.duplicate = function(addToRow) {
-      addToRow = addToRow || $scope.row;
-      var currentRowSpan = $scope.rowSpan(addToRow);
-      if (currentRowSpan <= 9) {
-        addToRow.panels.push(angular.copy($scope.panel));
-      }
-      else {
-        var rowsList = $scope.dashboard.current.rows;
-        var rowIndex = _.indexOf(rowsList, addToRow);
-        if (rowIndex === rowsList.length - 1) {
-          var newRow = angular.copy($scope.row);
-          newRow.panels = [];
-          $scope.dashboard.current.rows.push(newRow);
-          $scope.duplicate(newRow);
-        }
-        else {
-          $scope.duplicate(rowsList[rowIndex+1]);
-        }
-      }
-    };
-
-    $scope.toggleFullscreen = function() {
-      if ($scope.fullscreen && !$scope.editMode) {
-        $rootScope.$emit('panel-fullscreen-exit');
-        return;
-      }
-
-      $scope.enterFullscreenMode({edit: false});
-    };
-
     $scope.toggleSeries = function(info) {
       if ($scope.hiddenSeries[info.alias]) {
         delete $scope.hiddenSeries[info.alias];
@@ -473,11 +366,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       $scope.render();
     };
 
-    $scope.updateColumnSpan = function(span) {
-      $scope.panel.span = span;
-      $timeout($scope.render);
-    };
-
   });
 
 

+ 5 - 0
src/app/panels/graphite/timeSeries.js

@@ -50,7 +50,12 @@ function (_, kbn) {
       result.push([currentTime * 1000, currentValue]);
     }, this);
 
+    if (result.length > 2) {
+      this.info.timeStep = result[1][0] - result[0][0];
+    }
+
     if (result.length) {
+
       this.info.avg = (this.info.total / result.length);
       this.info.current = result[result.length-1][1];
 

+ 1 - 1
src/app/panels/text/module.js

@@ -24,7 +24,6 @@ function (angular, app, _, require) {
 
   module.controller('text', function($scope) {
     $scope.panelMeta = {
-      status  : "Stable",
       description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
     };
 
@@ -45,6 +44,7 @@ function (angular, app, _, require) {
     _.defaults($scope.panel,_d);
 
     $scope.init = function() {
+      $scope.initPanel($scope);
       $scope.ready = false;
     };
 

+ 4 - 6
src/app/panels/graphite/editor.html → src/app/partials/graphite/editor.html

@@ -1,4 +1,5 @@
-<div class="editor-row">
+
+<div class="editor-row" style="margin-top: 10px;">
 
 	<div  ng-repeat="target in panel.targets"
         class="grafana-target"
@@ -52,7 +53,7 @@
         </ul>
 
         <input  type="text"
-                class="grafana-target-text-input"
+                class="grafana-target-text-input span12"
                 ng-model="target.target"
                 focus-me="showTextEditor"
                 spellcheck='false'
@@ -75,7 +76,7 @@
             </ul>
           </li>
           <li ng-repeat="func in functions">
-            <a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/panels/graphite/funcEditor.html'" data-placement="bottom">
+            <a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/partials/graphite/funcEditor.html'" data-placement="bottom">
               {{func.text}}
             </a>
           </li>
@@ -111,6 +112,3 @@
   </div>
 </div>
 
-<div class="editor-row" style="margin-top: 20px" ng-show="editor.index == 1">
-  <button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add target</button>
-</div>

+ 0 - 0
src/app/panels/graphite/funcEditor.html → src/app/partials/graphite/funcEditor.html


+ 93 - 0
src/app/partials/influxdb/editor.html

@@ -0,0 +1,93 @@
+
+<div class="editor-row" style="margin-top: 10px;">
+
+  <div  ng-repeat="target in panel.targets"
+        class="grafana-target"
+        ng-class="{'grafana-target-hidden': target.hide}"
+        ng-controller="InfluxTargetCtrl"
+        ng-init="init()">
+
+    <div class="grafana-target-inner-wrapper">
+      <div class="grafana-target-inner">
+        <ul class="grafana-target-controls">
+          <li class="dropdown">
+            <a  class="pointer dropdown-toggle"
+                data-toggle="dropdown"
+                tabindex="1">
+              <i class="icon-cog"></i>
+            </a>
+            <ul class="dropdown-menu pull-right" role="menu">
+              <li role="menuitem">
+                <a  tabindex="1"
+                    ng-click="duplicate()">
+                  Duplicate
+                </a>
+              </li>
+            </ul>
+          </li>
+          <li>
+            <a class="pointer" tabindex="1" ng-click="removeTarget(target)">
+              <i class="icon-remove"></i>
+            </a>
+          </li>
+        </ul>
+
+        <ul class="grafana-target-controls-left">
+          <li>
+            <a  class="grafana-target-segment"
+                ng-click="target.hide = !target.hide; get_data();"
+                role="menuitem">
+              <i class="icon-eye-open"></i>
+            </a>
+          </li>
+        </ul>
+
+        <ul class="grafana-segment-list" role="menu">
+          <li class="grafana-target-segment">
+            from series
+          </li>
+          <li>
+            <input type="text"
+                   class="input-medium grafana-target-segment-input"
+                   ng-model="target.series"
+                   spellcheck='false'
+                   placeholder="series name"
+                   ng-model-onblur ng-change="get_data()" >
+          </li>
+          <li class="grafana-target-segment">
+            select
+          </li>
+          <li>
+            <input type="text"
+                   class="input-medium grafana-target-segment-input"
+                   ng-model="target.column"
+                   placeholder="value column"
+                   spellcheck='false'
+                   ng-model-onblur ng-change="get_data()" >
+          </li>
+          <li class="grafana-target-segment">
+            function
+          </li>
+          <li>
+            <select class="input-medium grafana-target-segment-input"  ng-change="get_data()" ng-model="target.function" ng-options="f for f in ['mean', 'sum', 'min', 'max', 'median', 'derivative', 'stddev']" ></select>
+          </li>
+          <li class="grafana-target-segment">
+            group by time
+          </li>
+          <li>
+            <input type="text"
+                   class="input-mini grafana-target-segment-input"
+                   ng-model="target.interval"
+                   placeholder="{{interval}}"
+                   bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
+                   spellcheck='false'
+                   ng-model-onblur ng-change="get_data()" >
+          </li>
+        </ul>
+
+        <div class="clearfix"></div>
+      </div>
+    </div>
+  </div>
+</div>
+

+ 17 - 0
src/app/partials/metrics.html

@@ -0,0 +1,17 @@
+<div ng-include src="datasource.editorSrc"></div>
+
+
+<div class="editor-row" style="margin-top: 20px">
+  <button class="btn btn-success pull-right" ng-click="add_target(panel.target)">Add query</button>
+
+  <div class="btn-group pull-right" style="margin-right: 10px;">
+    <button class="btn btn-info dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">{{datasource.name}} <span class="caret"></span></button>
+
+    <ul class="dropdown-menu" role="menu">
+      <li ng-repeat="datasource in datasources" role="menuitem">
+        <a ng-click="setDatasource(datasource.value);">{{datasource.name}}</a>
+      </li>
+    </ul>
+  </div>
+
+</div>

+ 6 - 6
src/app/partials/paneladd.html

@@ -1,6 +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>
+<div ng-include="'app/partials/panelgeneral.html'"></div>
+<div ng-if="!panelMeta.fullEditorTabs" 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>

+ 18 - 22
src/app/partials/panelgeneral.html

@@ -1,24 +1,20 @@
-  <div class="editor-row">
-    <div class="section">
-      <strong>{{panelMeta.status}}</strong> // <span ng-bind-html="panelMeta.description"></span>
+<div class="editor-row">
+  <div class="section">
+    <h5>General options</h5>
+    <div class="editor-option">
+      <label class="small">Title</label><input type="text" class="input-medium" ng-model='panel.title'></input>
     </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 [0,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-eye-open></i>'"></i>
-        </label>
-        <input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
-      </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 [0,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>
+    <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-eye-open></i>'"></i>
+      </label>
+      <input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
+    </div>
+  </div>
+</div>

+ 0 - 1
src/app/services/all.js

@@ -7,6 +7,5 @@ define([
   './datasourceSrv',
   './keyboardManager',
   './annotationsSrv',
-  './graphite/graphiteDatasource',
 ],
 function () {});

+ 15 - 5
src/app/services/datasourceSrv.js

@@ -1,25 +1,35 @@
 define([
   'angular',
   'underscore',
-  'config'
+  'config',
+  './graphite/graphiteDatasource',
+  './influxdb/influxdbDatasource',
 ],
 function (angular, _, config) {
   'use strict';
 
   var module = angular.module('kibana.services');
 
-  module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource) {
+  module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource) {
 
     var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true } );
 
     this.default = new GraphiteDatasource(defaultDatasource);
 
     this.get = function(name) {
-      if (!name) {
-        return this.default;
+      if (!name) { return this.default; }
+
+      var ds = config.datasources[name];
+      if (!ds) {
+        return null;
       }
 
-      return new GraphiteDatasource(config.datasources[name]);
+      switch(ds.type) {
+      case 'graphite':
+        return new GraphiteDatasource(ds);
+      case 'influxdb':
+        return new InfluxDatasource(ds);
+      }
     };
 
     this.listOptions = function() {

+ 2 - 0
src/app/services/graphite/graphiteDatasource.js

@@ -17,6 +17,8 @@ function (angular, _, $, config, kbn, moment) {
       this.type = 'graphite';
       this.basicAuth = datasource.basicAuth;
       this.url = datasource.url;
+      this.editorSrc = 'app/partials/graphite/editor.html';
+      this.name = datasource.name;
     }
 
     GraphiteDatasource.prototype.query = function(options) {

+ 137 - 0
src/app/services/influxdb/influxdbDatasource.js

@@ -0,0 +1,137 @@
+define([
+  'angular',
+  'underscore',
+  'kbn'
+],
+function (angular, _, kbn) {
+  'use strict';
+
+  var module = angular.module('kibana.services');
+
+  module.factory('InfluxDatasource', function($q, $http) {
+
+    function InfluxDatasource(datasource) {
+      this.type = 'influxDB';
+      this.editorSrc = 'app/partials/influxDB/editor.html';
+      this.url = datasource.url;
+      this.username = datasource.username;
+      this.password = datasource.password;
+      this.name = datasource.name;
+
+      this.templateSettings = {
+        interpolate : /\[\[([\s\S]+?)\]\]/g,
+      };
+    }
+
+    InfluxDatasource.prototype.query = function(options) {
+
+      var promises = _.map(options.targets, function(target) {
+        if (!target.series || !target.column || target.hide) {
+          return [];
+        }
+
+        var template = "select [[func]]([[column]]) from [[series]] where [[timeFilter]] group by time([[interval]]) order asc";
+
+        var templateData = {
+          series: target.series,
+          column: target.column,
+          func: target.function,
+          timeFilter: getTimeFilter(options),
+          interval: target.interval || options.interval
+        };
+
+        var query = _.template(template, templateData, this.templateSettings);
+        console.log(query);
+
+        return this.doInfluxRequest(query).then(handleInfluxQueryResponse);
+
+      }, this);
+
+      return $q.all(promises).then(function(results) {
+
+        return { data: _.flatten(results) };
+      });
+
+    };
+
+    InfluxDatasource.prototype.doInfluxRequest = function(query) {
+      var params = {
+        u: this.username,
+        p: this.password,
+        q: query
+      };
+
+      var options = {
+        method: 'GET',
+        url:    this.url + '/series',
+        params: params,
+      };
+
+      return $http(options);
+    };
+
+    function handleInfluxQueryResponse(results) {
+      var output = [];
+
+      _.each(results.data, function(series) {
+        var timeCol = series.columns.indexOf('time');
+
+        _.each(series.columns, function(column, index) {
+          if (column === "time" || column === "sequence_number") {
+            return;
+          }
+
+          console.log("series:"+series.name + ": "+series.points.length + " points");
+
+          var target = series.name + "." + column;
+          var datapoints = [];
+
+          for(var i = 0; i < series.points.length; i++) {
+            var t = Math.floor(series.points[i][timeCol] / 1000);
+            var v = series.points[i][index];
+            datapoints[i] = [v,t];
+          }
+
+          output.push({ target:target, datapoints:datapoints });
+        });
+      });
+
+      return output;
+    }
+
+    function getTimeFilter(options) {
+      var from = getInfluxTime(options.range.from);
+      var until = getInfluxTime(options.range.to);
+
+      if (until === 'now()') {
+        return 'time > now() - ' + from;
+      }
+
+      return 'time > ' + from + ' and time < ' + until;
+    }
+
+    function getInfluxTime(date) {
+      if (_.isString(date)) {
+        if (date === 'now') {
+          return 'now()';
+        }
+        else if (date.indexOf('now') >= 0) {
+          return date.substring(4);
+        }
+
+        date = kbn.parseDate(date);
+      }
+
+      return to_utc_epoch_seconds(date);
+    }
+
+    function to_utc_epoch_seconds(date) {
+      return (date.getTime() / 1000).toFixed(0) + 's';
+    }
+
+
+    return InfluxDatasource;
+
+  });
+
+});

文件差异内容过多而无法显示
+ 0 - 0
src/css/bootstrap.dark.min.css


文件差异内容过多而无法显示
+ 0 - 0
src/css/bootstrap.light.min.css


+ 19 - 2
src/css/less/grafana.less

@@ -318,15 +318,32 @@
 
 input[type=text].grafana-target-text-input {
   padding: 5px 7px;
-  border: 1px solid @grafanaTargetSegmentBorder;
+  border: none;
   margin: 0px;
   background: transparent;
-  width: 80%;
   float: left;
   color: @grafanaTargetColor;
   border-radius: 0;
 }
 
+input[type=text].grafana-target-segment-input {
+  border: none;
+  border-right: 1px solid @grafanaTargetSegmentBorder;
+  margin: 0px;
+  border-radius: 0;
+  height: 22px;
+  line-height: 22px;
+}
+
+select.grafana-target-segment-input {
+  border: none;
+  border-right: 1px solid @grafanaTargetSegmentBorder;
+  margin: 0px;
+  border-radius: 0;
+  height: 30px;
+  line-height: 30px;
+}
+
 .grafana-target .dropdown {
   padding: 0; margin: 0;
 }

部分文件因为文件数量过多而无法显示