Browse Source

Merge branch 'new_func_editor'

Torkel Ödegaard 11 years ago
parent
commit
adfa341d6e

+ 28 - 8
src/app/controllers/graphiteTarget.js

@@ -222,24 +222,44 @@ function (angular, _, config, gfunc, Parser) {
       $scope.targetChanged();
       $scope.targetChanged();
     };
     };
 
 
-    $scope.functionParamsChanged = function(func) {
-      func.updateText();
-      $scope.targetChanged();
-    };
-
     $scope.addFunction = function(funcDef) {
     $scope.addFunction = function(funcDef) {
-      $scope.functions.push(gfunc.createFuncInstance(funcDef));
+      var newFunc = gfunc.createFuncInstance(funcDef);
+      newFunc.added = true;
+      $scope.functions.push(newFunc);
+
+      $scope.moveAliasFuncLast();
+      $scope.smartlyHandleNewAliasByNode(newFunc);
+
+      if (!funcDef.params && newFunc.added) {
+        $scope.targetChanged();
+      }
+    };
 
 
+    $scope.moveAliasFuncLast = function() {
       var aliasFunc = _.find($scope.functions, function(func) {
       var aliasFunc = _.find($scope.functions, function(func) {
-        return func.def.name === 'alias';
+        return func.def.name === 'alias' ||
+               func.def.name === 'aliasByNode' ||
+               func.def.name === 'aliasByMetric';
       });
       });
 
 
       if (aliasFunc) {
       if (aliasFunc) {
         $scope.functions = _.without($scope.functions, aliasFunc);
         $scope.functions = _.without($scope.functions, aliasFunc);
         $scope.functions.push(aliasFunc);
         $scope.functions.push(aliasFunc);
       }
       }
+    };
 
 
-      $scope.targetChanged();
+    $scope.smartlyHandleNewAliasByNode = function(func) {
+      if (func.def.name !== 'aliasByNode') {
+        return;
+      }
+      for(var i = 0; i < $scope.segments.length; i++) {
+        if ($scope.segments[i].val.indexOf('*') >= 0)  {
+          func.params[0] = i;
+          func.added = false;
+          $scope.targetChanged();
+          return;
+        }
+      }
     };
     };
 
 
     $scope.duplicate = function() {
     $scope.duplicate = function() {

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

@@ -13,5 +13,6 @@ define([
   './grafanaGraph',
   './grafanaGraph',
   './bootstrap-tagsinput',
   './bootstrap-tagsinput',
   './bodyClass',
   './bodyClass',
-  './addGraphiteFunc'
+  './addGraphiteFunc',
+  './graphiteFuncEditor'
 ], function () {});
 ], function () {});

+ 216 - 0
src/app/directives/graphiteFuncEditor.js

@@ -0,0 +1,216 @@
+define([
+  'angular',
+  'underscore',
+  'jquery',
+],
+function (angular, _, $) {
+  'use strict';
+
+  angular
+    .module('kibana.directives')
+    .directive('graphiteFuncEditor', function($compile) {
+
+      var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
+      var paramTemplate = '<input type="text" style="display:none"' +
+                          ' class="input-mini grafana-function-param-input"></input>';
+
+      var funcControlsTemplate =
+         '<div class="graphite-func-controls">' +
+           '<span class="pointer icon-arrow-left"></span>' +
+           '<span class="pointer icon-info-sign"></span>' +
+           '<span class="pointer icon-remove" ></span>' +
+           '<span class="pointer icon-arrow-right"></span>' +
+         '</div>';
+
+      return {
+        restrict: 'A',
+        link: function postLink($scope, elem) {
+          var $funcLink = $(funcSpanTemplate);
+          var $funcControls = $(funcControlsTemplate);
+          var func = $scope.func;
+          var funcDef = func.def;
+
+          function clickFuncParam(paramIndex) {
+            /*jshint validthis:true */
+
+            var $link = $(this);
+            var $input = $link.next();
+
+            $input.val(func.params[paramIndex]);
+            $input.css('width', ($link.width() + 16) + 'px');
+
+            $link.hide();
+            $input.show();
+            $input.focus();
+            $input.select();
+
+            var typeahead = $input.data('typeahead');
+            if (typeahead) {
+              $input.val('');
+              typeahead.lookup();
+            }
+          }
+
+          function inputBlur(paramIndex) {
+            /*jshint validthis:true */
+
+            var $input = $(this);
+            var $link = $input.prev();
+
+            if ($input.val() !== '') {
+              $link.text($input.val());
+
+              if (func.updateParam($input.val(), paramIndex)) {
+                $scope.$apply(function() {
+                  $scope.targetChanged();
+                });
+              }
+            }
+
+            $input.hide();
+            $link.show();
+          }
+
+          function inputKeyPress(paramIndex, e) {
+            /*jshint validthis:true */
+
+            if(e.which === 13) {
+              inputBlur.call(this, paramIndex);
+            }
+          }
+
+          function inputKeyDown() {
+            /*jshint validthis:true */
+            this.style.width = (3 + this.value.length) * 8 + 'px';
+          }
+
+          function addTypeahead($input, paramIndex) {
+            $input.attr('data-provide', 'typeahead');
+
+            var options = funcDef.params[paramIndex].options;
+            if (funcDef.params[paramIndex].type === 'int') {
+              options = _.map(options, function(val) { return val.toString(); } );
+            }
+
+            $input.typeahead({
+              source: options,
+              minLength: 0,
+              items: 20,
+              updater: function (value) {
+                setTimeout(function() {
+                  inputBlur.call($input[0], paramIndex);
+                }, 0);
+                return value;
+              }
+            });
+
+            var typeahead = $input.data('typeahead');
+            typeahead.lookup = function () {
+              this.query = this.$element.val() || '';
+              return this.process(this.source);
+            };
+          }
+
+          function toggleFuncControls() {
+            var targetDiv = elem.closest('.grafana-target-inner');
+
+            if (elem.hasClass('show-function-controls')) {
+              elem.removeClass('show-function-controls');
+              targetDiv.removeClass('has-open-function');
+              $funcControls.hide();
+              return;
+            }
+
+            elem.addClass('show-function-controls');
+            targetDiv.addClass('has-open-function');
+
+            $funcControls.show();
+          }
+
+          function addElementsAndCompile() {
+            $funcControls.appendTo(elem);
+            $funcLink.appendTo(elem);
+
+            _.each(funcDef.params, function(param, index) {
+              var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
+              var $input = $(paramTemplate);
+
+              $paramLink.appendTo(elem);
+              $input.appendTo(elem);
+
+              $input.blur(_.partial(inputBlur, index));
+              $input.keyup(inputKeyDown);
+              $input.keypress(_.partial(inputKeyPress, index));
+              $paramLink.click(_.partial(clickFuncParam, index));
+
+              if (index !== funcDef.params.length - 1) {
+                $('<span>, </span>').appendTo(elem);
+              }
+
+              if (funcDef.params[index].options) {
+                addTypeahead($input, index);
+              }
+
+            });
+
+            $('<span>)</span>').appendTo(elem);
+
+            $compile(elem.contents())($scope);
+          }
+
+          function ifJustAddedFocusFistParam() {
+            if ($scope.func.added) {
+              $scope.func.added = false;
+              setTimeout(function() {
+                elem.find('.graphite-func-param-link').first().click();
+              }, 10);
+            }
+          }
+
+          function registerFuncControlsToggle() {
+            $funcLink.click(toggleFuncControls);
+          }
+
+          function registerFuncControlsActions() {
+            $funcControls.click(function(e) {
+              var $target = $(e.target);
+              if ($target.hasClass('icon-remove')) {
+                toggleFuncControls();
+                $scope.$apply(function() {
+                  $scope.removeFunction($scope.func);
+                });
+                return;
+              }
+
+              if ($target.hasClass('icon-arrow-left')) {
+                $scope.$apply(function() {
+                  _.move($scope.functions, $scope.$index, $scope.$index - 1);
+                });
+                return;
+              }
+
+              if ($target.hasClass('icon-arrow-right')) {
+                $scope.$apply(function() {
+                  _.move($scope.functions, $scope.$index, $scope.$index + 1);
+                });
+                return;
+              }
+
+              if ($target.hasClass('icon-info-sign')) {
+                window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank');
+                return;
+              }
+            });
+          }
+
+          addElementsAndCompile();
+          ifJustAddedFocusFistParam();
+          registerFuncControlsToggle();
+          registerFuncControlsActions();
+        }
+      };
+
+    });
+
+
+});

+ 86 - 75
src/app/partials/graphite/editor.html

@@ -7,88 +7,99 @@
         ng-controller="GraphiteTargetCtrl"
         ng-controller="GraphiteTargetCtrl"
         ng-init="init()">
         ng-init="init()">
 
 
-    <div class="grafana-target-inner-wrapper">
-      <div class="grafana-target-inner">
-        <ul class="grafana-target-controls">
-          <li ng-show="parserError">
-            <a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
-              <i class="icon-warning-sign"></i>
-            </a>
-          </li>
-          <li>
-            <a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
-              <i class="icon-pencil"></i>
-            </a>
-          </li>
-          <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>
+    <div class="grafana-target-inner">
+      <ul class="grafana-target-controls">
+        <li ng-show="parserError">
+          <a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
+            <i class="icon-warning-sign"></i>
+          </a>
+        </li>
+        <li>
+          <a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
+            <i class="icon-pencil"></i>
+          </a>
+        </li>
+        <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-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>
 
 
-        <input  type="text"
-                class="grafana-target-text-input span10"
-                ng-model="target.target"
-                focus-me="showTextEditor"
-                spellcheck='false'
-                ng-model-onblur ng-change="targetTextChanged()"
-                ng-show="showTextEditor" />
+      <input  type="text"
+              class="grafana-target-text-input span10"
+              ng-model="target.target"
+              focus-me="showTextEditor"
+              spellcheck='false'
+              ng-model-onblur ng-change="targetTextChanged()"
+              ng-show="showTextEditor" />
 
 
-        <ul class="grafana-segment-list" role="menu" ng-hide="showTextEditor">
-          <li class="dropdown" ng-repeat="segment in segments" role="menuitem">
-            <a  tabindex="1"
-                class="grafana-target-segment dropdown-toggle"
-                data-toggle="dropdown"
-                ng-click="getAltSegments($index)"
-                focus-me="segment.focus"
-                ng-bind-html-unsafe="segment.html">
-            </a>
-            <ul class="dropdown-menu scrollable grafana-segment-dropdown-menu" role="menu">
-              <li ng-repeat="altSegment in altSegments" role="menuitem">
-                <a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
-              </li>
-            </ul>
-          </li>
-          <li ng-repeat="func in functions">
-            <a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/partials/graphite/funcEditor.html'" data-placement="bottom">
-              {{func.text}}
-            </a>
-          </li>
-          <li class="dropdown" graphite-add-func>
+      <ul class="grafana-segment-list" role="menu" ng-hide="showTextEditor">
+        <li class="dropdown" ng-repeat="segment in segments" role="menuitem">
+          <a  tabindex="1"
+              class="grafana-target-segment dropdown-toggle"
+              data-toggle="dropdown"
+              ng-click="getAltSegments($index)"
+              focus-me="segment.focus"
+              ng-bind-html-unsafe="segment.html">
+          </a>
+          <ul class="dropdown-menu scrollable grafana-segment-dropdown-menu" role="menu">
+            <li ng-repeat="altSegment in altSegments" role="menuitem">
+              <a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
+            </li>
+          </ul>
+        </li>
+        <li ng-repeat="func in functions">
+          <span graphite-func-editor class="grafana-target-segment grafana-target-function">
+          </span>
+          <!-- <a  class="grafana-target-segment grafana-target-function dropdown-toggle"
+              bs-popover="'app/partials/graphite/funcEditor.html'"
+              data-placement="bottom">
+            {{func.def.name}}
+          </a> -->
+          <!-- <span class="grafana-target-segment grafana-target-function">
+            <span>{{func.def.name}}(</span><span ng-repeat="param in func.def.params">
+              <input  type="text"
+                      class="input-mini grafana-function-param-input"
+                      dynamic-width
+                      ng-model="func.params[$index]"></input>
+            </span><span>)</span>
+          </span> -->
+        </li>
+        <li class="dropdown" graphite-add-func>
 
 
-          </li>
-        </ul>
+        </li>
+      </ul>
 
 
 
 
-        <div class="clearfix"></div>
-      </div>
+      <div class="clearfix"></div>
     </div>
     </div>
   </div>
   </div>
 </div>
 </div>
 
 
+

+ 0 - 51
src/app/partials/graphite/funcEditor.html

@@ -1,51 +0,0 @@
-<div class="grafana-func-editor">
-
-  <div class="grafana-func-editor-header">
-   <a ng-click="removeFunction(func)">
-      Remove
-    </a>
-    &nbsp;&nbsp;
-    <a ng-click="helpFunction(func)">
-      Help
-    </a>
-    &nbsp;&nbsp;
-    <a class="close" ng-click="dismiss();" href="">×</a>
-  </div>
-
-  <div class="editor-row" ng-if="func.def.params.length">
-    <div class="section">
-      <div class="editor-option" ng-repeat="param in func.def.params">
-        <label class="small">{{param.name}}</label>
-        <div ng-switch on="param.type">
-          <div ng-switch-when="int">
-             <input
-                type="number"
-                step="any"
-                focus-me="true"
-                class="input-mini"
-                ng-change="functionParamsChanged(func)" ng-model-onblur
-                ng-model="func.params[$index]" />
-          </div>
-          <div ng-switch-when="string">
-             <input
-                type="text"
-                focus-me="true"
-                class="input-small"
-                ng-change="functionParamsChanged(func)" ng-model-onblur
-                ng-model="func.params[$index]" />
-          </div>
-          <div ng-switch-when="select">
-              <select
-                  class="input-mini"
-                  ng-model="func.params[$index]"
-                  ng-change="functionParamsChanged(func)"
-                  focus-me="true"
-                  ng-options="f for f in param.options">
-              </select>
-          </div>
-        </div>
-
-      </div>
-    </div>
-  </div>
-</div>

+ 21 - 6
src/app/services/graphite/gfunc.js

@@ -89,12 +89,12 @@ function (_) {
     params: [
     params: [
       {
       {
         name: "node",
         name: "node",
-        type: "select",
+        type: "int",
         options: [1,2,3,4,5,6,7,8,9,10,12]
         options: [1,2,3,4,5,6,7,8,9,10,12]
       },
       },
       {
       {
         name: "function",
         name: "function",
-        type: "select",
+        type: "string",
         options: ['sum', 'avg']
         options: ['sum', 'avg']
       }
       }
     ],
     ],
@@ -104,7 +104,7 @@ function (_) {
   addFuncDef({
   addFuncDef({
     name: 'aliasByNode',
     name: 'aliasByNode',
     category: categories.Special,
     category: categories.Special,
-    params: [ { name: "node", type: "select", options: [0,1,2,3,4,5,6,7,8,9,10,12] } ],
+    params: [ { name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] } ],
     defaultParams: [3]
     defaultParams: [3]
   });
   });
 
 
@@ -211,14 +211,14 @@ function (_) {
     category: categories.Filter,
     category: categories.Filter,
     params: [ { name: "n", type: "int", } ],
     params: [ { name: "n", type: "int", } ],
     defaultParams: [25]
     defaultParams: [25]
-  }); 
+  });
 
 
   addFuncDef({
   addFuncDef({
     name: 'currentBelow',
     name: 'currentBelow',
     category: categories.Filter,
     category: categories.Filter,
     params: [ { name: "n", type: "int", } ],
     params: [ { name: "n", type: "int", } ],
     defaultParams: [25]
     defaultParams: [25]
-  }); 
+  });
 
 
   addFuncDef({
   addFuncDef({
     name: "exclude",
     name: "exclude",
@@ -279,7 +279,7 @@ function (_) {
     this.updateText();
     this.updateText();
   }
   }
 
 
-  FuncInstance.prototype.render = function (metricExp) {
+  FuncInstance.prototype.render = function(metricExp) {
     var str = this.def.name + '(';
     var str = this.def.name + '(';
     var parameters = _.map(this.params, function(value) {
     var parameters = _.map(this.params, function(value) {
       return _.isString(value) ? "'" + value + "'" : value;
       return _.isString(value) ? "'" + value + "'" : value;
@@ -292,6 +292,21 @@ function (_) {
     return str + parameters.join(',') + ')';
     return str + parameters.join(',') + ')';
   };
   };
 
 
+  FuncInstance.prototype.updateParam = function(strValue, index) {
+    var oldValue = this.params[index];
+
+    if (this.def.params[index].type === 'int') {
+      this.params[index] = parseInt(strValue, 10);
+    }
+    else {
+      this.params[index] = strValue;
+    }
+
+    this.updateText();
+
+    return oldValue !== this.params[index];
+  };
+
   FuncInstance.prototype.updateText = function () {
   FuncInstance.prototype.updateText = function () {
     if (this.params.length === 0) {
     if (this.params.length === 0) {
       this.text = this.def.name + '()';
       this.text = this.def.name + '()';

File diff suppressed because it is too large
+ 0 - 0
src/css/bootstrap.dark.min.css


File diff suppressed because it is too large
+ 0 - 0
src/css/bootstrap.light.min.css


+ 40 - 27
src/css/less/grafana.less

@@ -221,10 +221,6 @@
   border-bottom: 1px solid @grafanaTargetBorder;
   border-bottom: 1px solid @grafanaTargetBorder;
 }
 }
 
 
-.grafana-target-inner-wrapper {
-  width: 100%;
-}
-
 .grafana-target-inner {
 .grafana-target-inner {
   border-top: 1px solid @grafanaTargetBorder;
   border-top: 1px solid @grafanaTargetBorder;
   border-left: 1px solid @grafanaTargetBorder;
   border-left: 1px solid @grafanaTargetBorder;
@@ -262,6 +258,10 @@
   color: @grafanaTargetColor;
   color: @grafanaTargetColor;
   display: inline-block;
   display: inline-block;
 
 
+  .has-open-function & {
+    padding-top: 25px;
+  }
+
   .grafana-target-hidden & {
   .grafana-target-hidden & {
     color: @grafanaTargetColorHide;
     color: @grafanaTargetColorHide;
   }
   }
@@ -269,18 +269,34 @@
   &:hover, &:focus {
   &:hover, &:focus {
     text-decoration: none;
     text-decoration: none;
   }
   }
-  &:hover {
+  &a:hover {
     background: @grafanaTargetFuncBackground;
     background: @grafanaTargetFuncBackground;
   }
   }
 }
 }
 
 
 .grafana-target-function {
 .grafana-target-function {
   background: @grafanaTargetFuncBackground;
   background: @grafanaTargetFuncBackground;
-  &:hover {
-    background: @grafanaTargetFuncHightlight;
+  > a {
+    color: @grafanaTargetColor;
+  }
+  > a:hover {
+    color: @linkColor;
+  }
+
+  &.show-function-controls {
+    padding-top: 5px;
+    min-width: 100px;
+    text-align: center;
   }
   }
 }
 }
 
 
+input[type=text].grafana-function-param-input {
+  background: transparent;
+  border: none;
+  margin: 0;
+  padding: 0;
+}
+
 .grafana-target-controls-left {
 .grafana-target-controls-left {
   list-style: none;
   list-style: none;
   float: left;
   float: left;
@@ -348,13 +364,23 @@ select.grafana-target-segment-input {
   padding: 0; margin: 0;
   padding: 0; margin: 0;
 }
 }
 
 
-input[type=text].func-param {
-  border: none;
-  background: inherit;
-  width: 30px;
-  border-radius: none;
-  padding: 0;
-  margin: 0;
+.graphite-func-controls {
+  display: none;
+  text-align: center;
+
+  .icon-arrow-left {
+    float: left;
+    position: relative;
+    top: 2px;
+  }
+  .icon-arrow-right {
+    float: right;
+    position: relative;
+    top: 2px;
+  }
+  .icon-remove {
+    margin-left: 10px;
+  }
 }
 }
 
 
 .grafana-target {
 .grafana-target {
@@ -363,19 +389,6 @@ input[type=text].func-param {
   }
   }
 }
 }
 
 
-.grafana-func-editor {
-  min-width: 140px;
-  .grafana-func-editor-header {
-    background: @grafanaTargetFuncHightlight;
-    text-align: center;
-    border-bottom: 1px solid @grafanaTargetFuncBackground;
-    padding: 3px 5px;
-    white-space: nowrap;
-  }
-  .editor-row {
-    padding: 5px;
-  }
-}
 
 
 .scrollable {
 .scrollable {
   max-height: 300px;
   max-height: 300px;

Some files were not shown because too many files changed in this diff